#566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - sameByPlan() & sameByBind()

This commit is contained in:
Robin Bygrave
2016-02-15 23:44:56 +13:00
parent 165d4aca92
commit 3d91a4f927
72 changed files with 3119 additions and 339 deletions
@@ -0,0 +1,14 @@
package com.avaje.ebeaninternal.api;
/**
* Key used for caching query plans for ORM and RawSql queries.
*/
public interface CQueryPlanKey {
/**
* Used by read audit such that we can log read audit entries without the full sql
* (which would make the read audit logs verbose).
*/
String getPartialKey();
}
@@ -5,32 +5,18 @@ package com.avaje.ebeaninternal.api;
*/
public class HashQuery {
private final HashQueryPlan planHash;
private final CQueryPlanKey planHash;
private final int bindHash;
/**
* Create the HashQuery.
*/
public HashQuery(HashQueryPlan planHash, int bindHash) {
public HashQuery(CQueryPlanKey planHash, int bindHash) {
this.planHash = planHash;
this.bindHash = bindHash;
}
/**
* Return the query plan hash.
*/
public HashQueryPlan getPlanHash() {
return planHash;
}
/**
* Return the bind values hash.
*/
public int getBindHash() {
return bindHash;
}
public int hashCode() {
int hc = 31 * planHash.hashCode();
hc = 31 * hc + bindHash;
@@ -9,14 +9,12 @@ public class HashQueryPlanBuilder {
private int bindCount;
private String rawSql;
public HashQueryPlanBuilder() {
this.planHash = 92821;
}
public String toString() {
return planHash+":"+bindCount+(rawSql != null ? ":r" : "");
return planHash+":"+bindCount;
}
/**
@@ -58,19 +56,24 @@ public class HashQueryPlanBuilder {
bindCount += extraBindCount;
}
/**
* Add raw sql to the hash.
*/
public void addRawSql(String rawSql) {
this.rawSql = rawSql;
public void bindIfNotNull(Object someValue) {
if (someValue != null) {
bindCount++;
}
}
/**
* Build and return the calculated HashQueryPlan.
*/
public HashQueryPlan build() {
return new HashQueryPlan(rawSql, planHash, bindCount);
public String build() {
return planHash+"_"+bindCount;
}
public int getPlanHash() {
return planHash;
}
public int getBindCount() {
return bindCount;
}
}
@@ -37,6 +37,16 @@ public interface SpiExpression extends Expression {
*/
int queryBindHash();
/**
* Return true if the expression is the same without taking into account bind values.
*/
boolean isSameByPlan(SpiExpression other);
/**
* Return true if the expression is the same with respect to bind values.
*/
boolean isSameByBind(SpiExpression other);
/**
* Add some sql to the query.
* <p>
@@ -65,4 +75,10 @@ public interface SpiExpression extends Expression {
* Validate all the properties/paths associated with this expression.
*/
void validate(SpiExpressionValidation validation);
/**
* Return a copy of the expression for use in the query plan key.
*/
SpiExpression copyForPlanKey();
}
@@ -1,16 +1,13 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.ExpressionFactory;
import com.avaje.ebean.ExpressionList;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import java.util.List;
/**
* Internal extension of ExpressionList.
*/
public interface SpiExpressionList<T> extends ExpressionList<T> {
public interface SpiExpressionList<T> extends ExpressionList<T>, SpiExpression {
/**
* Return the underlying list of expressions.
@@ -22,57 +19,9 @@ public interface SpiExpressionList<T> extends ExpressionList<T> {
*/
SpiExpressionList<?> trimPath(int prefixTrim);
/**
* Restore the ExpressionFactory after deserialisation.
*/
void setExpressionFactory(ExpressionFactory expr);
/**
* Process "Many" properties populating ManyWhereJoins.
* <p>
* Predicates on Many properties require an extra independent join clause.
* </p>
*/
void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoins);
/**
* Return true if this list is empty.
*/
boolean isEmpty();
/**
* Concatenate the expression sql into a String.
* <p>
* The list of expressions are evaluated in order building a sql statement
* with bind parameters.
* </p>
*/
String buildSql(SpiExpressionRequest request);
/**
* Combine the expression bind values into a list.
* <p>
* Expressions are evaluated in order and all the resulting bind values are
* returned as a List.
* </p>
*
* @return the list of all the bind values in order.
*/
List<Object> buildBindValues(SpiExpressionRequest request);
/**
* Prepare the expressions contained in the list. For example, compile sub-query expressions etc.
*/
void prepareExpression(BeanQueryRequest<?> request);
/**
* Calculate a hash based on the expressions but excluding the actual bind
* values.
*/
void queryPlanHash(HashQueryPlanBuilder builder);
/**
* Validate all the properties/paths used in this expression list.
*/
void validate(SpiExpressionValidation validation);
}
@@ -467,17 +467,13 @@ public interface SpiQuery<T> extends Query<T> {
String getName();
/**
* Identifies queries that are the same bar the bind variables.
* Prepare the query which prepares sub-query expressions and calculates
* and returns the query plan key.
* <p>
* This is used AFTER AutoTune has potentially tuned the query. This is
* used to identify and reused query plans (the final SQL string and
* associated SqlTree object).
* </p>
* <p>
* Excludes the actual bind values (as they don't effect the query plan).
* The query plan excludes actual bind values (as they don't effect the query plan).
* </p>
*/
HashQueryPlan queryPlanHash(BeanQueryRequest<?> request);
CQueryPlanKey prepare(BeanQueryRequest<?> request);
/**
* Calculate a hash based on the bind values used in the query.
@@ -1110,7 +1110,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
// the query hash after any tuning
request.calculateQueryPlanHash();
request.prepareQuery();
return request;
}
@@ -1,14 +1,11 @@
package com.avaje.ebeaninternal.server.core;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.PersistenceException;
import com.avaje.ebean.*;
import com.avaje.ebean.PersistenceContextScope;
import com.avaje.ebean.QueryEachConsumer;
import com.avaje.ebean.QueryEachWhileConsumer;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.RawSql;
import com.avaje.ebean.Version;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.PersistenceContext;
@@ -16,8 +13,8 @@ import com.avaje.ebean.event.BeanFindController;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.BeanIdList;
import com.avaje.ebeaninternal.api.HashQuery;
import com.avaje.ebeaninternal.api.HashQueryPlan;
import com.avaje.ebeaninternal.api.LoadContext;
import com.avaje.ebeaninternal.api.CQueryPlanKey;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiQuery.Type;
@@ -32,6 +29,13 @@ import com.avaje.ebeaninternal.server.query.CQueryPlan;
import com.avaje.ebeaninternal.server.query.CancelableQuery;
import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
import javax.persistence.PersistenceException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Wraps the objects involved in executing a Query.
*/
@@ -55,7 +59,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
private HashQuery cacheKey;
private HashQueryPlan queryPlanHash;
private CQueryPlanKey queryPlanKey;
/**
* Create the InternalQueryRequest.
@@ -126,10 +130,10 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
}
/**
* Calculate the query plan hash AFTER any potential AutoTune tuning.
* Prepare the query and calculate the query plan key.
*/
public void calculateQueryPlanHash() {
this.queryPlanHash = query.queryPlanHash(this);
public void prepareQuery() {
this.queryPlanKey = query.prepare(this);
}
public boolean isRawSql() {
@@ -355,7 +359,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
* query plan for this query exists.
*/
public CQueryPlan getQueryPlan() {
return beanDescriptor.getQueryPlan(queryPlanHash);
return beanDescriptor.getQueryPlan(queryPlanKey);
}
/**
@@ -366,15 +370,15 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
* with just the bind variables changing.
* </p>
*/
public HashQueryPlan getQueryPlanHash() {
return queryPlanHash;
public CQueryPlanKey getQueryPlanKey() {
return queryPlanKey;
}
/**
* Put the QueryPlan into the cache.
*/
public void putQueryPlan(CQueryPlan queryPlan) {
beanDescriptor.putQueryPlan(queryPlanHash, queryPlan);
beanDescriptor.putQueryPlan(queryPlanKey, queryPlan);
}
public boolean isUseBeanCache() {
@@ -401,7 +405,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
for (T bean : actualDetails) {
ids.add(beanDescriptor.getIdForJson(bean));
}
beanDescriptor.readAuditMany(queryPlanHash.getPartialKey(), "l2-query-cache", ids);
beanDescriptor.readAuditMany(queryPlanKey.getPartialKey(), "l2-query-cache", ids);
}
return cached;
@@ -29,7 +29,7 @@ import com.avaje.ebean.event.readaudit.ReadEvent;
import com.avaje.ebean.meta.MetaBeanInfo;
import com.avaje.ebean.meta.MetaQueryPlanStatistic;
import com.avaje.ebean.plugin.SpiBeanType;
import com.avaje.ebeaninternal.api.HashQueryPlan;
import com.avaje.ebeaninternal.api.CQueryPlanKey;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiTransaction;
@@ -87,7 +87,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
private final ConcurrentHashMap<Integer, SpiUpdatePlan> updatePlanCache = new ConcurrentHashMap<Integer, SpiUpdatePlan>();
private final ConcurrentHashMap<HashQueryPlan, CQueryPlan> queryPlanCache = new ConcurrentHashMap<HashQueryPlan, CQueryPlan>();
private final ConcurrentHashMap<CQueryPlanKey, CQueryPlan> queryPlanCache = new ConcurrentHashMap<CQueryPlanKey, CQueryPlan>();
private final ConcurrentHashMap<String, ElPropertyValue> elCache = new ConcurrentHashMap<String, ElPropertyValue>();
@@ -1163,11 +1163,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
}
}
public CQueryPlan getQueryPlan(HashQueryPlan key) {
public CQueryPlan getQueryPlan(CQueryPlanKey key) {
return queryPlanCache.get(key);
}
public void putQueryPlan(HashQueryPlan key, CQueryPlan plan) {
public void putQueryPlan(CQueryPlanKey key, CQueryPlan plan) {
queryPlanCache.put(key, plan);
}
@@ -10,7 +10,7 @@ import com.avaje.ebean.SqlUpdate;
import com.avaje.ebeaninternal.api.BindParams;
import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate;
import com.avaje.ebeaninternal.server.expression.IdInExpression;
import com.avaje.ebeaninternal.util.DefaultExpressionRequest;
import com.avaje.ebeaninternal.server.expression.DefaultExpressionRequest;
public class IntersectionRow {
@@ -63,6 +63,32 @@ public final class TableJoin {
return hc;
}
@Override
public int hashCode() {
return queryHash;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TableJoin that = (TableJoin) o;
if (!table.equals(that.table)) return false;
if (type != that.type) return false;
if (columns.length != that.columns.length) return false;
for (int i = 0; i < columns.length; i++) {
if (!columns[i].equals(that.columns[i])) {
return false;
}
}
return true;
}
/**
* Return a hash value for adding to a query plan.
*/
@@ -8,76 +8,97 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoinColumn;
*/
public class TableJoinColumn {
/**
* The local database column name.
*/
private final String localDbColumn;
/**
* The local database column name.
*/
private final String localDbColumn;
/**
* The foreign database column name.
*/
private final String foreignDbColumn;
/**
* The foreign database column name.
*/
private final String foreignDbColumn;
private final boolean insertable;
private final boolean updateable;
private final boolean insertable;
/**
* Hash for including in a query plan
*/
private final int queryHash;
private final boolean updateable;
/**
* Create the pair.
*/
public TableJoinColumn(DeployTableJoinColumn deploy) {
this.localDbColumn = InternString.intern(deploy.getLocalDbColumn());
this.foreignDbColumn = InternString.intern(deploy.getForeignDbColumn());
this.insertable = deploy.isInsertable();
this.updateable = deploy.isUpdateable();
this.queryHash = hashOf(localDbColumn) * 31 + hashOf(foreignDbColumn);
}
/**
* Hash for including in a query plan
*/
private final int queryHash;
private int hashOf(String value) {
return (value == null) ? 0 : value.hashCode();
}
/**
* Create the pair.
*/
public TableJoinColumn(DeployTableJoinColumn deploy) {
this.localDbColumn = InternString.intern(deploy.getLocalDbColumn());
this.foreignDbColumn = InternString.intern(deploy.getForeignDbColumn());
this.insertable = deploy.isInsertable();
this.updateable = deploy.isUpdateable();
this.queryHash = hash();
}
public String toString() {
return localDbColumn+" = "+foreignDbColumn;
}
int hash() {
int result = localDbColumn != null ? localDbColumn.hashCode() : 0;
result = 31 * result + (foreignDbColumn != null ? foreignDbColumn.hashCode() : 0);
result = 31 * result + (insertable ? 1 : 0);
result = 31 * result + (updateable ? 1 : 0);
return result;
}
/**
* Return a hash for including in a query plan.
*/
public int queryHash() {
return queryHash;
}
@Override
public int hashCode() {
return queryHash;
}
/**
* Return the foreign database column name.
*/
public String getForeignDbColumn() {
return foreignDbColumn;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
/**
* Return the local database column name.
*/
public String getLocalDbColumn() {
return localDbColumn;
}
TableJoinColumn that = (TableJoinColumn) o;
if (insertable != that.insertable) return false;
if (updateable != that.updateable) return false;
if (!localDbColumn.equals(that.localDbColumn)) return false;
return foreignDbColumn.equals(that.foreignDbColumn);
}
/**
* Return true if this column should be insertable.
*/
public boolean isInsertable() {
return insertable;
}
public String toString() {
return localDbColumn + " = " + foreignDbColumn;
}
/**
* Return true if this column should be updateable.
*/
public boolean isUpdateable() {
return updateable;
}
/**
* Return a hash for including in a query plan.
*/
public int queryHash() {
return queryHash;
}
/**
* Return the foreign database column name.
*/
public String getForeignDbColumn() {
return foreignDbColumn;
}
/**
* Return the local database column name.
*/
public String getLocalDbColumn() {
return localDbColumn;
}
/**
* Return true if this column should be insertable.
*/
public boolean isInsertable() {
return insertable;
}
/**
* Return true if this column should be updateable.
*/
public boolean isUpdateable() {
return updateable;
}
}
@@ -22,19 +22,20 @@ public abstract class AbstractExpression implements SpiExpression {
this.propName = propName;
}
public String getPropertyName() {
return propName;
@Override
public SpiExpression copyForPlanKey() {
return this;
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
String propertyName = getPropertyName();
if (propertyName != null) {
ElPropertyDeploy elProp = desc.getElPropertyDeploy(propertyName);
if (propName != null) {
ElPropertyDeploy elProp = desc.getElPropertyDeploy(propName);
if (elProp != null) {
if (elProp.containsFormulaWithJoin()) {
// for findRowCount query select clause
manyWhereJoin.addFormulaWithJoin(propertyName);
manyWhereJoin.addFormulaWithJoin(propName);
}
if (elProp.containsMany()) {
// for findRowCount we join to a many property
@@ -51,12 +52,11 @@ public abstract class AbstractExpression implements SpiExpression {
@Override
public void validate(SpiExpressionValidation validation) {
validation.validate(getPropertyName());
validation.validate(propName);
}
protected ElPropertyValue getElProp(SpiExpressionRequest request) {
protected final ElPropertyValue getElProp(SpiExpressionRequest request) {
String propertyName = getPropertyName();
return request.getBeanDescriptor().getElGetValue(propertyName);
return request.getBeanDescriptor().getElGetValue(propName);
}
}
@@ -2,11 +2,13 @@ package com.avaje.ebeaninternal.server.expression;
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.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
@@ -102,7 +104,7 @@ class AllEqualsExpression extends NonPrepareExpression {
Object value = entry.getValue();
String propName = entry.getKey();
builder.add(propName).add(value == null ? 0 : 1);
builder.bind(value == null ? 0 : 1);
builder.bindIfNotNull(value);
}
}
@@ -116,4 +118,48 @@ class AllEqualsExpression extends NonPrepareExpression {
return hc;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof AllEqualsExpression)) {
return false;
}
AllEqualsExpression that = (AllEqualsExpression) other;
return isSameByValue(that, false);
}
@Override
public boolean isSameByBind(SpiExpression other) {
if (!(other instanceof AllEqualsExpression)) {
return false;
}
AllEqualsExpression that = (AllEqualsExpression) other;
return isSameByValue(that, true);
}
private boolean isSameByValue(AllEqualsExpression that, boolean byValue) {
if (propMap.size() != that.propMap.size()) {
return false;
}
Iterator<Entry<String, Object>> thisIt = propMap.entrySet().iterator();
Iterator<Entry<String, Object>> thatIt = that.propMap.entrySet().iterator();
while (thisIt.hasNext() && thatIt.hasNext()) {
Entry<String, Object> thisNext = thisIt.next();
Entry<String, Object> thatNext = thatIt.next();
if (!thisNext.getKey().equals(thatNext.getKey())) {
return false;
}
if (!Same.sameBy(byValue, thisNext.getValue(), thatNext.getValue())) {
return false;
}
}
return true;
}
}
@@ -1,10 +1,9 @@
package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
class BetweenExpression extends AbstractExpression {
private static final long serialVersionUID = 2078918165221454910L;
@@ -30,7 +29,7 @@ class BetweenExpression extends AbstractExpression {
@Override
public void addSql(SpiExpressionRequest request) {
request.append(getPropertyName()).append(BETWEEN).append(" ? and ? ");
request.append(propName).append(BETWEEN).append(" ? and ? ");
}
@Override
@@ -45,4 +44,21 @@ class BetweenExpression extends AbstractExpression {
hc = hc * 31 + valueHigh.hashCode();
return hc;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof BetweenExpression)) {
return false;
}
BetweenExpression that = (BetweenExpression) other;
return this.propName.equals(that.propName);
}
@Override
public boolean isSameByBind(SpiExpression other) {
BetweenExpression that = (BetweenExpression) other;
return valueLow.equals(that.valueLow)
&& valueHigh.equals(that.valueHigh);
}
}
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.expression;
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.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
@@ -50,6 +51,7 @@ class BetweenPropertyExpression extends NonPrepareExpression {
validation.validate(highProperty);
}
@Override
public void addBindValues(SpiExpressionRequest request) {
request.addBindValue(value);
}
@@ -70,4 +72,21 @@ class BetweenPropertyExpression extends NonPrepareExpression {
public int queryBindHash() {
return value.hashCode();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof BetweenPropertyExpression)) {
return false;
}
BetweenPropertyExpression that = (BetweenPropertyExpression) other;
return lowProperty.equals(that.lowProperty)
&& highProperty.equals(that.highProperty);
}
@Override
public boolean isSameByBind(SpiExpression other) {
BetweenPropertyExpression that = (BetweenPropertyExpression) other;
return value.equals(that.value);
}
}
@@ -1,7 +1,7 @@
package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
@@ -16,6 +16,7 @@ class CaseInsensitiveEqualExpression extends AbstractExpression {
this.value = value.toLowerCase();
}
@Override
public void addBindValues(SpiExpressionRequest request) {
ElPropertyValue prop = getElProp(request);
@@ -28,14 +29,13 @@ class CaseInsensitiveEqualExpression extends AbstractExpression {
request.addBindValue(value);
}
@Override
public void addSql(SpiExpressionRequest request) {
String propertyName = getPropertyName();
String pname = propertyName;
String pname = propName;
ElPropertyValue prop = getElProp(request);
if (prop != null && prop.isDbEncrypted()) {
pname = prop.getBeanProperty().getDecryptProperty(propertyName);
pname = prop.getBeanProperty().getDecryptProperty(propName);
}
request.append("lower(").append(pname).append(") =? ");
@@ -51,4 +51,20 @@ class CaseInsensitiveEqualExpression extends AbstractExpression {
public int queryBindHash() {
return value.hashCode();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof CaseInsensitiveEqualExpression)) {
return false;
}
CaseInsensitiveEqualExpression that = (CaseInsensitiveEqualExpression) other;
return this.propName.equals(that.propName);
}
@Override
public boolean isSameByBind(SpiExpression other) {
CaseInsensitiveEqualExpression that = (CaseInsensitiveEqualExpression) other;
return value.equals(that.value);
}
}
@@ -86,6 +86,19 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio
this.likeType = likeType;
}
DefaultExampleExpression(ArrayList<SpiExpression> source) {
this.entity = null;
this.list = new ArrayList<SpiExpression>(source.size());
for (SpiExpression expression : source) {
list.add(expression.copyForPlanKey());
}
}
@Override
public SpiExpression copyForPlanKey() {
return new DefaultExampleExpression(list);
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins) {
if (list != null) {
@@ -201,6 +214,38 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio
return hc;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof DefaultExampleExpression)) {
return false;
}
DefaultExampleExpression that = (DefaultExampleExpression) other;
if (this.list.size() != that.list.size()) {
return false;
}
for (int i = 0; i < list.size(); i++) {
if (!list.get(i).isSameByPlan(that.list.get(i))) {
return false;
}
}
return true;
}
@Override
public boolean isSameByBind(SpiExpression other) {
DefaultExampleExpression that = (DefaultExampleExpression) other;
if (this.list.size() != that.list.size()) {
return false;
}
for (int i = 0; i < list.size(); i++) {
if (!list.get(i).isSameByBind(that.list.get(i))) {
return false;
}
}
return true;
}
/**
* Build the List of expressions.
*/
@@ -1,4 +1,4 @@
package com.avaje.ebeaninternal.util;
package com.avaje.ebeaninternal.server.expression;
import java.sql.Timestamp;
import java.util.ArrayList;
@@ -56,6 +56,10 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
this.listAndJoin = " and ";
}
private DefaultExpressionList() {
this(null, null, null, new ArrayList<SpiExpression>());
}
@Override
public SpiExpressionList<?> trimPath(int prefixTrim) {
throw new RuntimeException("Only allowed on FilterExpressionList");
@@ -65,17 +69,6 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return list;
}
/**
* Set the ExpressionFactory.
* <p>
* After deserialisation so that it can be further modified.
* </p>
*/
@Override
public void setExpressionFactory(ExpressionFactory expr) {
this.expr = expr;
}
/**
* Return a copy of the expression list.
* <p>
@@ -88,6 +81,14 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return copy;
}
public DefaultExpressionList<T> copyForPlanKey() {
DefaultExpressionList<T> copy = new DefaultExpressionList<T>();
for (int i = 0; i < list.size(); i++) {
copy.list.add(list.get(i).copyForPlanKey());
}
return copy;
}
/**
* Return true if one of the expressions is related to a Many property.
*/
@@ -325,7 +326,7 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
}
@Override
public String buildSql(SpiExpressionRequest request) {
public void addSql(SpiExpressionRequest request) {
request.append(listAndStart);
for (int i = 0, size = list.size(); i < size; i++) {
@@ -336,17 +337,13 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
expression.addSql(request);
}
request.append(listAndEnd);
return request.getSql();
}
@Override
public List<Object> buildBindValues(SpiExpressionRequest request) {
public void addBindValues(SpiExpressionRequest request) {
for (int i = 0, size = list.size(); i < size; i++) {
SpiExpression expression = list.get(i);
expression.addBindValues(request);
list.get(i).addBindValues(request);
}
return request.getBindValues();
}
@Override
@@ -371,6 +368,7 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
/**
* Calculate a hash based on the expressions.
*/
@Override
public int queryBindHash() {
int hash = DefaultExpressionList.class.getName().hashCode();
for (int i = 0, size = list.size(); i < size; i++) {
@@ -379,6 +377,38 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
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.
*/
@@ -1,4 +1,4 @@
package com.avaje.ebeaninternal.util;
package com.avaje.ebeaninternal.server.expression;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -40,7 +40,7 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
this.binder = binder;
this.expressionList = expressionList;
// immediately build the list of bind values (callback style)
expressionList.buildBindValues(this);
expressionList.addBindValues(this);
}
public DefaultExpressionRequest(BeanDescriptor<?> beanDescriptor) {
@@ -55,7 +55,8 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
* Build sql for the underlying expression list.
*/
public String buildSql() {
return expressionList.buildSql(this);
expressionList.addSql(this);
return sql.toString();
}
/**
@@ -71,10 +72,12 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
}
}
@Override
public JsonExpressionHandler getJsonHandler() {
return binder.getJsonExpressionHandler();
}
@Override
public String parseDeploy(String logicalProp) {
String s = deployParser.getDeployWord(logicalProp);
@@ -94,14 +97,17 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
/**
* Increments the parameter index and returns that value.
*/
@Override
public int nextParameter() {
return ++paramIndex;
}
@Override
public BeanDescriptor<?> getBeanDescriptor() {
return beanDescriptor;
}
@Override
public SpiOrmQueryRequest<?> getQueryRequest() {
return queryRequest;
}
@@ -109,16 +115,19 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
/**
* Append text the underlying sql expression.
*/
@Override
public SpiExpressionRequest append(String sqlExpression) {
sql.append(sqlExpression);
return this;
}
@Override
public void addBindEncryptKey(Object bindValue) {
bindValues.add(bindValue);
bindLog("****");
}
@Override
public void addBindValue(Object bindValue) {
bindValues.add(bindValue);
bindLog(bindValue);
@@ -137,10 +146,12 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
return bindLog == null ? "" : bindLog.toString();
}
@Override
public String getSql() {
return sql.toString();
}
@Override
public List<Object> getBindValues() {
return bindValues;
}
@@ -17,19 +17,26 @@ public class ExistsExpression implements SpiExpression {
private static final long serialVersionUID = 666990277309851644L;
private final boolean not;
protected final boolean not;
private final SpiQuery<?> subQuery;
protected final SpiQuery<?> subQuery;
private List<Object> bindParams;
protected List<Object> bindParams;
private String sql;
protected String sql;
public ExistsExpression(SpiQuery<?> subQuery, boolean not) {
this.subQuery = subQuery;
this.not = not;
}
ExistsExpression(boolean not, String sql , List<Object> bindParams) {
this.not = not;
this.sql = sql;
this.bindParams = bindParams;
this.subQuery = null;
}
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
@@ -38,10 +45,15 @@ public class ExistsExpression implements SpiExpression {
this.sql = subQuery.getGeneratedSql().replace('\n', ' ');
}
@Override
public SpiExpression copyForPlanKey() {
return this;
}
/**
* Compile/build the sub query.
*/
private CQuery<?> compileSubQuery(BeanQueryRequest<?> queryRequest) {
protected CQuery<?> compileSubQuery(BeanQueryRequest<?> queryRequest) {
SpiEbeanServer ebeanServer = (SpiEbeanServer) queryRequest.getEbeanServer();
return ebeanServer.compileQuery(subQuery, queryRequest.getTransaction());
}
@@ -76,6 +88,32 @@ public class ExistsExpression implements SpiExpression {
}
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof ExistsExpression)) {
return false;
}
ExistsExpression that = (ExistsExpression) other;
return this.sql.equals(that.sql)
&& this.not == that.not
&& this.bindParams.size() == that.bindParams.size();
}
@Override
public boolean isSameByBind(SpiExpression other) {
ExistsExpression that = (ExistsExpression) other;
if (this.bindParams.size() != that.bindParams.size()) {
return false;
}
for (int i = 0; i < bindParams.size(); i++) {
if (!bindParams.get(i).equals(that.bindParams.get(i))) {
return false;
}
}
return true;
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins) {
// Nothing to do for exists expression
@@ -1,8 +1,7 @@
package com.avaje.ebeaninternal.util;
package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.*;
import com.avaje.ebeaninternal.api.SpiExpressionList;
import com.avaje.ebeaninternal.server.expression.FilterExprPath;
import javax.persistence.PersistenceException;
import java.util.List;
@@ -6,7 +6,6 @@ import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.util.DefaultExpressionRequest;
/**
* Slightly redundant as Query.setId() ultimately also does the same job.
@@ -69,4 +68,14 @@ class IdExpression extends NonPrepareExpression implements SpiExpression {
return value.hashCode();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
return other instanceof IdExpression;
}
@Override
public boolean isSameByBind(SpiExpression other) {
IdExpression that = (IdExpression) other;
return value.equals(that.value);
}
}
@@ -2,11 +2,11 @@ package com.avaje.ebeaninternal.server.expression;
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.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
import com.avaje.ebeaninternal.util.DefaultExpressionRequest;
import java.util.List;
@@ -86,4 +86,27 @@ public class IdInExpression extends NonPrepareExpression {
return idList.hashCode();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof IdInExpression)) {
return false;
}
IdInExpression that = (IdInExpression) other;
return this.idList.size() == that.idList.size();
}
@Override
public boolean isSameByBind(SpiExpression other) {
IdInExpression that = (IdInExpression) other;
if (this.idList.size() != that.idList.size()) {
return false;
}
for (int i = 0; i < idList.size(); i++) {
if (!idList.get(i).equals(that.idList.get(i))) {
return false;
}
}
return true;
}
}
@@ -1,8 +1,8 @@
package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
@@ -61,20 +61,18 @@ class InExpression extends AbstractExpression {
return;
}
String propertyName = getPropertyName();
ElPropertyValue prop = getElProp(request);
if (prop != null && !prop.isAssocId()) {
prop = null;
}
if (prop != null) {
request.append(prop.getAssocIdInExpr(propertyName));
request.append(prop.getAssocIdInExpr(propName));
String inClause = prop.getAssocIdInValueExpr(values.length);
request.append(inClause);
} else {
request.append(propertyName);
request.append(propName);
if (not) {
request.append(" not");
}
@@ -105,4 +103,29 @@ class InExpression extends AbstractExpression {
return hc;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof InExpression)) {
return false;
}
InExpression that = (InExpression) other;
return propName.equals(that.propName)
&& not == that.not
&& values.length == that.values.length;
}
@Override
public boolean isSameByBind(SpiExpression other) {
InExpression that = (InExpression) other;
if (this.values.length != that.values.length) {
return false;
}
for (int i = 0; i < values.length; i++) {
if (!values[i].equals(that.values[i])) {
return false;
}
}
return true;
}
}
@@ -1,14 +1,15 @@
package com.avaje.ebeaninternal.server.expression;
import java.util.List;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.query.CQuery;
import java.util.List;
/**
* In expression using a sub query.
*/
@@ -24,12 +25,20 @@ class InQueryExpression extends AbstractExpression {
private String sql;
public InQueryExpression(String propertyName, SpiQuery<?> subQuery, boolean not) {
InQueryExpression(String propertyName, SpiQuery<?> subQuery, boolean not) {
super(propertyName);
this.subQuery = subQuery;
this.not = not;
}
InQueryExpression(String propertyName, boolean not, String sql, List<Object> bindParams) {
super(propertyName);
this.subQuery = null;
this.not = not;
this.sql = sql;
this.bindParams = bindParams;
}
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
@@ -61,7 +70,7 @@ class InQueryExpression extends AbstractExpression {
@Override
public void addSql(SpiExpressionRequest request) {
request.append(" (").append(getPropertyName()).append(")");
request.append(" (").append(propName).append(")");
if (not) {
request.append(" not");
}
@@ -77,4 +86,31 @@ class InQueryExpression extends AbstractExpression {
request.addBindValue(bindParams.get(i));
}
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof InQueryExpression)) {
return false;
}
InQueryExpression that = (InQueryExpression) other;
return propName.equals(that.propName)
&& sql.equals(that.sql)
&& not == that.not
&& bindParams.size() == that.bindParams.size();
}
@Override
public boolean isSameByBind(SpiExpression other) {
InQueryExpression that = (InQueryExpression) other;
if (this.bindParams.size() != that.bindParams.size()) {
return false;
}
for (int i = 0; i < bindParams.size(); i++) {
if (!bindParams.get(i).equals(that.bindParams.get(i))) {
return false;
}
}
return true;
}
}
@@ -1,7 +1,7 @@
package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
/**
@@ -60,6 +60,8 @@ class JsonPathExpression extends AbstractExpression {
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(JsonPathExpression.class).add(propName).add(path).add(operator);
builder.bindIfNotNull(value);
builder.bindIfNotNull(upperValue);
}
@Override
@@ -69,6 +71,27 @@ class JsonPathExpression extends AbstractExpression {
return hc;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof JsonPathExpression)) {
return false;
}
JsonPathExpression that = (JsonPathExpression) other;
return propName.equals(that.propName)
&& operator == that.operator
&& Same.sameByValue(path, that.path)
&& Same.sameByNull(value, that.value)
&& Same.sameByNull(upperValue, that.upperValue);
}
@Override
public boolean isSameByBind(SpiExpression other) {
JsonPathExpression that = (JsonPathExpression) other;
if (value != null ? !value.equals(that.value) : that.value != null) return false;
return upperValue != null ? upperValue.equals(that.upperValue) : that.upperValue == null;
}
@Override
public void addSql(SpiExpressionRequest request) {
@@ -15,7 +15,6 @@ import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.util.DefaultExpressionList;
/**
* Junction implementation.
@@ -35,6 +34,14 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
Conjunction(com.avaje.ebean.Query<T> query, ExpressionList<T> parent) {
super(false, AND, query, parent);
}
Conjunction(DefaultExpressionList<T> expressionList) {
super(false, AND, expressionList);
}
@Override
public SpiExpression copyForPlanKey() {
return new Conjunction<T>(exprList.copyForPlanKey());
}
}
static class Disjunction<T> extends JunctionExpression<T> {
@@ -44,9 +51,18 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
Disjunction(com.avaje.ebean.Query<T> query, ExpressionList<T> parent) {
super(true, OR, query, parent);
}
Disjunction(DefaultExpressionList<T> expressionList) {
super(true, OR, expressionList);
}
@Override
public SpiExpression copyForPlanKey() {
return new Disjunction<T>(exprList.copyForPlanKey());
}
}
private final DefaultExpressionList<T> exprList;
protected final DefaultExpressionList<T> exprList;
private final String joinType;
@@ -61,6 +77,15 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
this.exprList = new DefaultExpressionList<T>(query, parent);
}
/**
* Construct for copyForPlanKey.
*/
JunctionExpression(boolean disjunction, String joinType, DefaultExpressionList<T> exprList) {
this.disjunction = disjunction;
this.joinType = joinType;
this.exprList = exprList;
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
@@ -163,6 +188,25 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
return hc;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof JunctionExpression)) {
return false;
}
JunctionExpression that = (JunctionExpression) other;
return joinType.equals(that.joinType)
&& exprList.isSameByPlan(that.exprList);
}
@Override
public boolean isSameByBind(SpiExpression other) {
JunctionExpression that = (JunctionExpression) other;
return joinType.equals(that.joinType)
&& exprList.isSameByBind(that.exprList);
}
@Override
public ExpressionList<T> endJunction() {
return exprList.endJunction();
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.LikeType;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
@@ -22,6 +23,7 @@ class LikeExpression extends AbstractExpression {
this.val = value;
}
@Override
public void addBindValues(SpiExpressionRequest request) {
ElPropertyValue prop = getElProp(request);
@@ -35,14 +37,13 @@ class LikeExpression extends AbstractExpression {
request.addBindValue(bindValue);
}
@Override
public void addSql(SpiExpressionRequest request) {
String propertyName = getPropertyName();
String pname = propertyName;
String pname = propName;
ElPropertyValue prop = getElProp(request);
if (prop != null && prop.isDbEncrypted()) {
pname = prop.getBeanProperty().getDecryptProperty(propertyName);
pname = prop.getBeanProperty().getDecryptProperty(propName);
}
if (caseInsensitive) {
request.append("lower(").append(pname).append(")");
@@ -71,6 +72,24 @@ class LikeExpression extends AbstractExpression {
return val.hashCode();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof LikeExpression)) {
return false;
}
LikeExpression that = (LikeExpression) other;
return this.propName.equals(that.propName)
&& this.caseInsensitive == that.caseInsensitive
&& this.type == that.type;
}
@Override
public boolean isSameByBind(SpiExpression other) {
LikeExpression that = (LikeExpression) other;
return val.equals(that.val);
}
private static String getValue(String value, boolean caseInsensitive, LikeType type) {
if (caseInsensitive) {
value = value.toLowerCase();
@@ -26,6 +26,12 @@ abstract class LogicExpression implements SpiExpression {
And(Expression expOne, Expression expTwo) {
super(AND, expOne, expTwo);
}
@Override
public SpiExpression copyForPlanKey() {
return new And(expOne.copyForPlanKey(), expTwo.copyForPlanKey());
}
}
static class Or extends LogicExpression {
@@ -35,11 +41,16 @@ abstract class LogicExpression implements SpiExpression {
Or(Expression expOne, Expression expTwo) {
super(OR, expOne, expTwo);
}
@Override
public SpiExpression copyForPlanKey() {
return new Or(expOne.copyForPlanKey(), expTwo.copyForPlanKey());
}
}
private final SpiExpression expOne;
protected final SpiExpression expOne;
private final SpiExpression expTwo;
protected final SpiExpression expTwo;
private final String joinType;
@@ -49,6 +60,7 @@ abstract class LogicExpression implements SpiExpression {
this.expTwo = (SpiExpression) expTwo;
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
expOne.containsMany(desc, manyWhereJoin);
expTwo.containsMany(desc, manyWhereJoin);
@@ -60,11 +72,13 @@ abstract class LogicExpression implements SpiExpression {
expTwo.validate(validation);
}
@Override
public void addBindValues(SpiExpressionRequest request) {
expOne.addBindValues(request);
expTwo.addBindValues(request);
}
@Override
public void addSql(SpiExpressionRequest request) {
request.append("(");
@@ -83,16 +97,38 @@ abstract class LogicExpression implements SpiExpression {
/**
* Based on the joinType plus the two expressions.
*/
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(LogicExpression.class).add(joinType);
expOne.queryPlanHash(builder);
expTwo.queryPlanHash(builder);
}
@Override
public int queryBindHash() {
int hc = expOne.queryBindHash();
hc = hc * 31 + expTwo.queryBindHash();
return hc;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof LogicExpression)) {
return false;
}
LogicExpression that = (LogicExpression) other;
return this.joinType.equals(that.joinType)
&& this.expOne.isSameByPlan(that.expOne)
&& this.expTwo.isSameByPlan(that.expTwo);
}
@Override
public boolean isSameByBind(SpiExpression other) {
LogicExpression that = (LogicExpression) other;
return this.expOne.isSameByBind(that.expOne)
&& this.expTwo.isSameByBind(that.expTwo);
}
}
@@ -12,4 +12,9 @@ abstract class NonPrepareExpression implements SpiExpression {
public void prepareExpression(BeanQueryRequest<?> request) {
// do nothing
}
@Override
public SpiExpression copyForPlanKey() {
return this;
}
}
@@ -15,6 +15,11 @@ class NoopExpression implements SpiExpression {
protected static final NoopExpression INSTANCE = new NoopExpression();
@Override
public SpiExpression copyForPlanKey() {
return this;
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins) {
// nothing to do
@@ -50,4 +55,14 @@ class NoopExpression implements SpiExpression {
public void addBindValues(SpiExpressionRequest request) {
// nothing to do
}
@Override
public boolean isSameByPlan(SpiExpression other) {
return other instanceof NoopExpression;
}
@Override
public boolean isSameByBind(SpiExpression other) {
return true;
}
}
@@ -13,7 +13,8 @@ final class NotExpression implements SpiExpression {
private static final long serialVersionUID = 5648926732402355781L;
private static final String NOT = "not (";
private static final String NOT_START = "not (";
private static final String NOT_END = ") ";
private final SpiExpression exp;
@@ -21,6 +22,12 @@ final class NotExpression implements SpiExpression {
this.exp = (SpiExpression) exp;
}
@Override
public SpiExpression copyForPlanKey() {
return new NotExpression(exp.copyForPlanKey());
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
exp.containsMany(desc, manyWhereJoin);
}
@@ -30,14 +37,16 @@ final class NotExpression implements SpiExpression {
exp.validate(validation);
}
@Override
public void addBindValues(SpiExpressionRequest request) {
exp.addBindValues(request);
}
@Override
public void addSql(SpiExpressionRequest request) {
request.append(NOT);
request.append(NOT_START);
exp.addSql(request);
request.append(") ");
request.append(NOT_END);
}
@Override
@@ -54,8 +63,23 @@ final class NotExpression implements SpiExpression {
exp.queryPlanHash(builder);
}
@Override
public int queryBindHash() {
return exp.queryBindHash();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof NotExpression)) {
return false;
}
NotExpression that = (NotExpression) other;
return exp.isSameByPlan(that.exp);
}
@Override
public boolean isSameByBind(SpiExpression other) {
NotExpression that = (NotExpression) other;
return exp.isSameByBind(that.exp);
}
}
@@ -1,7 +1,7 @@
package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
@@ -28,7 +28,7 @@ class NullExpression extends AbstractExpression {
@Override
public void addSql(SpiExpressionRequest request) {
String propertyName = getPropertyName();
String propertyName = propName;
String nullExpr = notNull ? " is not null " : " is null ";
@@ -41,6 +41,23 @@ class NullExpression extends AbstractExpression {
request.append(propertyName).append(nullExpr);
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof NullExpression)) {
return false;
}
NullExpression that = (NullExpression) other;
return this.propName.equals(that.propName)
&& this.notNull == that.notNull;
}
@Override
public boolean isSameByBind(SpiExpression other) {
// no bind values so always true
return true;
}
/**
* Based on notNull flag and the propertyName.
*/
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.expression;
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.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
@@ -55,4 +56,31 @@ class RawExpression extends NonPrepareExpression {
public int queryBindHash() {
return sql.hashCode();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof RawExpression)) {
return false;
}
RawExpression that = (RawExpression) other;
return sql.equals(that.sql);
}
@Override
public boolean isSameByBind(SpiExpression other) {
if (!(other instanceof RawExpression)) {
return false;
}
RawExpression that = (RawExpression) other;
if (values.length != that.values.length) {
return false;
}
for (int i = 0; i < values.length; i++) {
if (!Same.sameByValue(values[i], that.values[i])) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,33 @@
package com.avaje.ebeaninternal.server.expression;
/**
* Utility to help isSame methods.
*/
public class Same {
/**
* Return true if both values are null or both an not null.
*/
public static boolean sameByNull(Object v1, Object v2) {
return v1 == null ? v2 == null : v2 != null;
}
/**
* Null safe equals check.
*/
public static boolean sameByValue(Object v1, Object v2) {
return v1 == null ? v2 == null : v1.equals(v2);
}
/**
* Null safe check by sameByValue or sameByNull based on byValue.
*/
public static boolean sameBy(boolean byValue, Object value, Object value1) {
if (byValue) {
return sameByValue(value, value1);
} else {
return sameByNull(value, value1);
}
}
}
@@ -1,8 +1,8 @@
package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
@@ -20,17 +20,25 @@ public class SimpleExpression extends AbstractExpression {
this.value = value;
}
public final String getPropName() {
return propName;
}
public boolean isOpEquals() {
return Op.EQ.equals(type);
}
public Object getValue() {
return value;
}
@Override
public void addBindValues(SpiExpressionRequest request) {
ElPropertyValue prop = getElProp(request);
if (prop != null) {
if (prop.isAssocId()) {
Object[] ids = prop.getAssocOneIdValues((EntityBean)value);
Object[] ids = prop.getAssocOneIdValues((EntityBean) value);
if (ids != null) {
for (int i = 0; i < ids.length; i++) {
request.addBindValue(ids[i]);
@@ -54,12 +62,10 @@ public class SimpleExpression extends AbstractExpression {
@Override
public void addSql(SpiExpressionRequest request) {
String propertyName = getPropertyName();
ElPropertyValue prop = getElProp(request);
if (prop != null) {
if (prop.isAssocId()) {
request.append(prop.getAssocOneIdExpr(propertyName, type.bind()));
request.append(prop.getAssocOneIdExpr(propName, type.bind()));
return;
}
if (prop.isDbEncrypted()) {
@@ -68,7 +74,7 @@ public class SimpleExpression extends AbstractExpression {
return;
}
}
request.append(propertyName).append(type.bind());
request.append(propName).append(type.bind());
}
/**
@@ -85,8 +91,20 @@ public class SimpleExpression extends AbstractExpression {
return value.hashCode();
}
public Object getValue() {
return value;
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof SimpleExpression)) {
return false;
}
SimpleExpression that = (SimpleExpression) other;
return this.propName.equals(that.propName)
&& this.type == that.type;
}
@Override
public boolean isSameByBind(SpiExpression other) {
SimpleExpression that = (SimpleExpression) other;
return value.equals(that.value);
}
}
@@ -1,13 +1,8 @@
package com.avaje.ebeaninternal.server.query;
import java.security.MessageDigest;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.config.dbplatform.SqlLimitResponse;
import com.avaje.ebeaninternal.api.HashQueryPlan;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.CQueryPlanKey;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
@@ -18,6 +13,10 @@ import com.avaje.ebeaninternal.server.type.RsetDataReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.MessageDigest;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Represents a query for a given SQL statement.
* <p>
@@ -45,7 +44,7 @@ public class CQueryPlan {
private final boolean autoTuned;
private final HashQueryPlan hash;
private final CQueryPlanKey planKey;
private final boolean rawSql;
@@ -79,7 +78,7 @@ public class CQueryPlan {
this.server = request.getServer();
this.beanType = request.getBeanDescriptor().getBeanType();
this.stats = new CQueryPlanStats(this, server.isCollectQueryOrigins());
this.hash = request.getQueryPlanHash();
this.planKey = request.getQueryPlanKey();
this.autoTuned = request.getQuery().isAutoTuned();
if (sqlRes != null) {
this.sql = sqlRes.getSql();
@@ -103,7 +102,7 @@ public class CQueryPlan {
this.server = request.getServer();
this.beanType = request.getBeanDescriptor().getBeanType();
this.stats = new CQueryPlanStats(this, server.isCollectQueryOrigins());
this.hash = buildHash(sql, rawSql, rowNumberIncluded, logWhereSql);
this.planKey = buildPlanKey(sql, rawSql, rowNumberIncluded, logWhereSql);
this.autoTuned = false;
this.sql = sql;
this.sqlTree = sqlTree;
@@ -114,15 +113,13 @@ public class CQueryPlan {
}
private HashQueryPlan buildHash(String sql, boolean rawSql, boolean rowNumberIncluded, String logWhereSql) {
HashQueryPlanBuilder builder = new HashQueryPlanBuilder();
builder.add(sql).add(rawSql).add(rowNumberIncluded).add(logWhereSql);
builder.addRawSql(sql);
return builder.build();
private CQueryPlanKey buildPlanKey(String sql, boolean rawSql, boolean rowNumberIncluded, String logWhereSql) {
return new RawSqlQueryPlanKey(sql, rawSql, rowNumberIncluded, logWhereSql);
}
public String toString() {
return beanType + " hash:" + hash;
return beanType + " hash:" + planKey;
}
public Class<?> getBeanType() {
@@ -147,8 +144,8 @@ public class CQueryPlan {
return autoTuned;
}
public HashQueryPlan getHash() {
return hash;
public CQueryPlanKey getPlanKey() {
return planKey;
}
/**
@@ -164,7 +161,7 @@ public class CQueryPlan {
private String calcAuditQueryKey() {
// rawSql needs to include the MD5 hash of the sql
return rawSql ? hash.getPartialKey() + "_" + getSqlMd5Hash() : hash.getPartialKey();
return rawSql ? planKey.getPartialKey() + "_" + getSqlMd5Hash() : planKey.getPartialKey();
}
/**
@@ -233,7 +233,7 @@ public final class CQueryPlanStats {
@Override
public String getQueryPlanHash() {
return queryPlan.getHash().toString();
return queryPlan.getPlanKey().toString();
}
@Override
@@ -13,7 +13,7 @@ import com.avaje.ebeaninternal.server.persist.Binder;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
import com.avaje.ebeaninternal.server.type.DataBind;
import com.avaje.ebeaninternal.server.util.BindParamsParser;
import com.avaje.ebeaninternal.util.DefaultExpressionRequest;
import com.avaje.ebeaninternal.server.expression.DefaultExpressionRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -0,0 +1,56 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebeaninternal.api.CQueryPlanKey;
/**
* QueryPlanKey for RawSql queries.
*/
public class RawSqlQueryPlanKey implements CQueryPlanKey {
private final String sql;
private final boolean rawSql;
private final boolean rowNumberIncluded;
private final String logWhereSql;
public RawSqlQueryPlanKey(String sql, boolean rawSql, boolean rowNumberIncluded, String logWhereSql) {
this.sql = sql;
this.rawSql = rawSql;
this.rowNumberIncluded = rowNumberIncluded;
this.logWhereSql = logWhereSql;
}
public String toString() {
return getPartialKey() + ":r";
}
/**
* Return as a partial key. For rawSql hash the sql is part of the key and as such
* needs to be included in order to have a complete key. Typically the MD5 of the sql
* can be used as a short form proxy for the actual sql.
*/
public String getPartialKey() {
return hashCode() + "_0";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RawSqlQueryPlanKey that = (RawSqlQueryPlanKey) o;
if (rawSql != that.rawSql) return false;
if (rowNumberIncluded != that.rowNumberIncluded) return false;
if (!sql.equals(that.sql)) return false;
return logWhereSql != null ? logWhereSql.equals(that.logWhereSql) : that.logWhereSql == null;
}
@Override
public int hashCode() {
int result = sql.hashCode();
result = 31 * result + (rawSql ? 1 : 0);
result = 31 * result + (rowNumberIncluded ? 1 : 0);
result = 31 * result + logWhereSql.hashCode();
return result;
}
}
@@ -13,9 +13,8 @@ import com.avaje.ebean.plugin.SpiBeanType;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebeaninternal.api.BindParams;
import com.avaje.ebeaninternal.api.HashQuery;
import com.avaje.ebeaninternal.api.HashQueryPlan;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.CQueryPlanKey;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionList;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
@@ -28,7 +27,7 @@ import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery;
import com.avaje.ebeaninternal.server.deploy.TableJoin;
import com.avaje.ebeaninternal.server.expression.SimpleExpression;
import com.avaje.ebeaninternal.server.query.CancelableQuery;
import com.avaje.ebeaninternal.util.DefaultExpressionList;
import com.avaje.ebeaninternal.server.expression.DefaultExpressionList;
import javax.persistence.PersistenceException;
import java.sql.Timestamp;
@@ -234,7 +233,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
/**
* Hash of final query after AutoTune tuning.
*/
private HashQueryPlan queryPlanHash;
private CQueryPlanKey queryPlanKey;
private transient PersistenceContext persistenceContext;
@@ -493,7 +492,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
if (se instanceof SimpleExpression) {
SimpleExpression e = (SimpleExpression) se;
if (e.isOpEquals()) {
return new NaturalKeyBindParam(e.getPropertyName(), e.getValue());
return new NaturalKeyBindParam(e.getPropName(), e.getValue());
}
}
}
@@ -759,55 +758,34 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
/**
* Calculate the query hash for either AutoTune query tuning or Query Plan caching.
*/
HashQueryPlan calculateQueryPlanHash() {
// exclude bind values and things unrelated to the sql being generated
HashQueryPlanBuilder builder = new HashQueryPlanBuilder();
builder.add((type == null ? 0 : type.ordinal() + 1));
builder.add(autoTuned).add(distinct).add(sqlDistinct).add(query);
builder.add(firstRow).add(maxRows).add(orderBy).add(forUpdate);
builder.add(rawWhereClause).add(additionalWhere).add(additionalHaving);
builder.add(mapKey);
builder.add(disableLazyLoading);
builder.add(id != null);
builder.add(temporalMode);
builder.add(rawSql == null ? 0 : rawSql.queryHash());
builder.add(includeTableJoin != null ? includeTableJoin.queryHash() : 0);
builder.add(rootTableAlias);
if (detail != null) {
detail.queryPlanHash(builder);
}
if (bindParams != null) {
bindParams.buildQueryPlanHash(builder);
}
CQueryPlanKey createQueryPlanKey() {
DefaultExpressionList<T> where = null;
DefaultExpressionList<T> having = null;
if (whereExpressions != null) {
whereExpressions.queryPlanHash(builder);
where = whereExpressions.copyForPlanKey();
}
if (havingExpressions != null) {
havingExpressions.queryPlanHash(builder);
having = havingExpressions.copyForPlanKey();
}
return builder.build();
queryPlanKey = new OrmQueryPlanKey(includeTableJoin, type, detail, maxRows, firstRow,
disableLazyLoading, rawWhereClause, orderBy, query, additionalWhere, additionalHaving,
distinct, sqlDistinct, mapKey, id, bindParams, where, having,
temporalMode, forUpdate, rootTableAlias, rawSql);
return queryPlanKey;
}
/**
* Calculate a hash that should be unique for the generated SQL across a given bean type.
* <p>
* This can used to enable the caching and reuse of a 'query plan'.
* </p>
* <p>
* This is calculated AFTER AutoTune query tuning has occurred.
* </p>
* Prepare the query which prepares any expressions (sub-query expressions etc) and calculates the query plan key.
*/
public HashQueryPlan queryPlanHash(BeanQueryRequest<?> request) {
public CQueryPlanKey prepare(BeanQueryRequest<?> request) {
prepareExpressions(request);
queryPlanHash = calculateQueryPlanHash();
return queryPlanHash;
queryPlanKey = createQueryPlanKey();
return queryPlanKey;
}
/**
@@ -852,7 +830,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
// so queryPlanHash is calculated well before this method is called
int hc = queryBindHash();
return new HashQuery(queryPlanHash, hc);
return new HashQuery(queryPlanKey, hc);
}
/**
@@ -0,0 +1,149 @@
package com.avaje.ebeaninternal.server.querydefn;
import com.avaje.ebean.OrderBy;
import com.avaje.ebean.RawSql;
import com.avaje.ebeaninternal.api.BindParams;
import com.avaje.ebeaninternal.api.CQueryPlanKey;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.TableJoin;
/**
* Query plan key for ORM queries.
*/
public class OrmQueryPlanKey implements CQueryPlanKey {
private final TableJoin includeTableJoin;
private final String orderByAsSting;
private final OrmQueryDetail detail;
private final BindParams bindParams;
private final SpiExpression whereExpressions;
private final SpiExpression havingExpressions;
private final RawSql rawSql;
private final boolean hasIdValue;
private final SpiQuery.Type type;
private final int maxRows;
private final int firstRow;
private final boolean disableLazyLoading;
private final String rawWhereClause;
private final String query;
private final String additionalWhere;
private final String additionalHaving;
private final boolean distinct;
private final boolean sqlDistinct;
private final String mapKey;
private final SpiQuery.TemporalMode temporalMode;
private final boolean forUpdate;
private final String rootTableAlias;
private final int planHash;
private final int bindCount;
public OrmQueryPlanKey(TableJoin includeTableJoin, SpiQuery.Type type, OrmQueryDetail detail, int maxRows, int firstRow, boolean disableLazyLoading, String rawWhereClause, OrderBy<?> orderBy, String query, String additionalWhere, String additionalHaving, boolean distinct, boolean sqlDistinct, String mapKey, Object id, BindParams bindParams, SpiExpression whereExpressions, SpiExpression havingExpressions, SpiQuery.TemporalMode temporalMode, boolean forUpdate, String rootTableAlias, RawSql rawSql) {
this.includeTableJoin = includeTableJoin;
this.type = type;
this.detail = detail;
this.maxRows = maxRows;
this.firstRow = firstRow;
this.disableLazyLoading = disableLazyLoading;
this.rawWhereClause = rawWhereClause;
this.orderByAsSting = (orderBy == null) ? null : orderBy.toStringFormat();
this.query = query;
this.additionalWhere = additionalWhere;
this.additionalHaving = additionalHaving;
this.distinct = distinct;
this.sqlDistinct = sqlDistinct;
this.mapKey = mapKey;
this.hasIdValue = (id != null);
this.bindParams = bindParams;
this.whereExpressions = whereExpressions;
this.havingExpressions = havingExpressions;
this.temporalMode = temporalMode;
this.forUpdate = forUpdate;
this.rootTableAlias = rootTableAlias;
this.rawSql = rawSql;
// exclude bind values and things unrelated to the sql being generated
HashQueryPlanBuilder builder = new HashQueryPlanBuilder();
builder.add((type == null ? 0 : type.ordinal() + 1));
builder.add(distinct).add(sqlDistinct).add(query);
builder.add(firstRow).add(maxRows);
builder.add(orderBy).add(forUpdate);
builder.add(rawWhereClause).add(additionalWhere).add(additionalHaving);
builder.add(mapKey);
builder.add(disableLazyLoading);
builder.add(hasIdValue);
builder.add(temporalMode);
builder.add(rawSql == null ? 0 : rawSql.queryHash());
builder.add(includeTableJoin != null ? includeTableJoin.queryHash() : 0);
builder.add(rootTableAlias);
if (detail != null) {
detail.queryPlanHash(builder);
}
if (bindParams != null) {
bindParams.buildQueryPlanHash(builder);
}
if (whereExpressions != null) {
whereExpressions.queryPlanHash(builder);
}
if (havingExpressions != null) {
havingExpressions.queryPlanHash(builder);
}
this.planHash = builder.getPlanHash();
this.bindCount = builder.getBindCount();
}
@Override
public String getPartialKey() {
return planHash + "_" + bindCount;
}
@Override
public int hashCode() {
return planHash;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
OrmQueryPlanKey that = (OrmQueryPlanKey) o;
if (planHash != that.planHash) return false;
if (bindCount != that.bindCount) return false;
if (maxRows != that.maxRows) return false;
if (firstRow != that.firstRow) return false;
if (disableLazyLoading != that.disableLazyLoading) return false;
if (distinct != that.distinct) return false;
if (sqlDistinct != that.sqlDistinct) return false;
if (forUpdate != that.forUpdate) return false;
if (hasIdValue != that.hasIdValue) return false;
if (type != that.type) return false;
if (temporalMode != that.temporalMode) return false;
if (includeTableJoin != null ? !includeTableJoin.equals(that.includeTableJoin) : that.includeTableJoin != null) return false;
if (orderByAsSting != null ? !orderByAsSting.equals(that.orderByAsSting) : that.orderByAsSting != null) return false;
if (whereExpressions != null ? !whereExpressions.isSameByPlan(that.whereExpressions) : that.whereExpressions != null) return false;
if (havingExpressions != null ? !havingExpressions.isSameByPlan(that.havingExpressions) : that.havingExpressions != null) return false;
// if (detail != null ? !detail.equals(that.detail) : that.detail != null) return false;
// if (bindParams != null ? !bindParams.equals(that.bindParams) : that.bindParams != null) return false;
// if (rawSql != null ? !rawSql.equals(that.rawSql) : that.rawSql != null) return false;
if (rawWhereClause != null ? !rawWhereClause.equals(that.rawWhereClause) : that.rawWhereClause != null) return false;
if (query != null ? !query.equals(that.query) : that.query != null) return false;
if (additionalWhere != null ? !additionalWhere.equals(that.additionalWhere) : that.additionalWhere != null) return false;
if (additionalHaving != null ? !additionalHaving.equals(that.additionalHaving) : that.additionalHaving != null) return false;
if (mapKey != null ? !mapKey.equals(that.mapKey) : that.mapKey != null) return false;
return rootTableAlias != null ? rootTableAlias.equals(that.rootTableAlias) : that.rootTableAlias == null;
}
}
@@ -12,7 +12,7 @@ import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.expression.FilterExprPath;
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
import com.avaje.ebeaninternal.server.query.SplitName;
import com.avaje.ebeaninternal.util.FilterExpressionList;
import com.avaje.ebeaninternal.server.expression.FilterExpressionList;
import java.io.Serializable;
import java.util.ArrayList;