Refactor of query plan hash - add HashQueryPlan, HashQuery etc

This commit is contained in:
Rob Bygrave
2014-01-21 00:48:38 +13:00
parent 6bd8c2bd05
commit f287205421
36 changed files with 499 additions and 300 deletions
+25 -22
View File
@@ -15,8 +15,6 @@ import java.util.List;
* Typically you will not construct an OrderBy yourself but use one that exists
* on the Query object.
* </p>
*
* @author rbygrave
*/
public final class OrderBy<T> implements Serializable {
@@ -24,7 +22,7 @@ public final class OrderBy<T> implements Serializable {
private transient Query<T> query;
private List<Property> list;
private final List<Property> list;
/**
* Create an empty OrderBy with no associated query.
@@ -168,31 +166,23 @@ public final class OrderBy<T> implements Serializable {
@Override
public boolean equals(Object obj) {
if (obj instanceof OrderBy<?>) {
if (obj == this) {
return true;
}
OrderBy<?> other = (OrderBy<?>) obj;
return hashCode() == other.hashCode();
if (obj == this) {
return true;
}
return false;
}
@Override
public int hashCode() {
return hash();
if (!(obj instanceof OrderBy<?>)) {
return false;
}
OrderBy<?> e = (OrderBy<?>) obj;
return e.list.equals(list);
}
/**
* Return a hash value for this OrderBy. This can be to determine logical
* equality for OrderBy clauses.
*/
public int hash() {
int hc = OrderBy.class.getName().hashCode();
for (int i = 0; i < list.size(); i++) {
hc = hc * 31 + list.get(i).hash();
}
return hc;
public int hashCode() {
return list.hashCode();
}
/**
@@ -218,11 +208,24 @@ public final class OrderBy<T> implements Serializable {
return new Property(property.substring(path.length() + 1), ascending);
}
protected int hash() {
public int hashCode() {
int hc = property.hashCode();
hc = hc * 31 + (ascending ? 0 : 1);
return hc;
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Property)) {
return false;
}
Property e = (Property) obj;
return e.ascending == ascending
&& e.property.equals(property);
}
public String toString() {
return toStringFormat();
@@ -1,7 +1,5 @@
package com.avaje.ebean.meta;
/**
* Query execution statistics Meta data.
*/
@@ -18,9 +16,9 @@ public interface MetaBeanQueryPlanStatistic {
public boolean isAutofetchTuned();
/**
* Return the query plan hash.
* Return a string representation of the query plan hash.
*/
public int getQueryPlanHash();
public String getQueryPlanHash();
/**
* Return the sql executed.
@@ -50,7 +50,7 @@ public class BindParams implements Serializable {
* taken into account when calculating the query hash.
* </p>
*/
public int getQueryPlanHash() {
public void buildQueryPlanHash(HashQueryPlanBuilder builder) {
int hc = 31;
for (Param param : positionedParameters) {
hc = hc * 31 + param.queryBindCount();
@@ -61,7 +61,8 @@ public class BindParams implements Serializable {
hc = hc * 31 + entry.getValue().queryBindCount();
}
return hc;
int bindCount = positionedParameters.size() + namedParameters.size();
builder.add(hc).bind(bindCount);
}
/**
@@ -0,0 +1,51 @@
package com.avaje.ebeaninternal.api;
/**
* A hash key for a query including both the query plan and bind values.
*/
public class HashQuery {
private final HashQueryPlan planHash;
private final int bindHash;
/**
* Create the HashQuery.
*/
public HashQuery(HashQueryPlan 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;
return hc;
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof HashQuery)) {
return false;
}
HashQuery e = (HashQuery) obj;
return e.bindHash == bindHash && e.planHash.equals(planHash);
}
}
@@ -0,0 +1,46 @@
package com.avaje.ebeaninternal.api;
import java.util.Objects;
/**
* A hash for a query plan.
*/
public class HashQueryPlan {
private final String rawSql;
private final int planHash;
private final int bindCount;
public HashQueryPlan(String rawSql, int planHash, int bindCount) {
this.rawSql = rawSql;
this.planHash = planHash;
this.bindCount = bindCount;
}
public String toString() {
return planHash+":"+bindCount+(rawSql != null ? ":r" : "");
}
public int hashCode() {
int hc = planHash;
hc = hc * 31 + bindCount;
hc = hc * 31 + Objects.hashCode(rawSql);
return hc;
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof HashQueryPlan)) {
return false;
}
HashQueryPlan e = (HashQueryPlan) obj;
return e.planHash == planHash
&& e.bindCount == bindCount
&& Objects.equals(e.rawSql, rawSql);
}
}
@@ -0,0 +1,80 @@
package com.avaje.ebeaninternal.api;
import java.util.Objects;
/**
* Used to build HashQueryPlan instances.
*/
public class HashQueryPlanBuilder {
private int planHash;
private int bindCount;
private String rawSql;
public HashQueryPlanBuilder() {
this.planHash = 31;
}
public String toString() {
return planHash+":"+bindCount+(rawSql != null ? ":r" : "");
}
/**
* Add a class to the hash calculation.
*/
public HashQueryPlanBuilder add(Class<?> cls) {
planHash = planHash * 31 + cls.getName().hashCode();
return this;
}
/**
* Add an object to the hash calculation.
*/
public HashQueryPlanBuilder add(Object object) {
planHash = planHash * 31 + Objects.hashCode(object);
return this;
}
/**
* Add an integer to the hash calculation.
*/
public HashQueryPlanBuilder add(int hashValue) {
planHash = planHash * 31 + (hashValue);
return this;
}
/**
* Add a boolean to the hash calculation.
*/
public HashQueryPlanBuilder add(boolean booleanValue) {
planHash = planHash * 31 + (booleanValue ? 31 : 0);
return this;
}
/**
* Add a number to the bind count for the hash.
*/
public HashQueryPlanBuilder bind(int extraBindCount) {
bindCount += extraBindCount;
return this;
}
/**
* Add raw sql to the hash.
*/
public HashQueryPlanBuilder addRawSql(String rawSql) {
this.rawSql = rawSql;
return this;
}
/**
* Build and return the calculated HashQueryPlan.
*/
public HashQueryPlan build() {
return new HashQueryPlan(rawSql, planHash, bindCount);
}
}
@@ -26,7 +26,7 @@ public interface SpiExpression extends Expression {
* from an AutoFetch perspective and get different tuning.
* </p>
*/
public int queryAutoFetchHash();
public void queryAutoFetchHash(HashQueryPlanBuilder builder);
/**
* Calculate a hash value for the expression.
@@ -37,7 +37,7 @@ public interface SpiExpression extends Expression {
* case the query execution plan can be reused.
* </p>
*/
public int queryPlanHash(BeanQueryRequest<?> request);
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder);
/**
* Return the hash value for the values that will be bound.
@@ -28,14 +28,13 @@ public interface SpiExpressionList<T> extends ExpressionList<T> {
*/
public void setExpressionFactory(ExpressionFactory expr);
/**
* Process "Many" properties populating ManyWhereJoins.
* <p>
* Predicates on Many properties require an extra independent
* join clause.
* </p>
*/
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoins);
/**
* Process "Many" properties populating ManyWhereJoins.
* <p>
* Predicates on Many properties require an extra independent join clause.
* </p>
*/
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoins);
/**
* Return true if this list is empty.
@@ -62,10 +61,10 @@ public interface SpiExpressionList<T> extends ExpressionList<T> {
*/
public ArrayList<Object> buildBindValues(SpiExpressionRequest request);
/**
* Calculate a hash based on the expressions but excluding the actual bind
* values.
*/
public int queryPlanHash(BeanQueryRequest<?> request);
/**
* Calculate a hash based on the expressions but excluding the actual bind
* values.
*/
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder);
}
@@ -341,7 +341,7 @@ public interface SpiQuery<T> extends Query<T> {
* tuning/modifying the query.
* </p>
*/
public int queryAutofetchHash();
public HashQueryPlan queryAutofetchHash(HashQueryPlanBuilder builder);
/**
* Identifies queries that are the same bar the bind variables.
@@ -354,7 +354,7 @@ public interface SpiQuery<T> extends Query<T> {
* Excludes the actual bind values (as they don't effect the query plan).
* </p>
*/
public int queryPlanHash(BeanQueryRequest<?> request);
public HashQueryPlan queryPlanHash(BeanQueryRequest<?> request);
/**
* Calculate a hash based on the bind values used in the query.
@@ -368,7 +368,7 @@ public interface SpiQuery<T> extends Query<T> {
/**
* Identifies queries that are exactly the same including bind variables.
*/
public int queryHash();
public HashQuery queryHash();
/**
* Return true if this is a query based on a SqlSelect rather than
@@ -14,6 +14,8 @@ import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.event.BeanFinder;
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.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
@@ -49,9 +51,9 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
private PersistenceContext persistenceContext;
private Integer cacheKey;
private HashQuery cacheKey;
private int queryPlanHash;
private HashQueryPlan queryPlanHash;
/**
* Flag set if background fetching taking place. In this case the transaction
@@ -332,7 +334,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
* with just the bind variables changing.
* </p>
*/
public int getQueryPlanHash() {
public HashQueryPlan getQueryPlanHash() {
return queryPlanHash;
}
@@ -356,14 +358,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
return null;
}
if (query.getType() == null) {
// the query plan and bind values must be the same
cacheKey = Integer.valueOf(query.queryHash());
} else {
// additionally the return type (List/Set/Map) must be the same
cacheKey = Integer.valueOf(31 * query.queryHash() + query.getType().hashCode());
}
cacheKey = query.queryHash();
// TODO: Sort out returning BeanCollection from L2 cache
return null;
@@ -40,6 +40,7 @@ import com.avaje.ebean.meta.MetaBeanInfo;
import com.avaje.ebean.meta.MetaBeanQueryPlanStatistic;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.JsonWriteBeanVisitor;
import com.avaje.ebeaninternal.api.HashQueryPlan;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiUpdatePlan;
@@ -87,7 +88,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
private final ConcurrentHashMap<Integer, SpiUpdatePlan> updatePlanCache = new ConcurrentHashMap<Integer, SpiUpdatePlan>();
private final ConcurrentHashMap<Integer, CQueryPlan> queryPlanCache = new ConcurrentHashMap<Integer, CQueryPlan>();
private final ConcurrentHashMap<HashQueryPlan, CQueryPlan> queryPlanCache = new ConcurrentHashMap<HashQueryPlan, CQueryPlan>();
private final ConcurrentHashMap<String, ElPropertyValue> elGetCache = new ConcurrentHashMap<String, ElPropertyValue>();
@@ -1202,11 +1203,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
return queryPlanCache.values().iterator();
}
public CQueryPlan getQueryPlan(Integer key) {
public CQueryPlan getQueryPlan(HashQueryPlan key) {
return queryPlanCache.get(key);
}
public void putQueryPlan(Integer key, CQueryPlan plan) {
public void putQueryPlan(HashQueryPlan key, CQueryPlan plan) {
queryPlanCache.put(key, plan);
}
@@ -3,9 +3,11 @@ package com.avaje.ebeaninternal.server.expression;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
@@ -105,29 +107,29 @@ class AllEqualsExpression implements SpiExpression {
* The null check is required due to the "is null" sql being generated.
* </p>
*/
public int queryAutoFetchHash() {
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
int hc = AllEqualsExpression.class.getName().hashCode();
Set<Entry<String, Object>> entries = propMap.entrySet();
Iterator<Entry<String, Object>> it = entries.iterator();
builder.add(AllEqualsExpression.class);
while (it.hasNext()) {
Map.Entry<java.lang.String, java.lang.Object> entry = it.next();
for (Entry<String, Object> entry : propMap.entrySet()) {
Object value = entry.getValue();
String propName = entry.getKey();
builder.add(propName).add(value == null ? 0 : 1);
builder.bind(value == null ? 0 : 1);
}
}
hc = hc * 31 + propName.hashCode();
hc = hc * 31 + (value == null ? 0 : 1);
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
queryAutoFetchHash(builder);
}
public int queryBindHash() {
int hc = 31;
for (Object value : propMap.values()) {
hc = hc * 31 + Objects.hashCode(value);
}
return hc;
}
public int queryPlanHash(BeanQueryRequest<?> request) {
return queryAutoFetchHash();
}
public int queryBindHash() {
return queryAutoFetchHash();
}
}
@@ -1,6 +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.SpiExpressionRequest;
@@ -30,14 +31,13 @@ class BetweenExpression extends AbstractExpression {
request.append(getPropertyName()).append(BETWEEN).append(" ? and ? ");
}
public int queryAutoFetchHash() {
int hc = BetweenExpression.class.getName().hashCode();
hc = hc * 31 + propName.hashCode();
return hc;
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
builder.add(BetweenExpression.class).add(propName);
builder.bind(2);
}
public int queryPlanHash(BeanQueryRequest<?> request) {
return queryAutoFetchHash();
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
queryAutoFetchHash(builder);
}
public int queryBindHash() {
@@ -1,6 +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.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
@@ -65,15 +66,13 @@ class BetweenPropertyExpression implements SpiExpression {
request.append(" ? ").append(BETWEEN).append(name(lowProperty)).append(" and ").append(name(highProperty));
}
public int queryAutoFetchHash() {
int hc = BetweenPropertyExpression.class.getName().hashCode();
hc = hc * 31 + lowProperty.hashCode();
hc = hc * 31 + highProperty.hashCode();
return hc;
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
builder.add(BetweenPropertyExpression.class).add(lowProperty).add(highProperty);
builder.bind(1);
}
public int queryPlanHash(BeanQueryRequest<?> request) {
return queryAutoFetchHash();
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
queryAutoFetchHash(builder);
}
public int queryBindHash() {
@@ -1,10 +1,11 @@
package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
class CaseInsensitiveEqualExpression extends AbstractExpression implements LuceneAwareExpression {
class CaseInsensitiveEqualExpression extends AbstractExpression {
private static final long serialVersionUID = -6406036750998971064L;
@@ -40,14 +41,13 @@ class CaseInsensitiveEqualExpression extends AbstractExpression implements Lucen
request.append("lower(").append(pname).append(") =? ");
}
public int queryAutoFetchHash() {
int hc = CaseInsensitiveEqualExpression.class.getName().hashCode();
hc = hc * 31 + propName.hashCode();
return hc;
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
builder.add(CaseInsensitiveEqualExpression.class).add(propName);
builder.bind(1);
}
public int queryPlanHash(BeanQueryRequest<?> request) {
return queryAutoFetchHash();
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
queryAutoFetchHash(builder);
}
public int queryBindHash() {
@@ -6,6 +6,7 @@ import java.util.Iterator;
import com.avaje.ebean.ExampleExpression;
import com.avaje.ebean.LikeType;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
@@ -163,28 +164,26 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio
/**
* Return a hash for autoFetch query identification.
*/
public int queryAutoFetchHash() {
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
// we have not yet built the list of expressions
// so just based on the class name
return DefaultExampleExpression.class.getName().hashCode();
builder.add(DefaultExampleExpression.class);
}
/**
* Return a hash for query plan identification.
*/
public int queryPlanHash(BeanQueryRequest<?> request) {
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
// this is always called once, and always called before
// addSql() and addBindValues() methods
list = buildExpressions(request);
int hc = DefaultExampleExpression.class.getName().hashCode();
builder.add(DefaultExampleExpression.class);
for (int i = 0; i < list.size(); i++) {
hc = hc * 31 + list.get(i).queryPlanHash(request);
list.get(i).queryPlanHash(request, builder);
}
return hc;
}
/**
@@ -1,6 +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.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
@@ -49,14 +50,13 @@ class IdExpression implements SpiExpression {
/**
* No properties so this is just a unique static number.
*/
public int queryAutoFetchHash() {
// this number is unique for a given bean type
// which is all that is required
return IdExpression.class.getName().hashCode();
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
builder.add(IdExpression.class);
builder.bind(1);
}
public int queryPlanHash(BeanQueryRequest<?> request) {
return queryAutoFetchHash();
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
queryAutoFetchHash(builder);
}
public int queryBindHash() {
@@ -3,6 +3,7 @@ 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.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
@@ -67,16 +68,13 @@ public class IdInExpression implements SpiExpression {
/**
* Incorporates the number of Id values to bind.
*/
public int queryAutoFetchHash() {
// this number is unique for a given bean type
// which is all that is required
int hc = IdInExpression.class.getName().hashCode();
hc = hc * 31 + idList.size();
return hc;
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
builder.add(IdInExpression.class).add(idList.size());
builder.bind(idList.size());
}
public int queryPlanHash(BeanQueryRequest<?> request) {
return queryAutoFetchHash();
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
queryAutoFetchHash(builder);
}
public int queryBindHash() {
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.expression;
import java.util.Collection;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
@@ -79,19 +80,18 @@ class InExpression extends AbstractExpression {
/**
* Based on the number of values in the in clause.
*/
public int queryAutoFetchHash() {
int hc = InExpression.class.getName().hashCode() + 31 * values.length;
hc = hc * 31 + propName.hashCode();
return hc;
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
builder.add(InExpression.class).add(propName).add(values.length);
builder.bind(values.length);
}
public int queryPlanHash(BeanQueryRequest<?> request) {
return queryAutoFetchHash();
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
queryAutoFetchHash(builder);
}
public int queryBindHash() {
int hc = 0;
for (int i = 1; i < values.length; i++) {
int hc = 31;
for (int i = 0; i < values.length; i++) {
hc = 31 * hc + values[i].hashCode();
}
return hc;
@@ -3,6 +3,7 @@ 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.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiQuery;
@@ -26,23 +27,19 @@ class InQueryExpression extends AbstractExpression {
this.subQuery = subQuery;
}
public int queryAutoFetchHash() {
int hc = InQueryExpression.class.getName().hashCode();
hc = hc * 31 + propName.hashCode();
hc = hc * 31 + subQuery.queryAutofetchHash();
return hc;
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
builder.add(InQueryExpression.class).add(propName);
subQuery.queryAutofetchHash(builder);
}
public int queryPlanHash(BeanQueryRequest<?> request) {
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
// queryPlanHash executes prior to addSql() or addBindValues()
// ... so compiledQuery will exist
compiledSubQuery = compileSubQuery(request);
int hc = InQueryExpression.class.getName().hashCode();
hc = hc * 31 + propName.hashCode();
hc = hc * 31 + subQuery.queryPlanHash(request);
return hc;
queryAutoFetchHash(builder);
}
/**
@@ -17,6 +17,7 @@ import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.QueryListener;
import com.avaje.ebean.QueryResultVisitor;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
@@ -115,28 +116,20 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
/**
* Based on Junction type and all the expression contained.
*/
public int queryAutoFetchHash() {
int hc = JunctionExpression.class.getName().hashCode();
hc = hc * 31 + joinType.hashCode();
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
builder.add(JunctionExpression.class).add(joinType);
List<SpiExpression> list = exprList.internalList();
for (int i = 0; i < list.size(); i++) {
hc = hc * 31 + list.get(i).queryAutoFetchHash();
list.get(i).queryAutoFetchHash(builder);
}
return hc;
}
public int queryPlanHash(BeanQueryRequest<?> request) {
int hc = JunctionExpression.class.getName().hashCode();
hc = hc * 31 + joinType.hashCode();
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
builder.add(JunctionExpression.class).add(joinType);
List<SpiExpression> list = exprList.internalList();
for (int i = 0; i < list.size(); i++) {
hc = hc * 31 + list.get(i).queryPlanHash(request);
list.get(i).queryPlanHash(request, builder);
}
return hc;
}
public int queryBindHash() {
@@ -2,10 +2,11 @@ package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.LikeType;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
class LikeExpression extends AbstractExpression implements LuceneAwareExpression {
class LikeExpression extends AbstractExpression {
private static final long serialVersionUID = -5398151809111172380L;
@@ -59,15 +60,13 @@ class LikeExpression extends AbstractExpression implements LuceneAwareExpression
/**
* Based on caseInsensitive and the property name.
*/
public int queryAutoFetchHash() {
int hc = LikeExpression.class.getName().hashCode();
hc = hc * 31 + (caseInsensitive ? 0 : 1);
hc = hc * 31 + propName.hashCode();
return hc;
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
builder.add(LikeExpression.class).add(caseInsensitive).add(propName);
builder.bind(1);
}
public int queryPlanHash(BeanQueryRequest<?> request) {
return queryAutoFetchHash();
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
queryAutoFetchHash(builder);
}
public int queryBindHash() {
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.Expression;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
@@ -69,18 +70,16 @@ abstract class LogicExpression implements SpiExpression {
/**
* Based on the joinType plus the two expressions.
*/
public int queryAutoFetchHash() {
int hc = LogicExpression.class.getName().hashCode() + joinType.hashCode();
hc = hc * 31 + expOne.queryAutoFetchHash();
hc = hc * 31 + expTwo.queryAutoFetchHash();
return hc;
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
builder.add(LogicExpression.class).add(joinType);
expOne.queryAutoFetchHash(builder);
expTwo.queryAutoFetchHash(builder);
}
public int queryPlanHash(BeanQueryRequest<?> request) {
int hc = LogicExpression.class.getName().hashCode() + joinType.hashCode();
hc = hc * 31 + expOne.queryPlanHash(request);
hc = hc * 31 + expTwo.queryPlanHash(request);
return hc;
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
builder.add(LogicExpression.class).add(joinType);
expOne.queryPlanHash(request, builder);
expTwo.queryPlanHash(request, builder);
}
public int queryBindHash() {
@@ -1,10 +0,0 @@
package com.avaje.ebeaninternal.server.expression;
/**
* Marker interface for lucene aware expressions.
*
* @author rbygrave
*/
public interface LuceneAwareExpression {
}
@@ -2,12 +2,13 @@ package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.Expression;
import com.avaje.ebean.event.BeanQueryRequest;
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.server.deploy.BeanDescriptor;
final class NotExpression implements SpiExpression, LuceneAwareExpression {
final class NotExpression implements SpiExpression {
private static final long serialVersionUID = 5648926732402355781L;
@@ -36,16 +37,13 @@ final class NotExpression implements SpiExpression, LuceneAwareExpression {
/**
* Based on the expression.
*/
public int queryAutoFetchHash() {
int hc = NotExpression.class.getName().hashCode();
hc = hc * 31 + exp.queryAutoFetchHash();
return hc;
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
builder.add(NotExpression.class);
exp.queryAutoFetchHash(builder);
}
public int queryPlanHash(BeanQueryRequest<?> request) {
int hc = NotExpression.class.getName().hashCode();
hc = hc * 31 + exp.queryPlanHash(request);
return hc;
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
queryAutoFetchHash(builder);
}
public int queryBindHash() {
@@ -1,6 +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.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
@@ -41,15 +42,12 @@ class NullExpression extends AbstractExpression {
/**
* Based on notNull flag and the propertyName.
*/
public int queryAutoFetchHash() {
int hc = NullExpression.class.getName().hashCode();
hc = hc * 31 + (notNull ? 1 : 0);
hc = hc * 31 + propName.hashCode();
return hc;
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
builder.add(NullExpression.class).add(notNull).add(propName);
}
public int queryPlanHash(BeanQueryRequest<?> request) {
return queryAutoFetchHash();
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
queryAutoFetchHash(builder);
}
public int queryBindHash() {
@@ -1,16 +0,0 @@
package com.avaje.ebeaninternal.server.expression;
import javax.persistence.PersistenceException;
/**
* Exception used to wrap Lucene parsing exceptions.
*/
public class PersistenceLuceneParseException extends PersistenceException {
private static final long serialVersionUID = 838790249273928392L;
public PersistenceLuceneParseException(Throwable e){
super(e);
}
}
@@ -1,6 +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.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
@@ -38,14 +39,12 @@ class RawExpression implements SpiExpression {
/**
* Based on the sql.
*/
public int queryAutoFetchHash() {
int hc = RawExpression.class.getName().hashCode();
hc = hc * 31 + sql.hashCode();
return hc;
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
builder.add(RawExpression.class).add(sql);
}
public int queryPlanHash(BeanQueryRequest<?> request) {
return queryAutoFetchHash();
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
queryAutoFetchHash(builder);
}
public int queryBindHash() {
@@ -1,10 +1,11 @@
package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
public class SimpleExpression extends AbstractExpression implements LuceneAwareExpression {
public class SimpleExpression extends AbstractExpression {
private static final long serialVersionUID = -382881395755603790L;
@@ -91,15 +92,13 @@ public class SimpleExpression extends AbstractExpression implements LuceneAwareE
/**
* Based on the type and propertyName.
*/
public int queryAutoFetchHash() {
int hc = SimpleExpression.class.getName().hashCode();
hc = hc * 31 + propName.hashCode();
hc = hc * 31 + type.name().hashCode();
return hc;
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
builder.add(SimpleExpression.class).add(propName).add(type.name());
builder.bind(1);
}
public int queryPlanHash(BeanQueryRequest<?> request) {
return queryAutoFetchHash();
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
queryAutoFetchHash(builder);
}
public int queryBindHash() {
@@ -4,6 +4,8 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import com.avaje.ebean.config.dbplatform.SqlLimitResponse;
import com.avaje.ebeaninternal.api.HashQueryPlan;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
@@ -34,7 +36,7 @@ public class CQueryPlan {
private final boolean autofetchTuned;
private final int hash;
private final HashQueryPlan hash;
private final boolean rawSql;
@@ -86,7 +88,7 @@ public class CQueryPlan {
this.beanType = beanType;
this.stats = new CQueryPlanStats(this);
this.hash = 0;
this.hash = buildHash(sql, rawSql, rowNumberIncluded, logWhereSql);
this.autofetchTuned = false;
this.sql = sql;
this.sqlTree = sqlTree;
@@ -95,6 +97,13 @@ public class CQueryPlan {
this.logWhereSql = logWhereSql;
this.encryptedProps = sqlTree.getEncryptedProps();
}
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();
}
public String toString() {
return beanType+" hash:"+hash;
@@ -122,7 +131,7 @@ public class CQueryPlan {
return autofetchTuned;
}
public int getHash() {
public HashQueryPlan getHash() {
return hash;
}
@@ -131,8 +131,8 @@ public final class CQueryPlanStats {
@Override
public int getQueryPlanHash() {
return queryPlan.getHash();
public String getQueryPlanHash() {
return queryPlan.getHash().toString();
}
@Override
@@ -33,6 +33,9 @@ import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.meta.MetaAutoFetchStatistic;
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.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionList;
@@ -207,7 +210,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
/**
* Hash of final query after AutoFetch tuning.
*/
private int queryPlanHash;
private HashQueryPlan queryPlanHash;
private transient PersistenceContext persistenceContext;
@@ -627,62 +630,61 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
/**
* Calculate the query hash for either AutoFetch query tuning or Query Plan caching.
*/
private int calculateHash(BeanQueryRequest<?> request) {
private HashQueryPlan calculateHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
// exclude bind values and things unrelated to
// the sql being generated
if (builder == null) {
builder = new HashQueryPlanBuilder();
}
// must use String name of class as actual class hashCode
// can change between JVM restarts.
int hc = beanType.getName().hashCode();
builder.add((type == null ? 0 : type.ordinal()+1));
builder.add(useIndex).add(autoFetchTuned).add(distinct).add(query);
builder.add(firstRow).add(maxRows).add(orderBy).add(forUpdate);
builder.add(rawWhereClause).add(additionalWhere).add(additionalHaving);
builder.add(mapKey);
builder.add(id != null);
builder.add(rawSql == null ? 0 : rawSql.queryHash());
hc = hc * 31 + (type == null ? 0 : type.ordinal());
hc = hc * 31 + (useIndex == null ? 0 : useIndex.hashCode());
hc = hc * 31 + (rawSql == null ? 0 : rawSql.queryHash());
hc = hc * 31 + (autoFetchTuned ? 31 : 0);
hc = hc * 31 + (distinct ? 31 : 0);
hc = hc * 31 + (query == null ? 0 : query.hashCode());
hc = hc * 31 + detail.queryPlanHash(request);
hc = hc * 31 + (firstRow == 0 ? 0 : firstRow);
hc = hc * 31 + (maxRows == 0 ? 0 : maxRows);
hc = hc * 31 + (orderBy == null ? 0 : orderBy.hash());
hc = hc * 31 + (rawWhereClause == null ? 0 : rawWhereClause.hashCode());
hc = hc * 31 + (additionalWhere == null ? 0 : additionalWhere.hashCode());
hc = hc * 31 + (additionalHaving == null ? 0 : additionalHaving.hashCode());
hc = hc * 31 + (mapKey == null ? 0 : mapKey.hashCode());
hc = hc * 31 + (id == null ? 0 : 1);
if (bindParams != null) {
hc = hc * 31 + bindParams.getQueryPlanHash();
if (detail != null) {
detail.queryPlanHash(request, builder);
}
if (bindParams != null) {
bindParams.buildQueryPlanHash(builder);
}
if (request == null) {
// for AutoFetch...
hc = hc * 31 + (whereExpressions == null ? 0 : whereExpressions.queryAutoFetchHash());
hc = hc * 31 + (havingExpressions == null ? 0 : havingExpressions.queryAutoFetchHash());
builder.add(true);
if (whereExpressions != null) {
whereExpressions.queryAutoFetchHash(builder);
}
if (havingExpressions != null) {
havingExpressions.queryAutoFetchHash(builder);
}
} else {
// for query plan...
hc = hc * 31 + (whereExpressions == null ? 0 : whereExpressions.queryPlanHash(request));
hc = hc * 31 + (havingExpressions == null ? 0 : havingExpressions.queryPlanHash(request));
builder.add(false);
if (whereExpressions != null) {
whereExpressions.queryPlanHash(request, builder);
}
if (havingExpressions != null) {
havingExpressions.queryPlanHash(request, builder);
}
}
hc = hc * 31 + (forUpdate ? 31 : 0);
return hc;
return builder.build();
}
/**
* Calculate a hash used by AutoFetch to identify when a query has changed
* (and hence potentially needs a new tuned query plan to be developed).
*/
public int queryAutofetchHash() {
public HashQueryPlan queryAutofetchHash(HashQueryPlanBuilder builder) {
return calculateHash(null);
return calculateHash(null, builder);
}
/**
@@ -695,9 +697,9 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
* This is calculated AFTER AutoFetch query tuning has occurred.
* </p>
*/
public int queryPlanHash(BeanQueryRequest<?> request) {
public HashQueryPlan queryPlanHash(BeanQueryRequest<?> request) {
queryPlanHash = calculateHash(request);
queryPlanHash = calculateHash(request, null);
return queryPlanHash;
}
@@ -724,12 +726,12 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
* (including bind values) before.
* </p>
*/
public int queryHash() {
public HashQuery queryHash() {
// calculateQueryPlanHash is called just after potential AutoFetch tuning
// so queryPlanHash is calculated well before this method is called
int hc = queryPlanHash;
hc = hc * 31 + queryBindHash();
return hc;
int hc = queryBindHash();
return new HashQuery(queryPlanHash, hc);
}
/**
@@ -14,6 +14,7 @@ import javax.persistence.PersistenceException;
import com.avaje.ebean.FetchConfig;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
@@ -63,17 +64,19 @@ public class OrmQueryDetail implements Serializable {
/**
* Calculate the hash for the query plan.
*/
public int queryPlanHash(BeanQueryRequest<?> request) {
int hc = (baseProps == null ? 1 : baseProps.queryPlanHash(request));
if (fetchPaths != null) {
for (OrmQueryProperties p : fetchPaths.values()) {
hc = hc * 31 + p.queryPlanHash(request);
}
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
if (baseProps == null) {
builder.add(false);
} else {
builder.add(true);
baseProps.queryPlanHash(request, builder);
}
if (fetchPaths != null) {
for (OrmQueryProperties p : fetchPaths.values()) {
p.queryPlanHash(request, builder);
}
return hc;
}
}
/**
@@ -14,6 +14,7 @@ import com.avaje.ebean.FetchConfig;
import com.avaje.ebean.OrderBy;
import com.avaje.ebean.Query;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpressionFactory;
import com.avaje.ebeaninternal.api.SpiExpressionList;
import com.avaje.ebeaninternal.api.SpiQuery;
@@ -355,17 +356,18 @@ public class OrmQueryProperties implements Serializable {
* Calculate the query plan hash.
*/
@SuppressWarnings("unchecked")
public int queryPlanHash(BeanQueryRequest<?> request) {
int hc = (path != null ? path.hashCode() : 1);
if (properties != null){
hc = hc * 31 + properties.hashCode();
} else {
hc = hc * 31 + (included != null ? included.hashCode() : 1);
}
hc = hc * 31 + (filterMany != null ? filterMany.queryPlanHash(request) : 1);
return hc;
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
builder.add(path);
if (properties != null) {
builder.add(properties);
} else {
builder.add(included);
}
builder.add(filterMany != null);
if (filterMany != null) {
filterMany.queryPlanHash(request, builder);
}
}
public String getProperties() {
@@ -20,6 +20,7 @@ import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.QueryListener;
import com.avaje.ebean.QueryResultVisitor;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionList;
@@ -283,26 +284,24 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
* Calculate a hash based on the expressions but excluding the actual bind
* values.
*/
public int queryAutoFetchHash() {
int hash = DefaultExpressionList.class.getName().hashCode();
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
builder.add(DefaultExpressionList.class);
for (int i = 0, size = list.size(); i < size; i++) {
SpiExpression expression = list.get(i);
hash = hash * 31 + expression.queryAutoFetchHash();
expression.queryAutoFetchHash(builder);
}
return hash;
}
/**
* Calculate a hash based on the expressions but excluding the actual bind
* values.
*/
public int queryPlanHash(BeanQueryRequest<?> request) {
int hash = DefaultExpressionList.class.getName().hashCode();
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
builder.add(DefaultExpressionList.class);
for (int i = 0, size = list.size(); i < size; i++) {
SpiExpression expression = list.get(i);
hash = hash * 31 + expression.queryPlanHash(request);
expression.queryPlanHash(request, builder);
}
return hash;
}
/**