From f2872054214649219707e363136dd20f8e3dcbac Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 21 Jan 2014 00:48:38 +1300 Subject: [PATCH] Refactor of query plan hash - add HashQueryPlan, HashQuery etc --- src/main/java/com/avaje/ebean/OrderBy.java | 47 +++++----- .../meta/MetaBeanQueryPlanStatistic.java | 6 +- .../avaje/ebeaninternal/api/BindParams.java | 5 +- .../avaje/ebeaninternal/api/HashQuery.java | 51 +++++++++++ .../ebeaninternal/api/HashQueryPlan.java | 46 ++++++++++ .../api/HashQueryPlanBuilder.java | 80 +++++++++++++++++ .../ebeaninternal/api/SpiExpression.java | 4 +- .../ebeaninternal/api/SpiExpressionList.java | 25 +++--- .../com/avaje/ebeaninternal/api/SpiQuery.java | 6 +- .../server/core/OrmQueryRequest.java | 17 ++-- .../server/deploy/BeanDescriptor.java | 7 +- .../expression/AllEqualsExpression.java | 34 +++---- .../server/expression/BetweenExpression.java | 12 +-- .../expression/BetweenPropertyExpression.java | 13 ++- .../CaseInsensitiveEqualExpression.java | 14 +-- .../expression/DefaultExampleExpression.java | 13 ++- .../server/expression/IdExpression.java | 12 +-- .../server/expression/IdInExpression.java | 14 ++- .../server/expression/InExpression.java | 16 ++-- .../server/expression/InQueryExpression.java | 17 ++-- .../server/expression/JunctionExpression.java | 21 ++--- .../server/expression/LikeExpression.java | 15 ++-- .../server/expression/LogicExpression.java | 19 ++-- .../expression/LuceneAwareExpression.java | 10 --- .../server/expression/NotExpression.java | 16 ++-- .../server/expression/NullExpression.java | 12 ++- .../PersistenceLuceneParseException.java | 16 ---- .../server/expression/RawExpression.java | 11 ++- .../server/expression/SimpleExpression.java | 15 ++-- .../server/query/CQueryPlan.java | 15 +++- .../server/query/CQueryPlanStats.java | 4 +- .../server/querydefn/DefaultOrmQuery.java | 88 ++++++++++--------- .../server/querydefn/OrmQueryDetail.java | 23 ++--- .../server/querydefn/OrmQueryProperties.java | 24 ++--- .../util/DefaultExpressionList.java | 15 ++-- .../tests/query/other/TestQueryPlanHash.java | 56 ++++++++++++ 36 files changed, 499 insertions(+), 300 deletions(-) create mode 100644 src/main/java/com/avaje/ebeaninternal/api/HashQuery.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/HashQueryPlan.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/HashQueryPlanBuilder.java delete mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/LuceneAwareExpression.java delete mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/PersistenceLuceneParseException.java create mode 100644 src/test/java/com/avaje/tests/query/other/TestQueryPlanHash.java diff --git a/src/main/java/com/avaje/ebean/OrderBy.java b/src/main/java/com/avaje/ebean/OrderBy.java index 734960217..83c2b3404 100644 --- a/src/main/java/com/avaje/ebean/OrderBy.java +++ b/src/main/java/com/avaje/ebean/OrderBy.java @@ -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. *

- * - * @author rbygrave */ public final class OrderBy implements Serializable { @@ -24,7 +22,7 @@ public final class OrderBy implements Serializable { private transient Query query; - private List list; + private final List list; /** * Create an empty OrderBy with no associated query. @@ -168,31 +166,23 @@ public final class OrderBy 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 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(); diff --git a/src/main/java/com/avaje/ebean/meta/MetaBeanQueryPlanStatistic.java b/src/main/java/com/avaje/ebean/meta/MetaBeanQueryPlanStatistic.java index 6980c59c8..1b4ffe9a1 100644 --- a/src/main/java/com/avaje/ebean/meta/MetaBeanQueryPlanStatistic.java +++ b/src/main/java/com/avaje/ebean/meta/MetaBeanQueryPlanStatistic.java @@ -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. diff --git a/src/main/java/com/avaje/ebeaninternal/api/BindParams.java b/src/main/java/com/avaje/ebeaninternal/api/BindParams.java index f5342779b..23f43cd63 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/BindParams.java +++ b/src/main/java/com/avaje/ebeaninternal/api/BindParams.java @@ -50,7 +50,7 @@ public class BindParams implements Serializable { * taken into account when calculating the query hash. *

*/ - 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); } /** diff --git a/src/main/java/com/avaje/ebeaninternal/api/HashQuery.java b/src/main/java/com/avaje/ebeaninternal/api/HashQuery.java new file mode 100644 index 000000000..0a560a57b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/HashQuery.java @@ -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); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/HashQueryPlan.java b/src/main/java/com/avaje/ebeaninternal/api/HashQueryPlan.java new file mode 100644 index 000000000..96055b245 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/HashQueryPlan.java @@ -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); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/HashQueryPlanBuilder.java b/src/main/java/com/avaje/ebeaninternal/api/HashQueryPlanBuilder.java new file mode 100644 index 000000000..1d9a70f4d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/HashQueryPlanBuilder.java @@ -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); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java index d3af546ce..09c3574f4 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java @@ -26,7 +26,7 @@ public interface SpiExpression extends Expression { * from an AutoFetch perspective and get different tuning. *

*/ - 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. *

*/ - public int queryPlanHash(BeanQueryRequest request); + public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder); /** * Return the hash value for the values that will be bound. diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java index 52190c5e0..d5467593d 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java @@ -28,14 +28,13 @@ public interface SpiExpressionList extends ExpressionList { */ public void setExpressionFactory(ExpressionFactory expr); - /** - * Process "Many" properties populating ManyWhereJoins. - *

- * Predicates on Many properties require an extra independent - * join clause. - *

- */ - public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoins); + /** + * Process "Many" properties populating ManyWhereJoins. + *

+ * Predicates on Many properties require an extra independent join clause. + *

+ */ + public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoins); /** * Return true if this list is empty. @@ -62,10 +61,10 @@ public interface SpiExpressionList extends ExpressionList { */ public ArrayList 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); } diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java index 9c9c3593d..7a59ce06e 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -341,7 +341,7 @@ public interface SpiQuery extends Query { * tuning/modifying the query. *

*/ - 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 extends Query { * Excludes the actual bind values (as they don't effect the query plan). *

*/ - 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 extends Query { /** * 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 diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java index 754876e2f..690f50bca 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java @@ -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 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 extends BeanRequest implements BeanQueryRe * with just the bind variables changing. *

*/ - public int getQueryPlanHash() { + public HashQueryPlan getQueryPlanHash() { return queryPlanHash; } @@ -356,14 +358,7 @@ public final class OrmQueryRequest 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; diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index c3c873b1e..4b6024c92 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -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 implements MetaBeanInfo { private final ConcurrentHashMap updatePlanCache = new ConcurrentHashMap(); - private final ConcurrentHashMap queryPlanCache = new ConcurrentHashMap(); + private final ConcurrentHashMap queryPlanCache = new ConcurrentHashMap(); private final ConcurrentHashMap elGetCache = new ConcurrentHashMap(); @@ -1202,11 +1203,11 @@ public class BeanDescriptor 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); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java index c82a66abf..17f12deb3 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java @@ -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. *

*/ - public int queryAutoFetchHash() { + public void queryAutoFetchHash(HashQueryPlanBuilder builder) { - int hc = AllEqualsExpression.class.getName().hashCode(); - Set> entries = propMap.entrySet(); - Iterator> it = entries.iterator(); + builder.add(AllEqualsExpression.class); - while (it.hasNext()) { - Map.Entry entry = it.next(); + for (Entry 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(); - } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java index e25b9d025..a5e36c7fc 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java @@ -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() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java index 7a2f14b66..917986aed 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java @@ -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() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java index 517185f8c..553eb203a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java @@ -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() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java index 04edff69c..710eec230 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java @@ -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; } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java index 43c203ff9..a30579894 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java @@ -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() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java index 7026db7f6..9d6d327b3 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java @@ -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() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java index dc43a320d..06a6c5e4e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java @@ -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; diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java index 4fa82d4bf..5c0c6a83d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java @@ -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); } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java index c8d0b7af1..33cf5c1bf 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java @@ -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 implements Junction, 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 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 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() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java index d0bec6978..ea20c61f9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java @@ -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() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java index a8e813227..9a8058f48 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java @@ -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() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/LuceneAwareExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/LuceneAwareExpression.java deleted file mode 100644 index f80115e4e..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/LuceneAwareExpression.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.avaje.ebeaninternal.server.expression; - -/** - * Marker interface for lucene aware expressions. - * - * @author rbygrave - */ -public interface LuceneAwareExpression { - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java index e17885ada..6badb6947 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java @@ -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() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java index e5f80d1e0..01533d5a0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java @@ -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() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/PersistenceLuceneParseException.java b/src/main/java/com/avaje/ebeaninternal/server/expression/PersistenceLuceneParseException.java deleted file mode 100644 index f5a8e6364..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/PersistenceLuceneParseException.java +++ /dev/null @@ -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); - } - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java index 048c31f39..3f76082de 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java @@ -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() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java index 49e0fbe7d..5fb5de70d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java @@ -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() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java index 0f6380346..526ce1e42 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java @@ -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; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanStats.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanStats.java index d492403fe..72de23d8c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanStats.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanStats.java @@ -131,8 +131,8 @@ public final class CQueryPlanStats { @Override - public int getQueryPlanHash() { - return queryPlan.getHash(); + public String getQueryPlanHash() { + return queryPlan.getHash().toString(); } @Override diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 6d6d748a8..2defb4988 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -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 implements SpiQuery { /** * Hash of final query after AutoFetch tuning. */ - private int queryPlanHash; + private HashQueryPlan queryPlanHash; private transient PersistenceContext persistenceContext; @@ -627,62 +630,61 @@ public class DefaultOrmQuery implements SpiQuery { /** * 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 implements SpiQuery { * This is calculated AFTER AutoFetch query tuning has occurred. *

*/ - 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 implements SpiQuery { * (including bind values) before. *

*/ - 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); } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java index d525cb077..3e70e6fc7 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -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; + } } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java index f813629e0..def146384 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -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() { diff --git a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java index 8c18dd40a..eee8f5eae 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java @@ -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 implements SpiExpressionList { * 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; } /** diff --git a/src/test/java/com/avaje/tests/query/other/TestQueryPlanHash.java b/src/test/java/com/avaje/tests/query/other/TestQueryPlanHash.java new file mode 100644 index 000000000..34e7b6827 --- /dev/null +++ b/src/test/java/com/avaje/tests/query/other/TestQueryPlanHash.java @@ -0,0 +1,56 @@ +package com.avaje.tests.query.other; + +import org.junit.Assert; +import org.junit.Test; + +import com.avaje.ebean.Ebean; +import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.tests.model.basic.Order; + +public class TestQueryPlanHash { + + @Test + public void test() { + + + HashQueryPlanBuilder builder1 = new HashQueryPlanBuilder(); + + SpiQuery squery = (SpiQuery)Ebean.find(Order.class); + squery.queryAutofetchHash(builder1); + + squery.where().in("id", 1,2,3); + + HashQueryPlanBuilder builder2 = new HashQueryPlanBuilder(); + squery.queryAutofetchHash(builder2); + int q1BindHash = squery.queryBindHash(); + + Assert.assertNotSame(builder1.build(), builder2.build()); + + HashQueryPlanBuilder builder3 = new HashQueryPlanBuilder(); + SpiQuery squery2 = (SpiQuery)Ebean.find(Order.class); + squery2.where().in("id", 2,2,3); + + squery2.queryAutofetchHash(builder3); + int q2BindHash = squery2.queryBindHash(); + + Assert.assertEquals(builder3.build(), builder2.build()); + + Assert.assertTrue(q1BindHash != q2BindHash); + + HashQueryPlanBuilder builder4 = new HashQueryPlanBuilder(); + SpiQuery squery4 = (SpiQuery)Ebean.find(Order.class); + squery4.where().in("name", 2,2,3); + + squery4.queryAutofetchHash(builder4); + int q4BindHash = squery4.queryBindHash(); + + //different query plan + Assert.assertTrue(!builder3.build().equals(builder4.build())); + + //same bind hash + Assert.assertTrue(q4BindHash == q2BindHash); + + } + +}