diff --git a/src/main/java/com/avaje/ebeaninternal/api/CQueryPlanKey.java b/src/main/java/com/avaje/ebeaninternal/api/CQueryPlanKey.java new file mode 100644 index 000000000..b169dca82 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/CQueryPlanKey.java @@ -0,0 +1,14 @@ +package com.avaje.ebeaninternal.api; + +/** + * Key used for caching query plans for ORM and RawSql queries. + */ +public interface CQueryPlanKey { + + /** + * Used by read audit such that we can log read audit entries without the full sql + * (which would make the read audit logs verbose). + */ + String getPartialKey(); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/HashQuery.java b/src/main/java/com/avaje/ebeaninternal/api/HashQuery.java index 0a560a57b..5d8f6f3ae 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/HashQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/HashQuery.java @@ -5,32 +5,18 @@ package com.avaje.ebeaninternal.api; */ public class HashQuery { - private final HashQueryPlan planHash; + private final CQueryPlanKey planHash; private final int bindHash; /** * Create the HashQuery. */ - public HashQuery(HashQueryPlan planHash, int bindHash) { + public HashQuery(CQueryPlanKey planHash, int bindHash) { this.planHash = planHash; this.bindHash = bindHash; } - /** - * Return the query plan hash. - */ - public HashQueryPlan getPlanHash() { - return planHash; - } - - /** - * Return the bind values hash. - */ - public int getBindHash() { - return bindHash; - } - public int hashCode() { int hc = 31 * planHash.hashCode(); hc = 31 * hc + bindHash; diff --git a/src/main/java/com/avaje/ebeaninternal/api/HashQueryPlanBuilder.java b/src/main/java/com/avaje/ebeaninternal/api/HashQueryPlanBuilder.java index ecbe059c1..9365d6b20 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/HashQueryPlanBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/api/HashQueryPlanBuilder.java @@ -9,14 +9,12 @@ public class HashQueryPlanBuilder { private int bindCount; - private String rawSql; - public HashQueryPlanBuilder() { this.planHash = 92821; } public String toString() { - return planHash+":"+bindCount+(rawSql != null ? ":r" : ""); + return planHash+":"+bindCount; } /** @@ -58,19 +56,24 @@ public class HashQueryPlanBuilder { bindCount += extraBindCount; } - /** - * Add raw sql to the hash. - */ - public void addRawSql(String rawSql) { - this.rawSql = rawSql; + public void bindIfNotNull(Object someValue) { + if (someValue != null) { + bindCount++; + } } /** * Build and return the calculated HashQueryPlan. */ - public HashQueryPlan build() { - return new HashQueryPlan(rawSql, planHash, bindCount); + public String build() { + return planHash+"_"+bindCount; } - + public int getPlanHash() { + return planHash; + } + + public int getBindCount() { + return bindCount; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java index d2987fc53..bacd508de 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java @@ -37,6 +37,16 @@ public interface SpiExpression extends Expression { */ int queryBindHash(); + /** + * Return true if the expression is the same without taking into account bind values. + */ + boolean isSameByPlan(SpiExpression other); + + /** + * Return true if the expression is the same with respect to bind values. + */ + boolean isSameByBind(SpiExpression other); + /** * Add some sql to the query. *

@@ -65,4 +75,10 @@ public interface SpiExpression extends Expression { * Validate all the properties/paths associated with this expression. */ void validate(SpiExpressionValidation validation); + + /** + * Return a copy of the expression for use in the query plan key. + */ + SpiExpression copyForPlanKey(); + } diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java index 98c7ee1fd..de075203c 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java @@ -1,16 +1,13 @@ package com.avaje.ebeaninternal.api; -import com.avaje.ebean.ExpressionFactory; import com.avaje.ebean.ExpressionList; -import com.avaje.ebean.event.BeanQueryRequest; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import java.util.List; /** * Internal extension of ExpressionList. */ -public interface SpiExpressionList extends ExpressionList { +public interface SpiExpressionList extends ExpressionList, SpiExpression { /** * Return the underlying list of expressions. @@ -22,57 +19,9 @@ public interface SpiExpressionList extends ExpressionList { */ SpiExpressionList trimPath(int prefixTrim); - /** - * Restore the ExpressionFactory after deserialisation. - */ - void setExpressionFactory(ExpressionFactory expr); - - /** - * Process "Many" properties populating ManyWhereJoins. - *

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

- */ - void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoins); - /** * Return true if this list is empty. */ boolean isEmpty(); - /** - * Concatenate the expression sql into a String. - *

- * The list of expressions are evaluated in order building a sql statement - * with bind parameters. - *

- */ - String buildSql(SpiExpressionRequest request); - - /** - * Combine the expression bind values into a list. - *

- * Expressions are evaluated in order and all the resulting bind values are - * returned as a List. - *

- * - * @return the list of all the bind values in order. - */ - List buildBindValues(SpiExpressionRequest request); - - /** - * Prepare the expressions contained in the list. For example, compile sub-query expressions etc. - */ - void prepareExpression(BeanQueryRequest request); - - /** - * Calculate a hash based on the expressions but excluding the actual bind - * values. - */ - void queryPlanHash(HashQueryPlanBuilder builder); - - /** - * Validate all the properties/paths used in this expression list. - */ - void validate(SpiExpressionValidation validation); } diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java index a03634ff0..c9def4a75 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -467,17 +467,13 @@ public interface SpiQuery extends Query { String getName(); /** - * Identifies queries that are the same bar the bind variables. + * Prepare the query which prepares sub-query expressions and calculates + * and returns the query plan key. *

- * This is used AFTER AutoTune has potentially tuned the query. This is - * used to identify and reused query plans (the final SQL string and - * associated SqlTree object). - *

- *

- * Excludes the actual bind values (as they don't effect the query plan). + * The query plan excludes actual bind values (as they don't effect the query plan). *

*/ - HashQueryPlan queryPlanHash(BeanQueryRequest request); + CQueryPlanKey prepare(BeanQueryRequest request); /** * Calculate a hash based on the bind values used in the query. diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java index 95dc3b21d..d00d5793b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -1110,7 +1110,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } // the query hash after any tuning - request.calculateQueryPlanHash(); + request.prepareQuery(); return request; } 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 4735cce1a..952dfa2ca 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java @@ -1,14 +1,11 @@ package com.avaje.ebeaninternal.server.core; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.persistence.PersistenceException; - -import com.avaje.ebean.*; +import com.avaje.ebean.PersistenceContextScope; +import com.avaje.ebean.QueryEachConsumer; +import com.avaje.ebean.QueryEachWhileConsumer; +import com.avaje.ebean.QueryIterator; +import com.avaje.ebean.RawSql; +import com.avaje.ebean.Version; import com.avaje.ebean.bean.BeanCollection; import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.bean.PersistenceContext; @@ -16,8 +13,8 @@ import com.avaje.ebean.event.BeanFindController; import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.BeanIdList; import com.avaje.ebeaninternal.api.HashQuery; -import com.avaje.ebeaninternal.api.HashQueryPlan; import com.avaje.ebeaninternal.api.LoadContext; +import com.avaje.ebeaninternal.api.CQueryPlanKey; import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.api.SpiQuery.Type; @@ -32,6 +29,13 @@ import com.avaje.ebeaninternal.server.query.CQueryPlan; import com.avaje.ebeaninternal.server.query.CancelableQuery; import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext; +import javax.persistence.PersistenceException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + /** * Wraps the objects involved in executing a Query. */ @@ -55,7 +59,7 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe private HashQuery cacheKey; - private HashQueryPlan queryPlanHash; + private CQueryPlanKey queryPlanKey; /** * Create the InternalQueryRequest. @@ -126,10 +130,10 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe } /** - * Calculate the query plan hash AFTER any potential AutoTune tuning. + * Prepare the query and calculate the query plan key. */ - public void calculateQueryPlanHash() { - this.queryPlanHash = query.queryPlanHash(this); + public void prepareQuery() { + this.queryPlanKey = query.prepare(this); } public boolean isRawSql() { @@ -355,7 +359,7 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe * query plan for this query exists. */ public CQueryPlan getQueryPlan() { - return beanDescriptor.getQueryPlan(queryPlanHash); + return beanDescriptor.getQueryPlan(queryPlanKey); } /** @@ -366,15 +370,15 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe * with just the bind variables changing. *

*/ - public HashQueryPlan getQueryPlanHash() { - return queryPlanHash; + public CQueryPlanKey getQueryPlanKey() { + return queryPlanKey; } /** * Put the QueryPlan into the cache. */ public void putQueryPlan(CQueryPlan queryPlan) { - beanDescriptor.putQueryPlan(queryPlanHash, queryPlan); + beanDescriptor.putQueryPlan(queryPlanKey, queryPlan); } public boolean isUseBeanCache() { @@ -401,7 +405,7 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe for (T bean : actualDetails) { ids.add(beanDescriptor.getIdForJson(bean)); } - beanDescriptor.readAuditMany(queryPlanHash.getPartialKey(), "l2-query-cache", ids); + beanDescriptor.readAuditMany(queryPlanKey.getPartialKey(), "l2-query-cache", ids); } return cached; 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 1763ef9ba..85c5f06e5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -29,7 +29,7 @@ import com.avaje.ebean.event.readaudit.ReadEvent; import com.avaje.ebean.meta.MetaBeanInfo; import com.avaje.ebean.meta.MetaQueryPlanStatistic; import com.avaje.ebean.plugin.SpiBeanType; -import com.avaje.ebeaninternal.api.HashQueryPlan; +import com.avaje.ebeaninternal.api.CQueryPlanKey; import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.api.SpiTransaction; @@ -87,7 +87,7 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType { private final ConcurrentHashMap updatePlanCache = new ConcurrentHashMap(); - private final ConcurrentHashMap queryPlanCache = new ConcurrentHashMap(); + private final ConcurrentHashMap queryPlanCache = new ConcurrentHashMap(); private final ConcurrentHashMap elCache = new ConcurrentHashMap(); @@ -1163,11 +1163,11 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType { } } - public CQueryPlan getQueryPlan(HashQueryPlan key) { + public CQueryPlan getQueryPlan(CQueryPlanKey key) { return queryPlanCache.get(key); } - public void putQueryPlan(HashQueryPlan key, CQueryPlan plan) { + public void putQueryPlan(CQueryPlanKey key, CQueryPlan plan) { queryPlanCache.put(key, plan); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/IntersectionRow.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/IntersectionRow.java index a55a88115..3d0a48a65 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/IntersectionRow.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/IntersectionRow.java @@ -10,7 +10,7 @@ import com.avaje.ebean.SqlUpdate; import com.avaje.ebeaninternal.api.BindParams; import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate; import com.avaje.ebeaninternal.server.expression.IdInExpression; -import com.avaje.ebeaninternal.util.DefaultExpressionRequest; +import com.avaje.ebeaninternal.server.expression.DefaultExpressionRequest; public class IntersectionRow { diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoin.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoin.java index ac228db2a..8ed839383 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoin.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoin.java @@ -63,6 +63,32 @@ public final class TableJoin { return hc; } + @Override + public int hashCode() { + return queryHash; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TableJoin that = (TableJoin) o; + + if (!table.equals(that.table)) return false; + if (type != that.type) return false; + if (columns.length != that.columns.length) return false; + + for (int i = 0; i < columns.length; i++) { + if (!columns[i].equals(that.columns[i])) { + return false; + } + } + return true; + } + + + /** * Return a hash value for adding to a query plan. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumn.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumn.java index 0ee61b6cc..59a77f77f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumn.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumn.java @@ -8,76 +8,97 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoinColumn; */ public class TableJoinColumn { - /** - * The local database column name. - */ - private final String localDbColumn; + /** + * The local database column name. + */ + private final String localDbColumn; - /** - * The foreign database column name. - */ - private final String foreignDbColumn; + /** + * The foreign database column name. + */ + private final String foreignDbColumn; - private final boolean insertable; - - private final boolean updateable; + private final boolean insertable; - /** - * Hash for including in a query plan - */ - private final int queryHash; + private final boolean updateable; - /** - * Create the pair. - */ - public TableJoinColumn(DeployTableJoinColumn deploy) { - this.localDbColumn = InternString.intern(deploy.getLocalDbColumn()); - this.foreignDbColumn = InternString.intern(deploy.getForeignDbColumn()); - this.insertable = deploy.isInsertable(); - this.updateable = deploy.isUpdateable(); - this.queryHash = hashOf(localDbColumn) * 31 + hashOf(foreignDbColumn); - } + /** + * Hash for including in a query plan + */ + private final int queryHash; - private int hashOf(String value) { - return (value == null) ? 0 : value.hashCode(); - } + /** + * Create the pair. + */ + public TableJoinColumn(DeployTableJoinColumn deploy) { + this.localDbColumn = InternString.intern(deploy.getLocalDbColumn()); + this.foreignDbColumn = InternString.intern(deploy.getForeignDbColumn()); + this.insertable = deploy.isInsertable(); + this.updateable = deploy.isUpdateable(); + this.queryHash = hash(); + } - public String toString() { - return localDbColumn+" = "+foreignDbColumn; - } + int hash() { + int result = localDbColumn != null ? localDbColumn.hashCode() : 0; + result = 31 * result + (foreignDbColumn != null ? foreignDbColumn.hashCode() : 0); + result = 31 * result + (insertable ? 1 : 0); + result = 31 * result + (updateable ? 1 : 0); + return result; + } - /** - * Return a hash for including in a query plan. - */ - public int queryHash() { - return queryHash; - } + @Override + public int hashCode() { + return queryHash; + } - /** - * Return the foreign database column name. - */ - public String getForeignDbColumn() { - return foreignDbColumn; - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; - /** - * Return the local database column name. - */ - public String getLocalDbColumn() { - return localDbColumn; - } + TableJoinColumn that = (TableJoinColumn) o; + if (insertable != that.insertable) return false; + if (updateable != that.updateable) return false; + if (!localDbColumn.equals(that.localDbColumn)) return false; + return foreignDbColumn.equals(that.foreignDbColumn); + } - /** - * Return true if this column should be insertable. - */ - public boolean isInsertable() { - return insertable; - } + public String toString() { + return localDbColumn + " = " + foreignDbColumn; + } - /** - * Return true if this column should be updateable. - */ - public boolean isUpdateable() { - return updateable; - } + /** + * Return a hash for including in a query plan. + */ + public int queryHash() { + return queryHash; + } + + /** + * Return the foreign database column name. + */ + public String getForeignDbColumn() { + return foreignDbColumn; + } + + /** + * Return the local database column name. + */ + public String getLocalDbColumn() { + return localDbColumn; + } + + /** + * Return true if this column should be insertable. + */ + public boolean isInsertable() { + return insertable; + } + + /** + * Return true if this column should be updateable. + */ + public boolean isUpdateable() { + return updateable; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/AbstractExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/AbstractExpression.java index 63b72bcb6..d471a8d57 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/AbstractExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/AbstractExpression.java @@ -22,19 +22,20 @@ public abstract class AbstractExpression implements SpiExpression { this.propName = propName; } - public String getPropertyName() { - return propName; + @Override + public SpiExpression copyForPlanKey() { + return this; } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { - String propertyName = getPropertyName(); - if (propertyName != null) { - ElPropertyDeploy elProp = desc.getElPropertyDeploy(propertyName); + if (propName != null) { + ElPropertyDeploy elProp = desc.getElPropertyDeploy(propName); if (elProp != null) { if (elProp.containsFormulaWithJoin()) { // for findRowCount query select clause - manyWhereJoin.addFormulaWithJoin(propertyName); + manyWhereJoin.addFormulaWithJoin(propName); } if (elProp.containsMany()) { // for findRowCount we join to a many property @@ -51,12 +52,11 @@ public abstract class AbstractExpression implements SpiExpression { @Override public void validate(SpiExpressionValidation validation) { - validation.validate(getPropertyName()); + validation.validate(propName); } - protected ElPropertyValue getElProp(SpiExpressionRequest request) { + protected final ElPropertyValue getElProp(SpiExpressionRequest request) { - String propertyName = getPropertyName(); - return request.getBeanDescriptor().getElGetValue(propertyName); + return request.getBeanDescriptor().getElGetValue(propName); } } 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 2b338b234..f7a372b98 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java @@ -2,11 +2,13 @@ package com.avaje.ebeaninternal.server.expression; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; +import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; @@ -102,7 +104,7 @@ class AllEqualsExpression extends NonPrepareExpression { Object value = entry.getValue(); String propName = entry.getKey(); builder.add(propName).add(value == null ? 0 : 1); - builder.bind(value == null ? 0 : 1); + builder.bindIfNotNull(value); } } @@ -116,4 +118,48 @@ class AllEqualsExpression extends NonPrepareExpression { return hc; } + + @Override + public boolean isSameByPlan(SpiExpression other) { + if (!(other instanceof AllEqualsExpression)) { + return false; + } + + AllEqualsExpression that = (AllEqualsExpression) other; + return isSameByValue(that, false); + } + + @Override + public boolean isSameByBind(SpiExpression other) { + if (!(other instanceof AllEqualsExpression)) { + return false; + } + + AllEqualsExpression that = (AllEqualsExpression) other; + return isSameByValue(that, true); + } + + private boolean isSameByValue(AllEqualsExpression that, boolean byValue) { + + if (propMap.size() != that.propMap.size()) { + return false; + } + + Iterator> thisIt = propMap.entrySet().iterator(); + Iterator> thatIt = that.propMap.entrySet().iterator(); + + while (thisIt.hasNext() && thatIt.hasNext()) { + Entry thisNext = thisIt.next(); + Entry thatNext = thatIt.next(); + + if (!thisNext.getKey().equals(thatNext.getKey())) { + return false; + } + if (!Same.sameBy(byValue, thisNext.getValue(), thatNext.getValue())) { + return false; + } + } + + return true; + } } 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 20aa4dbf5..349baeace 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java @@ -1,10 +1,9 @@ package com.avaje.ebeaninternal.server.expression; -import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; - class BetweenExpression extends AbstractExpression { private static final long serialVersionUID = 2078918165221454910L; @@ -30,7 +29,7 @@ class BetweenExpression extends AbstractExpression { @Override public void addSql(SpiExpressionRequest request) { - request.append(getPropertyName()).append(BETWEEN).append(" ? and ? "); + request.append(propName).append(BETWEEN).append(" ? and ? "); } @Override @@ -45,4 +44,21 @@ class BetweenExpression extends AbstractExpression { hc = hc * 31 + valueHigh.hashCode(); return hc; } + + @Override + public boolean isSameByPlan(SpiExpression other) { + if (!(other instanceof BetweenExpression)) { + return false; + } + + BetweenExpression that = (BetweenExpression) other; + return this.propName.equals(that.propName); + } + + @Override + public boolean isSameByBind(SpiExpression other) { + BetweenExpression that = (BetweenExpression) other; + return valueLow.equals(that.valueLow) + && valueHigh.equals(that.valueHigh); + } } 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 b3a92dd00..06fdba136 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java @@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.expression; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; @@ -50,6 +51,7 @@ class BetweenPropertyExpression extends NonPrepareExpression { validation.validate(highProperty); } + @Override public void addBindValues(SpiExpressionRequest request) { request.addBindValue(value); } @@ -70,4 +72,21 @@ class BetweenPropertyExpression extends NonPrepareExpression { public int queryBindHash() { return value.hashCode(); } + + @Override + public boolean isSameByPlan(SpiExpression other) { + if (!(other instanceof BetweenPropertyExpression)) { + return false; + } + + BetweenPropertyExpression that = (BetweenPropertyExpression) other; + return lowProperty.equals(that.lowProperty) + && highProperty.equals(that.highProperty); + } + + @Override + public boolean isSameByBind(SpiExpression other) { + BetweenPropertyExpression that = (BetweenPropertyExpression) other; + return value.equals(that.value); + } } 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 2492b96c3..e4f32285a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java @@ -1,7 +1,7 @@ package com.avaje.ebeaninternal.server.expression; -import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.server.el.ElPropertyValue; @@ -16,6 +16,7 @@ class CaseInsensitiveEqualExpression extends AbstractExpression { this.value = value.toLowerCase(); } + @Override public void addBindValues(SpiExpressionRequest request) { ElPropertyValue prop = getElProp(request); @@ -28,14 +29,13 @@ class CaseInsensitiveEqualExpression extends AbstractExpression { request.addBindValue(value); } + @Override public void addSql(SpiExpressionRequest request) { - String propertyName = getPropertyName(); - String pname = propertyName; - + String pname = propName; ElPropertyValue prop = getElProp(request); if (prop != null && prop.isDbEncrypted()) { - pname = prop.getBeanProperty().getDecryptProperty(propertyName); + pname = prop.getBeanProperty().getDecryptProperty(propName); } request.append("lower(").append(pname).append(") =? "); @@ -51,4 +51,20 @@ class CaseInsensitiveEqualExpression extends AbstractExpression { public int queryBindHash() { return value.hashCode(); } + + @Override + public boolean isSameByPlan(SpiExpression other) { + if (!(other instanceof CaseInsensitiveEqualExpression)) { + return false; + } + + CaseInsensitiveEqualExpression that = (CaseInsensitiveEqualExpression) other; + return this.propName.equals(that.propName); + } + + @Override + public boolean isSameByBind(SpiExpression other) { + CaseInsensitiveEqualExpression that = (CaseInsensitiveEqualExpression) other; + return value.equals(that.value); + } } 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 8b642f30b..f085a92b2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java @@ -86,6 +86,19 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio this.likeType = likeType; } + DefaultExampleExpression(ArrayList source) { + this.entity = null; + this.list = new ArrayList(source.size()); + for (SpiExpression expression : source) { + list.add(expression.copyForPlanKey()); + } + } + + @Override + public SpiExpression copyForPlanKey() { + return new DefaultExampleExpression(list); + } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins whereManyJoins) { if (list != null) { @@ -201,6 +214,38 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio return hc; } + @Override + public boolean isSameByPlan(SpiExpression other) { + if (!(other instanceof DefaultExampleExpression)) { + return false; + } + + DefaultExampleExpression that = (DefaultExampleExpression) other; + if (this.list.size() != that.list.size()) { + return false; + } + for (int i = 0; i < list.size(); i++) { + if (!list.get(i).isSameByPlan(that.list.get(i))) { + return false; + } + } + return true; + } + + @Override + public boolean isSameByBind(SpiExpression other) { + DefaultExampleExpression that = (DefaultExampleExpression) other; + if (this.list.size() != that.list.size()) { + return false; + } + for (int i = 0; i < list.size(); i++) { + if (!list.get(i).isSameByBind(that.list.get(i))) { + return false; + } + } + return true; + } + /** * Build the List of expressions. */ diff --git a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java similarity index 92% rename from src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java rename to src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java index 7df81fc96..267bd493c 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java @@ -1,4 +1,4 @@ -package com.avaje.ebeaninternal.util; +package com.avaje.ebeaninternal.server.expression; import java.sql.Timestamp; import java.util.ArrayList; @@ -56,6 +56,10 @@ public class DefaultExpressionList implements SpiExpressionList { this.listAndJoin = " and "; } + private DefaultExpressionList() { + this(null, null, null, new ArrayList()); + } + @Override public SpiExpressionList trimPath(int prefixTrim) { throw new RuntimeException("Only allowed on FilterExpressionList"); @@ -65,17 +69,6 @@ public class DefaultExpressionList implements SpiExpressionList { return list; } - /** - * Set the ExpressionFactory. - *

- * After deserialisation so that it can be further modified. - *

- */ - @Override - public void setExpressionFactory(ExpressionFactory expr) { - this.expr = expr; - } - /** * Return a copy of the expression list. *

@@ -88,6 +81,14 @@ public class DefaultExpressionList implements SpiExpressionList { return copy; } + public DefaultExpressionList copyForPlanKey() { + DefaultExpressionList copy = new DefaultExpressionList(); + for (int i = 0; i < list.size(); i++) { + copy.list.add(list.get(i).copyForPlanKey()); + } + return copy; + } + /** * Return true if one of the expressions is related to a Many property. */ @@ -325,7 +326,7 @@ public class DefaultExpressionList implements SpiExpressionList { } @Override - public String buildSql(SpiExpressionRequest request) { + public void addSql(SpiExpressionRequest request) { request.append(listAndStart); for (int i = 0, size = list.size(); i < size; i++) { @@ -336,17 +337,13 @@ public class DefaultExpressionList implements SpiExpressionList { expression.addSql(request); } request.append(listAndEnd); - return request.getSql(); } @Override - public List buildBindValues(SpiExpressionRequest request) { - + public void addBindValues(SpiExpressionRequest request) { for (int i = 0, size = list.size(); i < size; i++) { - SpiExpression expression = list.get(i); - expression.addBindValues(request); + list.get(i).addBindValues(request); } - return request.getBindValues(); } @Override @@ -371,6 +368,7 @@ public class DefaultExpressionList implements SpiExpressionList { /** * Calculate a hash based on the expressions. */ + @Override public int queryBindHash() { int hash = DefaultExpressionList.class.getName().hashCode(); for (int i = 0, size = list.size(); i < size; i++) { @@ -379,6 +377,38 @@ public class DefaultExpressionList implements SpiExpressionList { return hash; } + @Override + public boolean isSameByPlan(SpiExpression other) { + if (!(other instanceof DefaultExpressionList)) { + return false; + } + + DefaultExpressionList that = (DefaultExpressionList)other; + if (list.size() != that.list.size()) { + return false; + } + for (int i = 0, size = list.size(); i < size; i++) { + if (!list.get(i).isSameByPlan(that.list.get(i))) { + return false; + } + } + return true; + } + + @Override + public boolean isSameByBind(SpiExpression other) { + DefaultExpressionList that = (DefaultExpressionList)other; + if (list.size() != that.list.size()) { + return false; + } + for (int i = 0, size = list.size(); i < size; i++) { + if (!list.get(i).isSameByBind(that.list.get(i))) { + return false; + } + } + return true; + } + /** * Path exists - for the given path in a JSON document. */ diff --git a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionRequest.java similarity index 93% rename from src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java rename to src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionRequest.java index 739071934..c87126df4 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionRequest.java @@ -1,4 +1,4 @@ -package com.avaje.ebeaninternal.util; +package com.avaje.ebeaninternal.server.expression; import java.sql.SQLException; import java.util.ArrayList; @@ -40,7 +40,7 @@ public class DefaultExpressionRequest implements SpiExpressionRequest { this.binder = binder; this.expressionList = expressionList; // immediately build the list of bind values (callback style) - expressionList.buildBindValues(this); + expressionList.addBindValues(this); } public DefaultExpressionRequest(BeanDescriptor beanDescriptor) { @@ -55,7 +55,8 @@ public class DefaultExpressionRequest implements SpiExpressionRequest { * Build sql for the underlying expression list. */ public String buildSql() { - return expressionList.buildSql(this); + expressionList.addSql(this); + return sql.toString(); } /** @@ -71,10 +72,12 @@ public class DefaultExpressionRequest implements SpiExpressionRequest { } } + @Override public JsonExpressionHandler getJsonHandler() { return binder.getJsonExpressionHandler(); } + @Override public String parseDeploy(String logicalProp) { String s = deployParser.getDeployWord(logicalProp); @@ -94,14 +97,17 @@ public class DefaultExpressionRequest implements SpiExpressionRequest { /** * Increments the parameter index and returns that value. */ + @Override public int nextParameter() { return ++paramIndex; } + @Override public BeanDescriptor getBeanDescriptor() { return beanDescriptor; } + @Override public SpiOrmQueryRequest getQueryRequest() { return queryRequest; } @@ -109,16 +115,19 @@ public class DefaultExpressionRequest implements SpiExpressionRequest { /** * Append text the underlying sql expression. */ + @Override public SpiExpressionRequest append(String sqlExpression) { sql.append(sqlExpression); return this; } + @Override public void addBindEncryptKey(Object bindValue) { bindValues.add(bindValue); bindLog("****"); } + @Override public void addBindValue(Object bindValue) { bindValues.add(bindValue); bindLog(bindValue); @@ -137,10 +146,12 @@ public class DefaultExpressionRequest implements SpiExpressionRequest { return bindLog == null ? "" : bindLog.toString(); } + @Override public String getSql() { return sql.toString(); } + @Override public List getBindValues() { return bindValues; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java index 1c5c1852e..dca741608 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java @@ -17,19 +17,26 @@ public class ExistsExpression implements SpiExpression { private static final long serialVersionUID = 666990277309851644L; - private final boolean not; + protected final boolean not; - private final SpiQuery subQuery; + protected final SpiQuery subQuery; - private List bindParams; + protected List bindParams; - private String sql; + protected String sql; public ExistsExpression(SpiQuery subQuery, boolean not) { this.subQuery = subQuery; this.not = not; } + ExistsExpression(boolean not, String sql , List bindParams) { + this.not = not; + this.sql = sql; + this.bindParams = bindParams; + this.subQuery = null; + } + @Override public void prepareExpression(BeanQueryRequest request) { @@ -38,10 +45,15 @@ public class ExistsExpression implements SpiExpression { this.sql = subQuery.getGeneratedSql().replace('\n', ' '); } + @Override + public SpiExpression copyForPlanKey() { + return this; + } + /** * Compile/build the sub query. */ - private CQuery compileSubQuery(BeanQueryRequest queryRequest) { + protected CQuery compileSubQuery(BeanQueryRequest queryRequest) { SpiEbeanServer ebeanServer = (SpiEbeanServer) queryRequest.getEbeanServer(); return ebeanServer.compileQuery(subQuery, queryRequest.getTransaction()); } @@ -76,6 +88,32 @@ public class ExistsExpression implements SpiExpression { } } + @Override + public boolean isSameByPlan(SpiExpression other) { + if (!(other instanceof ExistsExpression)) { + return false; + } + + ExistsExpression that = (ExistsExpression) other; + return this.sql.equals(that.sql) + && this.not == that.not + && this.bindParams.size() == that.bindParams.size(); + } + + @Override + public boolean isSameByBind(SpiExpression other) { + ExistsExpression that = (ExistsExpression) other; + if (this.bindParams.size() != that.bindParams.size()) { + return false; + } + for (int i = 0; i < bindParams.size(); i++) { + if (!bindParams.get(i).equals(that.bindParams.get(i))) { + return false; + } + } + return true; + } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins whereManyJoins) { // Nothing to do for exists expression diff --git a/src/main/java/com/avaje/ebeaninternal/util/FilterExpressionList.java b/src/main/java/com/avaje/ebeaninternal/server/expression/FilterExpressionList.java similarity index 96% rename from src/main/java/com/avaje/ebeaninternal/util/FilterExpressionList.java rename to src/main/java/com/avaje/ebeaninternal/server/expression/FilterExpressionList.java index e746bbd50..2c8fd7358 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/FilterExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/FilterExpressionList.java @@ -1,8 +1,7 @@ -package com.avaje.ebeaninternal.util; +package com.avaje.ebeaninternal.server.expression; import com.avaje.ebean.*; import com.avaje.ebeaninternal.api.SpiExpressionList; -import com.avaje.ebeaninternal.server.expression.FilterExprPath; import javax.persistence.PersistenceException; import java.util.List; 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 27493d159..41aaec673 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java @@ -6,7 +6,6 @@ import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.util.DefaultExpressionRequest; /** * Slightly redundant as Query.setId() ultimately also does the same job. @@ -69,4 +68,14 @@ class IdExpression extends NonPrepareExpression implements SpiExpression { return value.hashCode(); } + @Override + public boolean isSameByPlan(SpiExpression other) { + return other instanceof IdExpression; + } + + @Override + public boolean isSameByBind(SpiExpression other) { + IdExpression that = (IdExpression) other; + return value.equals(that.value); + } } 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 cff55539a..cf67e92c3 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java @@ -2,11 +2,11 @@ package com.avaje.ebeaninternal.server.expression; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.id.IdBinder; -import com.avaje.ebeaninternal.util.DefaultExpressionRequest; import java.util.List; @@ -86,4 +86,27 @@ public class IdInExpression extends NonPrepareExpression { return idList.hashCode(); } + @Override + public boolean isSameByPlan(SpiExpression other) { + if (!(other instanceof IdInExpression)) { + return false; + } + + IdInExpression that = (IdInExpression) other; + return this.idList.size() == that.idList.size(); + } + + @Override + public boolean isSameByBind(SpiExpression other) { + IdInExpression that = (IdInExpression) other; + if (this.idList.size() != that.idList.size()) { + return false; + } + for (int i = 0; i < idList.size(); i++) { + if (!idList.get(i).equals(that.idList.get(i))) { + return false; + } + } + return true; + } } 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 477f46ad3..52af4e2d3 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java @@ -1,8 +1,8 @@ package com.avaje.ebeaninternal.server.expression; import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.server.el.ElPropertyValue; @@ -61,20 +61,18 @@ class InExpression extends AbstractExpression { return; } - String propertyName = getPropertyName(); - ElPropertyValue prop = getElProp(request); if (prop != null && !prop.isAssocId()) { prop = null; } if (prop != null) { - request.append(prop.getAssocIdInExpr(propertyName)); + request.append(prop.getAssocIdInExpr(propName)); String inClause = prop.getAssocIdInValueExpr(values.length); request.append(inClause); } else { - request.append(propertyName); + request.append(propName); if (not) { request.append(" not"); } @@ -105,4 +103,29 @@ class InExpression extends AbstractExpression { return hc; } + @Override + public boolean isSameByPlan(SpiExpression other) { + if (!(other instanceof InExpression)) { + return false; + } + + InExpression that = (InExpression) other; + return propName.equals(that.propName) + && not == that.not + && values.length == that.values.length; + } + + @Override + public boolean isSameByBind(SpiExpression other) { + InExpression that = (InExpression) other; + if (this.values.length != that.values.length) { + return false; + } + for (int i = 0; i < values.length; i++) { + if (!values[i].equals(that.values[i])) { + return false; + } + } + return true; + } } 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 21ba19cc2..5da4daee4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java @@ -1,14 +1,15 @@ package com.avaje.ebeaninternal.server.expression; -import java.util.List; - import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.server.query.CQuery; +import java.util.List; + /** * In expression using a sub query. */ @@ -24,12 +25,20 @@ class InQueryExpression extends AbstractExpression { private String sql; - public InQueryExpression(String propertyName, SpiQuery subQuery, boolean not) { + InQueryExpression(String propertyName, SpiQuery subQuery, boolean not) { super(propertyName); this.subQuery = subQuery; this.not = not; } + InQueryExpression(String propertyName, boolean not, String sql, List bindParams) { + super(propertyName); + this.subQuery = null; + this.not = not; + this.sql = sql; + this.bindParams = bindParams; + } + @Override public void prepareExpression(BeanQueryRequest request) { @@ -61,7 +70,7 @@ class InQueryExpression extends AbstractExpression { @Override public void addSql(SpiExpressionRequest request) { - request.append(" (").append(getPropertyName()).append(")"); + request.append(" (").append(propName).append(")"); if (not) { request.append(" not"); } @@ -77,4 +86,31 @@ class InQueryExpression extends AbstractExpression { request.addBindValue(bindParams.get(i)); } } + + @Override + public boolean isSameByPlan(SpiExpression other) { + if (!(other instanceof InQueryExpression)) { + return false; + } + + InQueryExpression that = (InQueryExpression) other; + return propName.equals(that.propName) + && sql.equals(that.sql) + && not == that.not + && bindParams.size() == that.bindParams.size(); + } + + @Override + public boolean isSameByBind(SpiExpression other) { + InQueryExpression that = (InQueryExpression) other; + if (this.bindParams.size() != that.bindParams.size()) { + return false; + } + for (int i = 0; i < bindParams.size(); i++) { + if (!bindParams.get(i).equals(that.bindParams.get(i))) { + return false; + } + } + return true; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java index 547fa7001..0f14623b2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java @@ -1,7 +1,7 @@ package com.avaje.ebeaninternal.server.expression; -import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; /** @@ -60,6 +60,8 @@ class JsonPathExpression extends AbstractExpression { @Override public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(JsonPathExpression.class).add(propName).add(path).add(operator); + builder.bindIfNotNull(value); + builder.bindIfNotNull(upperValue); } @Override @@ -69,6 +71,27 @@ class JsonPathExpression extends AbstractExpression { return hc; } + @Override + public boolean isSameByPlan(SpiExpression other) { + if (!(other instanceof JsonPathExpression)) { + return false; + } + + JsonPathExpression that = (JsonPathExpression) other; + return propName.equals(that.propName) + && operator == that.operator + && Same.sameByValue(path, that.path) + && Same.sameByNull(value, that.value) + && Same.sameByNull(upperValue, that.upperValue); + } + + @Override + public boolean isSameByBind(SpiExpression other) { + JsonPathExpression that = (JsonPathExpression) other; + if (value != null ? !value.equals(that.value) : that.value != null) return false; + return upperValue != null ? upperValue.equals(that.upperValue) : that.upperValue == null; + } + @Override public void addSql(SpiExpressionRequest request) { 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 d4d88ffad..1f1641f2c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java @@ -15,7 +15,6 @@ import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.util.DefaultExpressionList; /** * Junction implementation. @@ -35,6 +34,14 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr Conjunction(com.avaje.ebean.Query query, ExpressionList parent) { super(false, AND, query, parent); } + + Conjunction(DefaultExpressionList expressionList) { + super(false, AND, expressionList); + } + @Override + public SpiExpression copyForPlanKey() { + return new Conjunction(exprList.copyForPlanKey()); + } } static class Disjunction extends JunctionExpression { @@ -44,9 +51,18 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr Disjunction(com.avaje.ebean.Query query, ExpressionList parent) { super(true, OR, query, parent); } + + Disjunction(DefaultExpressionList expressionList) { + super(true, OR, expressionList); + } + + @Override + public SpiExpression copyForPlanKey() { + return new Disjunction(exprList.copyForPlanKey()); + } } - private final DefaultExpressionList exprList; + protected final DefaultExpressionList exprList; private final String joinType; @@ -61,6 +77,15 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr this.exprList = new DefaultExpressionList(query, parent); } + /** + * Construct for copyForPlanKey. + */ + JunctionExpression(boolean disjunction, String joinType, DefaultExpressionList exprList) { + this.disjunction = disjunction; + this.joinType = joinType; + this.exprList = exprList; + } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { @@ -163,6 +188,25 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr return hc; } + @Override + public boolean isSameByPlan(SpiExpression other) { + + if (!(other instanceof JunctionExpression)) { + return false; + } + + JunctionExpression that = (JunctionExpression) other; + return joinType.equals(that.joinType) + && exprList.isSameByPlan(that.exprList); + } + + @Override + public boolean isSameByBind(SpiExpression other) { + JunctionExpression that = (JunctionExpression) other; + return joinType.equals(that.joinType) + && exprList.isSameByBind(that.exprList); + } + @Override public ExpressionList endJunction() { return exprList.endJunction(); 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 cf3e49ae0..56e58092f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java @@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.expression; import com.avaje.ebean.LikeType; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.server.el.ElPropertyValue; @@ -22,6 +23,7 @@ class LikeExpression extends AbstractExpression { this.val = value; } + @Override public void addBindValues(SpiExpressionRequest request) { ElPropertyValue prop = getElProp(request); @@ -35,14 +37,13 @@ class LikeExpression extends AbstractExpression { request.addBindValue(bindValue); } + @Override public void addSql(SpiExpressionRequest request) { - String propertyName = getPropertyName(); - String pname = propertyName; - + String pname = propName; ElPropertyValue prop = getElProp(request); if (prop != null && prop.isDbEncrypted()) { - pname = prop.getBeanProperty().getDecryptProperty(propertyName); + pname = prop.getBeanProperty().getDecryptProperty(propName); } if (caseInsensitive) { request.append("lower(").append(pname).append(")"); @@ -71,6 +72,24 @@ class LikeExpression extends AbstractExpression { return val.hashCode(); } + @Override + public boolean isSameByPlan(SpiExpression other) { + if (!(other instanceof LikeExpression)) { + return false; + } + + LikeExpression that = (LikeExpression) other; + return this.propName.equals(that.propName) + && this.caseInsensitive == that.caseInsensitive + && this.type == that.type; + } + + @Override + public boolean isSameByBind(SpiExpression other) { + LikeExpression that = (LikeExpression) other; + return val.equals(that.val); + } + private static String getValue(String value, boolean caseInsensitive, LikeType type) { if (caseInsensitive) { value = value.toLowerCase(); 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 71d4fe100..977b63d5a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java @@ -26,6 +26,12 @@ abstract class LogicExpression implements SpiExpression { And(Expression expOne, Expression expTwo) { super(AND, expOne, expTwo); } + + @Override + public SpiExpression copyForPlanKey() { + return new And(expOne.copyForPlanKey(), expTwo.copyForPlanKey()); + } + } static class Or extends LogicExpression { @@ -35,11 +41,16 @@ abstract class LogicExpression implements SpiExpression { Or(Expression expOne, Expression expTwo) { super(OR, expOne, expTwo); } + + @Override + public SpiExpression copyForPlanKey() { + return new Or(expOne.copyForPlanKey(), expTwo.copyForPlanKey()); + } } - private final SpiExpression expOne; + protected final SpiExpression expOne; - private final SpiExpression expTwo; + protected final SpiExpression expTwo; private final String joinType; @@ -49,6 +60,7 @@ abstract class LogicExpression implements SpiExpression { this.expTwo = (SpiExpression) expTwo; } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { expOne.containsMany(desc, manyWhereJoin); expTwo.containsMany(desc, manyWhereJoin); @@ -60,11 +72,13 @@ abstract class LogicExpression implements SpiExpression { expTwo.validate(validation); } + @Override public void addBindValues(SpiExpressionRequest request) { expOne.addBindValues(request); expTwo.addBindValues(request); } + @Override public void addSql(SpiExpressionRequest request) { request.append("("); @@ -83,16 +97,38 @@ abstract class LogicExpression implements SpiExpression { /** * Based on the joinType plus the two expressions. */ + @Override public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(LogicExpression.class).add(joinType); expOne.queryPlanHash(builder); expTwo.queryPlanHash(builder); } + @Override public int queryBindHash() { int hc = expOne.queryBindHash(); hc = hc * 31 + expTwo.queryBindHash(); return hc; } + @Override + public boolean isSameByPlan(SpiExpression other) { + + if (!(other instanceof LogicExpression)) { + return false; + } + + LogicExpression that = (LogicExpression) other; + return this.joinType.equals(that.joinType) + && this.expOne.isSameByPlan(that.expOne) + && this.expTwo.isSameByPlan(that.expTwo); + } + + @Override + public boolean isSameByBind(SpiExpression other) { + LogicExpression that = (LogicExpression) other; + return this.expOne.isSameByBind(that.expOne) + && this.expTwo.isSameByBind(that.expTwo); + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NonPrepareExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NonPrepareExpression.java index e72bbf19a..40dc0b66d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/NonPrepareExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NonPrepareExpression.java @@ -12,4 +12,9 @@ abstract class NonPrepareExpression implements SpiExpression { public void prepareExpression(BeanQueryRequest request) { // do nothing } + + @Override + public SpiExpression copyForPlanKey() { + return this; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java index 6fe932fe3..a59ff8fbc 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java @@ -15,6 +15,11 @@ class NoopExpression implements SpiExpression { protected static final NoopExpression INSTANCE = new NoopExpression(); + @Override + public SpiExpression copyForPlanKey() { + return this; + } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins whereManyJoins) { // nothing to do @@ -50,4 +55,14 @@ class NoopExpression implements SpiExpression { public void addBindValues(SpiExpressionRequest request) { // nothing to do } + + @Override + public boolean isSameByPlan(SpiExpression other) { + return other instanceof NoopExpression; + } + + @Override + public boolean isSameByBind(SpiExpression other) { + return true; + } } 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 21c6c2dbe..b7d8867a7 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java @@ -13,7 +13,8 @@ final class NotExpression implements SpiExpression { private static final long serialVersionUID = 5648926732402355781L; - private static final String NOT = "not ("; + private static final String NOT_START = "not ("; + private static final String NOT_END = ") "; private final SpiExpression exp; @@ -21,6 +22,12 @@ final class NotExpression implements SpiExpression { this.exp = (SpiExpression) exp; } + @Override + public SpiExpression copyForPlanKey() { + return new NotExpression(exp.copyForPlanKey()); + } + + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { exp.containsMany(desc, manyWhereJoin); } @@ -30,14 +37,16 @@ final class NotExpression implements SpiExpression { exp.validate(validation); } + @Override public void addBindValues(SpiExpressionRequest request) { exp.addBindValues(request); } + @Override public void addSql(SpiExpressionRequest request) { - request.append(NOT); + request.append(NOT_START); exp.addSql(request); - request.append(") "); + request.append(NOT_END); } @Override @@ -54,8 +63,23 @@ final class NotExpression implements SpiExpression { exp.queryPlanHash(builder); } + @Override public int queryBindHash() { return exp.queryBindHash(); } + @Override + public boolean isSameByPlan(SpiExpression other) { + if (!(other instanceof NotExpression)) { + return false; + } + NotExpression that = (NotExpression) other; + return exp.isSameByPlan(that.exp); + } + + @Override + public boolean isSameByBind(SpiExpression other) { + NotExpression that = (NotExpression) other; + return exp.isSameByBind(that.exp); + } } 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 c84784501..6ad5a58ea 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java @@ -1,7 +1,7 @@ package com.avaje.ebeaninternal.server.expression; -import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.server.el.ElPropertyValue; @@ -28,7 +28,7 @@ class NullExpression extends AbstractExpression { @Override public void addSql(SpiExpressionRequest request) { - String propertyName = getPropertyName(); + String propertyName = propName; String nullExpr = notNull ? " is not null " : " is null "; @@ -41,6 +41,23 @@ class NullExpression extends AbstractExpression { request.append(propertyName).append(nullExpr); } + @Override + public boolean isSameByPlan(SpiExpression other) { + if (!(other instanceof NullExpression)) { + return false; + } + + NullExpression that = (NullExpression) other; + return this.propName.equals(that.propName) + && this.notNull == that.notNull; + } + + @Override + public boolean isSameByBind(SpiExpression other) { + // no bind values so always true + return true; + } + /** * Based on notNull flag and the propertyName. */ 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 7b37424e1..93261f6b2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java @@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.expression; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; @@ -55,4 +56,31 @@ class RawExpression extends NonPrepareExpression { public int queryBindHash() { return sql.hashCode(); } + + @Override + public boolean isSameByPlan(SpiExpression other) { + if (!(other instanceof RawExpression)) { + return false; + } + RawExpression that = (RawExpression) other; + return sql.equals(that.sql); + } + + @Override + public boolean isSameByBind(SpiExpression other) { + if (!(other instanceof RawExpression)) { + return false; + } + + RawExpression that = (RawExpression) other; + if (values.length != that.values.length) { + return false; + } + for (int i = 0; i < values.length; i++) { + if (!Same.sameByValue(values[i], that.values[i])) { + return false; + } + } + return true; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/Same.java b/src/main/java/com/avaje/ebeaninternal/server/expression/Same.java new file mode 100644 index 000000000..5f46e954b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/Same.java @@ -0,0 +1,33 @@ +package com.avaje.ebeaninternal.server.expression; + +/** + * Utility to help isSame methods. + */ +public class Same { + + /** + * Return true if both values are null or both an not null. + */ + public static boolean sameByNull(Object v1, Object v2) { + return v1 == null ? v2 == null : v2 != null; + } + + /** + * Null safe equals check. + */ + public static boolean sameByValue(Object v1, Object v2) { + return v1 == null ? v2 == null : v1.equals(v2); + } + + /** + * Null safe check by sameByValue or sameByNull based on byValue. + */ + public static boolean sameBy(boolean byValue, Object value, Object value1) { + + if (byValue) { + return sameByValue(value, value1); + } else { + return sameByNull(value, value1); + } + } +} 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 20e4cd6f4..0b6a4f9a4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java @@ -1,8 +1,8 @@ package com.avaje.ebeaninternal.server.expression; import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.server.el.ElPropertyValue; @@ -20,17 +20,25 @@ public class SimpleExpression extends AbstractExpression { this.value = value; } + public final String getPropName() { + return propName; + } + public boolean isOpEquals() { return Op.EQ.equals(type); } + public Object getValue() { + return value; + } + @Override public void addBindValues(SpiExpressionRequest request) { ElPropertyValue prop = getElProp(request); if (prop != null) { if (prop.isAssocId()) { - Object[] ids = prop.getAssocOneIdValues((EntityBean)value); + Object[] ids = prop.getAssocOneIdValues((EntityBean) value); if (ids != null) { for (int i = 0; i < ids.length; i++) { request.addBindValue(ids[i]); @@ -54,12 +62,10 @@ public class SimpleExpression extends AbstractExpression { @Override public void addSql(SpiExpressionRequest request) { - String propertyName = getPropertyName(); - ElPropertyValue prop = getElProp(request); if (prop != null) { if (prop.isAssocId()) { - request.append(prop.getAssocOneIdExpr(propertyName, type.bind())); + request.append(prop.getAssocOneIdExpr(propName, type.bind())); return; } if (prop.isDbEncrypted()) { @@ -68,7 +74,7 @@ public class SimpleExpression extends AbstractExpression { return; } } - request.append(propertyName).append(type.bind()); + request.append(propName).append(type.bind()); } /** @@ -85,8 +91,20 @@ public class SimpleExpression extends AbstractExpression { return value.hashCode(); } - public Object getValue() { - return value; + @Override + public boolean isSameByPlan(SpiExpression other) { + if (!(other instanceof SimpleExpression)) { + return false; + } + + SimpleExpression that = (SimpleExpression) other; + return this.propName.equals(that.propName) + && this.type == that.type; } + @Override + public boolean isSameByBind(SpiExpression other) { + SimpleExpression that = (SimpleExpression) other; + return value.equals(that.value); + } } 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 0a54ce7b8..ba084fb19 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java @@ -1,13 +1,8 @@ package com.avaje.ebeaninternal.server.query; -import java.security.MessageDigest; -import java.sql.ResultSet; -import java.sql.SQLException; - import com.avaje.ebean.bean.ObjectGraphNode; import com.avaje.ebean.config.dbplatform.SqlLimitResponse; -import com.avaje.ebeaninternal.api.HashQueryPlan; -import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import com.avaje.ebeaninternal.api.CQueryPlanKey; import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.server.core.OrmQueryRequest; import com.avaje.ebeaninternal.server.deploy.BeanProperty; @@ -18,6 +13,10 @@ import com.avaje.ebeaninternal.server.type.RsetDataReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.security.MessageDigest; +import java.sql.ResultSet; +import java.sql.SQLException; + /** * Represents a query for a given SQL statement. *

@@ -45,7 +44,7 @@ public class CQueryPlan { private final boolean autoTuned; - private final HashQueryPlan hash; + private final CQueryPlanKey planKey; private final boolean rawSql; @@ -79,7 +78,7 @@ public class CQueryPlan { this.server = request.getServer(); this.beanType = request.getBeanDescriptor().getBeanType(); this.stats = new CQueryPlanStats(this, server.isCollectQueryOrigins()); - this.hash = request.getQueryPlanHash(); + this.planKey = request.getQueryPlanKey(); this.autoTuned = request.getQuery().isAutoTuned(); if (sqlRes != null) { this.sql = sqlRes.getSql(); @@ -103,7 +102,7 @@ public class CQueryPlan { this.server = request.getServer(); this.beanType = request.getBeanDescriptor().getBeanType(); this.stats = new CQueryPlanStats(this, server.isCollectQueryOrigins()); - this.hash = buildHash(sql, rawSql, rowNumberIncluded, logWhereSql); + this.planKey = buildPlanKey(sql, rawSql, rowNumberIncluded, logWhereSql); this.autoTuned = false; this.sql = sql; this.sqlTree = sqlTree; @@ -114,15 +113,13 @@ public class CQueryPlan { } - private HashQueryPlan buildHash(String sql, boolean rawSql, boolean rowNumberIncluded, String logWhereSql) { - HashQueryPlanBuilder builder = new HashQueryPlanBuilder(); - builder.add(sql).add(rawSql).add(rowNumberIncluded).add(logWhereSql); - builder.addRawSql(sql); - return builder.build(); + private CQueryPlanKey buildPlanKey(String sql, boolean rawSql, boolean rowNumberIncluded, String logWhereSql) { + + return new RawSqlQueryPlanKey(sql, rawSql, rowNumberIncluded, logWhereSql); } public String toString() { - return beanType + " hash:" + hash; + return beanType + " hash:" + planKey; } public Class getBeanType() { @@ -147,8 +144,8 @@ public class CQueryPlan { return autoTuned; } - public HashQueryPlan getHash() { - return hash; + public CQueryPlanKey getPlanKey() { + return planKey; } /** @@ -164,7 +161,7 @@ public class CQueryPlan { private String calcAuditQueryKey() { // rawSql needs to include the MD5 hash of the sql - return rawSql ? hash.getPartialKey() + "_" + getSqlMd5Hash() : hash.getPartialKey(); + return rawSql ? planKey.getPartialKey() + "_" + getSqlMd5Hash() : planKey.getPartialKey(); } /** 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 31636a05e..6196867ac 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanStats.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanStats.java @@ -233,7 +233,7 @@ public final class CQueryPlanStats { @Override public String getQueryPlanHash() { - return queryPlan.getHash().toString(); + return queryPlan.getPlanKey().toString(); } @Override diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java index 152ac6712..c42ec239f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java @@ -13,7 +13,7 @@ import com.avaje.ebeaninternal.server.persist.Binder; import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; import com.avaje.ebeaninternal.server.type.DataBind; import com.avaje.ebeaninternal.server.util.BindParamsParser; -import com.avaje.ebeaninternal.util.DefaultExpressionRequest; +import com.avaje.ebeaninternal.server.expression.DefaultExpressionRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/RawSqlQueryPlanKey.java b/src/main/java/com/avaje/ebeaninternal/server/query/RawSqlQueryPlanKey.java new file mode 100644 index 000000000..f26301ba3 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/RawSqlQueryPlanKey.java @@ -0,0 +1,56 @@ +package com.avaje.ebeaninternal.server.query; + +import com.avaje.ebeaninternal.api.CQueryPlanKey; + +/** + * QueryPlanKey for RawSql queries. + */ +public class RawSqlQueryPlanKey implements CQueryPlanKey { + + private final String sql; + private final boolean rawSql; + private final boolean rowNumberIncluded; + private final String logWhereSql; + + public RawSqlQueryPlanKey(String sql, boolean rawSql, boolean rowNumberIncluded, String logWhereSql) { + this.sql = sql; + this.rawSql = rawSql; + this.rowNumberIncluded = rowNumberIncluded; + this.logWhereSql = logWhereSql; + } + + public String toString() { + return getPartialKey() + ":r"; + } + + /** + * Return as a partial key. For rawSql hash the sql is part of the key and as such + * needs to be included in order to have a complete key. Typically the MD5 of the sql + * can be used as a short form proxy for the actual sql. + */ + public String getPartialKey() { + return hashCode() + "_0"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + RawSqlQueryPlanKey that = (RawSqlQueryPlanKey) o; + + if (rawSql != that.rawSql) return false; + if (rowNumberIncluded != that.rowNumberIncluded) return false; + if (!sql.equals(that.sql)) return false; + return logWhereSql != null ? logWhereSql.equals(that.logWhereSql) : that.logWhereSql == null; + } + + @Override + public int hashCode() { + int result = sql.hashCode(); + result = 31 * result + (rawSql ? 1 : 0); + result = 31 * result + (rowNumberIncluded ? 1 : 0); + result = 31 * result + logWhereSql.hashCode(); + return result; + } +} 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 7f3dea20d..0c07ea385 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -13,9 +13,8 @@ import com.avaje.ebean.plugin.SpiBeanType; import com.avaje.ebean.text.PathProperties; import com.avaje.ebeaninternal.api.BindParams; import com.avaje.ebeaninternal.api.HashQuery; -import com.avaje.ebeaninternal.api.HashQueryPlan; -import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.CQueryPlanKey; import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionList; import com.avaje.ebeaninternal.api.SpiExpressionValidation; @@ -28,7 +27,7 @@ import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery; import com.avaje.ebeaninternal.server.deploy.TableJoin; import com.avaje.ebeaninternal.server.expression.SimpleExpression; import com.avaje.ebeaninternal.server.query.CancelableQuery; -import com.avaje.ebeaninternal.util.DefaultExpressionList; +import com.avaje.ebeaninternal.server.expression.DefaultExpressionList; import javax.persistence.PersistenceException; import java.sql.Timestamp; @@ -234,7 +233,7 @@ public class DefaultOrmQuery implements SpiQuery { /** * Hash of final query after AutoTune tuning. */ - private HashQueryPlan queryPlanHash; + private CQueryPlanKey queryPlanKey; private transient PersistenceContext persistenceContext; @@ -493,7 +492,7 @@ public class DefaultOrmQuery implements SpiQuery { if (se instanceof SimpleExpression) { SimpleExpression e = (SimpleExpression) se; if (e.isOpEquals()) { - return new NaturalKeyBindParam(e.getPropertyName(), e.getValue()); + return new NaturalKeyBindParam(e.getPropName(), e.getValue()); } } } @@ -759,55 +758,34 @@ public class DefaultOrmQuery implements SpiQuery { /** * Calculate the query hash for either AutoTune query tuning or Query Plan caching. */ - HashQueryPlan calculateQueryPlanHash() { - - // exclude bind values and things unrelated to the sql being generated - HashQueryPlanBuilder builder = new HashQueryPlanBuilder(); - - builder.add((type == null ? 0 : type.ordinal() + 1)); - builder.add(autoTuned).add(distinct).add(sqlDistinct).add(query); - builder.add(firstRow).add(maxRows).add(orderBy).add(forUpdate); - builder.add(rawWhereClause).add(additionalWhere).add(additionalHaving); - builder.add(mapKey); - builder.add(disableLazyLoading); - builder.add(id != null); - builder.add(temporalMode); - builder.add(rawSql == null ? 0 : rawSql.queryHash()); - builder.add(includeTableJoin != null ? includeTableJoin.queryHash() : 0); - builder.add(rootTableAlias); - - if (detail != null) { - detail.queryPlanHash(builder); - } - if (bindParams != null) { - bindParams.buildQueryPlanHash(builder); - } + CQueryPlanKey createQueryPlanKey() { + DefaultExpressionList where = null; + DefaultExpressionList having = null; if (whereExpressions != null) { - whereExpressions.queryPlanHash(builder); + where = whereExpressions.copyForPlanKey(); } if (havingExpressions != null) { - havingExpressions.queryPlanHash(builder); + having = havingExpressions.copyForPlanKey(); } - return builder.build(); + queryPlanKey = new OrmQueryPlanKey(includeTableJoin, type, detail, maxRows, firstRow, + disableLazyLoading, rawWhereClause, orderBy, query, additionalWhere, additionalHaving, + distinct, sqlDistinct, mapKey, id, bindParams, where, having, + temporalMode, forUpdate, rootTableAlias, rawSql); + + return queryPlanKey; } /** - * Calculate a hash that should be unique for the generated SQL across a given bean type. - *

- * This can used to enable the caching and reuse of a 'query plan'. - *

- *

- * This is calculated AFTER AutoTune query tuning has occurred. - *

+ * Prepare the query which prepares any expressions (sub-query expressions etc) and calculates the query plan key. */ - public HashQueryPlan queryPlanHash(BeanQueryRequest request) { + public CQueryPlanKey prepare(BeanQueryRequest request) { prepareExpressions(request); - queryPlanHash = calculateQueryPlanHash(); - return queryPlanHash; + queryPlanKey = createQueryPlanKey(); + return queryPlanKey; } /** @@ -852,7 +830,7 @@ public class DefaultOrmQuery implements SpiQuery { // so queryPlanHash is calculated well before this method is called int hc = queryBindHash(); - return new HashQuery(queryPlanHash, hc); + return new HashQuery(queryPlanKey, hc); } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKey.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKey.java new file mode 100644 index 000000000..7f7f7a41e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKey.java @@ -0,0 +1,149 @@ +package com.avaje.ebeaninternal.server.querydefn; + +import com.avaje.ebean.OrderBy; +import com.avaje.ebean.RawSql; +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.CQueryPlanKey; +import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.deploy.TableJoin; + +/** + * Query plan key for ORM queries. + */ +public class OrmQueryPlanKey implements CQueryPlanKey { + + private final TableJoin includeTableJoin; + private final String orderByAsSting; + private final OrmQueryDetail detail; + private final BindParams bindParams; + private final SpiExpression whereExpressions; + private final SpiExpression havingExpressions; + private final RawSql rawSql; + private final boolean hasIdValue; + + private final SpiQuery.Type type; + private final int maxRows; + private final int firstRow; + private final boolean disableLazyLoading; + private final String rawWhereClause; + private final String query; + private final String additionalWhere; + private final String additionalHaving; + private final boolean distinct; + private final boolean sqlDistinct; + private final String mapKey; + private final SpiQuery.TemporalMode temporalMode; + private final boolean forUpdate; + private final String rootTableAlias; + + private final int planHash; + private final int bindCount; + + public OrmQueryPlanKey(TableJoin includeTableJoin, SpiQuery.Type type, OrmQueryDetail detail, int maxRows, int firstRow, boolean disableLazyLoading, String rawWhereClause, OrderBy orderBy, String query, String additionalWhere, String additionalHaving, boolean distinct, boolean sqlDistinct, String mapKey, Object id, BindParams bindParams, SpiExpression whereExpressions, SpiExpression havingExpressions, SpiQuery.TemporalMode temporalMode, boolean forUpdate, String rootTableAlias, RawSql rawSql) { + + this.includeTableJoin = includeTableJoin; + this.type = type; + this.detail = detail; + this.maxRows = maxRows; + this.firstRow = firstRow; + this.disableLazyLoading = disableLazyLoading; + this.rawWhereClause = rawWhereClause; + this.orderByAsSting = (orderBy == null) ? null : orderBy.toStringFormat(); + this.query = query; + this.additionalWhere = additionalWhere; + this.additionalHaving = additionalHaving; + this.distinct = distinct; + this.sqlDistinct = sqlDistinct; + this.mapKey = mapKey; + this.hasIdValue = (id != null); + this.bindParams = bindParams; + this.whereExpressions = whereExpressions; + this.havingExpressions = havingExpressions; + this.temporalMode = temporalMode; + this.forUpdate = forUpdate; + this.rootTableAlias = rootTableAlias; + this.rawSql = rawSql; + + // exclude bind values and things unrelated to the sql being generated + HashQueryPlanBuilder builder = new HashQueryPlanBuilder(); + + builder.add((type == null ? 0 : type.ordinal() + 1)); + builder.add(distinct).add(sqlDistinct).add(query); + builder.add(firstRow).add(maxRows); + builder.add(orderBy).add(forUpdate); + builder.add(rawWhereClause).add(additionalWhere).add(additionalHaving); + builder.add(mapKey); + builder.add(disableLazyLoading); + builder.add(hasIdValue); + builder.add(temporalMode); + builder.add(rawSql == null ? 0 : rawSql.queryHash()); + builder.add(includeTableJoin != null ? includeTableJoin.queryHash() : 0); + builder.add(rootTableAlias); + + if (detail != null) { + detail.queryPlanHash(builder); + } + if (bindParams != null) { + bindParams.buildQueryPlanHash(builder); + } + if (whereExpressions != null) { + whereExpressions.queryPlanHash(builder); + } + if (havingExpressions != null) { + havingExpressions.queryPlanHash(builder); + } + + this.planHash = builder.getPlanHash(); + this.bindCount = builder.getBindCount(); + } + + @Override + public String getPartialKey() { + return planHash + "_" + bindCount; + } + + @Override + public int hashCode() { + return planHash; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + OrmQueryPlanKey that = (OrmQueryPlanKey) o; + + if (planHash != that.planHash) return false; + if (bindCount != that.bindCount) return false; + + if (maxRows != that.maxRows) return false; + if (firstRow != that.firstRow) return false; + if (disableLazyLoading != that.disableLazyLoading) return false; + + if (distinct != that.distinct) return false; + if (sqlDistinct != that.sqlDistinct) return false; + if (forUpdate != that.forUpdate) return false; + if (hasIdValue != that.hasIdValue) return false; + if (type != that.type) return false; + if (temporalMode != that.temporalMode) return false; + if (includeTableJoin != null ? !includeTableJoin.equals(that.includeTableJoin) : that.includeTableJoin != null) return false; + if (orderByAsSting != null ? !orderByAsSting.equals(that.orderByAsSting) : that.orderByAsSting != null) return false; + + if (whereExpressions != null ? !whereExpressions.isSameByPlan(that.whereExpressions) : that.whereExpressions != null) return false; + if (havingExpressions != null ? !havingExpressions.isSameByPlan(that.havingExpressions) : that.havingExpressions != null) return false; + +// if (detail != null ? !detail.equals(that.detail) : that.detail != null) return false; +// if (bindParams != null ? !bindParams.equals(that.bindParams) : that.bindParams != null) return false; +// if (rawSql != null ? !rawSql.equals(that.rawSql) : that.rawSql != null) return false; + + if (rawWhereClause != null ? !rawWhereClause.equals(that.rawWhereClause) : that.rawWhereClause != null) return false; + if (query != null ? !query.equals(that.query) : that.query != null) return false; + if (additionalWhere != null ? !additionalWhere.equals(that.additionalWhere) : that.additionalWhere != null) return false; + if (additionalHaving != null ? !additionalHaving.equals(that.additionalHaving) : that.additionalHaving != null) return false; + if (mapKey != null ? !mapKey.equals(that.mapKey) : that.mapKey != null) return false; + return rootTableAlias != null ? rootTableAlias.equals(that.rootTableAlias) : that.rootTableAlias == null; + } +} 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 271c1dd68..467c99468 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -12,7 +12,7 @@ import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.server.expression.FilterExprPath; import com.avaje.ebeaninternal.server.lib.util.StringHelper; import com.avaje.ebeaninternal.server.query.SplitName; -import com.avaje.ebeaninternal.util.FilterExpressionList; +import com.avaje.ebeaninternal.server.expression.FilterExpressionList; import java.io.Serializable; import java.util.ArrayList; diff --git a/src/test/java/com/avaje/ebean/BaseTestCase.java b/src/test/java/com/avaje/ebean/BaseTestCase.java index 07bda7c83..cbc438435 100644 --- a/src/test/java/com/avaje/ebean/BaseTestCase.java +++ b/src/test/java/com/avaje/ebean/BaseTestCase.java @@ -1,6 +1,7 @@ package com.avaje.ebean; import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import org.avaje.agentloader.AgentLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,4 +30,16 @@ public class BaseTestCase { SpiEbeanServer spi = (SpiEbeanServer)Ebean.getDefaultServer(); return spi.getDatabasePlatform().getName().equals("h2"); } + + protected BeanDescriptor getBeanDescriptor(Class cls) { + return spiEbeanServer().getBeanDescriptor(cls); + } + + protected SpiEbeanServer spiEbeanServer() { + return (SpiEbeanServer) Ebean.getDefaultServer(); + } + + protected EbeanServer server() { + return Ebean.getDefaultServer(); + } } diff --git a/src/test/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumnTest.java b/src/test/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumnTest.java new file mode 100644 index 000000000..9c9f9679d --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumnTest.java @@ -0,0 +1,49 @@ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoinColumn; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TableJoinColumnTest { + + TableJoinColumn col(String localDbColumn, String foreignDbColumn, boolean insertable, boolean updateable) { + DeployTableJoinColumn column = new DeployTableJoinColumn(localDbColumn, foreignDbColumn, insertable, updateable); + return new TableJoinColumn(column); + } + + @Test + public void equals_when_same() { + assertSame(col("a", "b", true, true), col("a", "b", true, true)); + } + + @Test + public void equals_when_diffFirstCol() { + assertDifferent(col("a", "b", true, true), col("c", "b", true, true)); + } + + @Test + public void equals_when_diffSecondCol() { + assertDifferent(col("a", "b", true, true), col("a", "c", true, true)); + } + + @Test + public void equals_when_diffInsertable() { + assertDifferent(col("a", "b", true, true), col("a", "b", false, true)); + } + + @Test + public void equals_when_diffUpdateable() { + assertDifferent(col("a", "b", true, true), col("a", "b", true, false)); + } + + private void assertDifferent(TableJoinColumn col, TableJoinColumn col2) { + assertThat(col).isNotEqualTo(col2); + assertThat(col.hashCode()).isNotEqualTo(col2.hashCode()); + } + + private void assertSame(TableJoinColumn col, TableJoinColumn col2) { + assertThat(col).isEqualTo(col2); + assertThat(col.hashCode()).isEqualTo(col2.hashCode()); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/deploy/TableJoinTest.java b/src/test/java/com/avaje/ebeaninternal/server/deploy/TableJoinTest.java new file mode 100644 index 000000000..0b0758ca3 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/deploy/TableJoinTest.java @@ -0,0 +1,67 @@ +package com.avaje.ebeaninternal.server.deploy; + + +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoin; +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoinColumn; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TableJoinTest { + + DeployTableJoinColumn col(String localDbColumn, String foreignDbColumn, boolean insertable, boolean updateable) { + return new DeployTableJoinColumn(localDbColumn, foreignDbColumn, insertable, updateable); + } + + TableJoin table(String tableName, String col1, String col2) { + DeployTableJoin deploy = new DeployTableJoin(); + deploy.setTable(tableName); + deploy.addJoinColumn(col(col1, col2, true, true)); + return new TableJoin(deploy); + } + + TableJoin table(String tableName, String col1, String col2, String col3, String col4) { + DeployTableJoin deploy = new DeployTableJoin(); + deploy.setTable(tableName); + deploy.addJoinColumn(col(col1, col2, true, true)); + deploy.addJoinColumn(col(col3, col4, true, true)); + return new TableJoin(deploy); + } + + @Test + public void equals_when_same() { + assertSame(table("myTable", "a", "b"), table("myTable", "a", "b")); + } + + @Test + public void equals_when_diffTable() { + assertDifferent(table("myTable", "a", "b"), table("diffTable", "a", "b")); + } + + @Test + public void equals_when_diffColumn() { + assertDifferent(table("myTable", "a", "b"), table("myTable", "c", "b")); + } + + @Test + public void equals_when_moreColumns() { + assertDifferent(table("myTable", "a", "b"), table("myTable", "a", "b", "c", "d")); + } + + @Test + public void equals_when_lessColumns() { + assertDifferent(table("myTable", "a", "b", "c", "d"), table("myTable", "a", "b")); + } + + void assertSame(TableJoin join1, TableJoin join2) { + assertThat(join1).isEqualTo(join1); + assertThat(join1.hashCode()).isEqualTo(join1.hashCode()); + } + + + void assertDifferent(TableJoin join1, TableJoin join2) { + assertThat(join1).isNotEqualTo(join2); + assertThat(join1.hashCode()).isNotEqualTo(join2.hashCode()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpressionTest.java new file mode 100644 index 000000000..619519225 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpressionTest.java @@ -0,0 +1,118 @@ +package com.avaje.ebeaninternal.server.expression; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class AllEqualsExpressionTest { + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp("a", 10).isSameByPlan(exp("a", 10))).isTrue(); + } + + @Test + public void isSameByBind_when_sameMultiple() { + + assertThat(exp("a", 10, "b", "23").isSameByBind(exp("a", 10, "b", "23"))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBindValue() { + + assertThat(exp("a", 10).isSameByPlan(exp("a", 20))).isTrue(); + } + + @Test + public void isSameByPlan_when_multiple() { + + assertThat(exp("a", 10, "b", 20, "c", 30).isSameByPlan(exp("a", 10, "b", 20, "c", 30))).isTrue(); + } + + @Test + public void isSameByPlan_when_less() { + + assertThat(exp("a", 10, "b", 20, "c", 30).isSameByPlan(exp("a", 10, "b", 20))).isFalse(); + } + + @Test + public void isSameByPlan_when_more() { + + assertThat(exp("a", 10, "b", 20).isSameByPlan(exp("a", 10, "b", 20, "c", 30))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffProperty_diff() { + + assertThat(exp("a", 10).isSameByPlan(exp("b", 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffType_diff() { + + assertThat(exp("a", 10).isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByPlan_when_diffBindByNull_last() { + + assertThat(exp("a", 10).isSameByPlan(exp("a", null))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffBindByNull_first() { + + assertThat(exp("a", null).isSameByPlan(exp("a", 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_differentExpressionType() { + + assertThat(exp("a", null).isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByBind_when_diffBindByNull_last() { + + assertThat(exp("a", 10).isSameByBind(exp("a", null))).isFalse(); + } + + @Test + public void isSameByBind_when_diffBindByNull_first() { + + assertThat(exp("a", null).isSameByBind(exp("a", 10))).isFalse(); + } + + + @NotNull + private AllEqualsExpression exp(Map propMap) { + return new AllEqualsExpression(propMap); + } + + AllEqualsExpression exp(String key0, Object val1) { + LinkedHashMap map = new LinkedHashMap(); + map.put(key0, val1); + return exp(map); + } + + + AllEqualsExpression exp(String key0, Object val1, String key2, Object val2) { + LinkedHashMap map = new LinkedHashMap(); + map.put(key0, val1); + map.put(key2, val2); + return exp(map); + } + + AllEqualsExpression exp(String key0, Object val1, String key2, Object val2, String key3, Object val3) { + LinkedHashMap map = new LinkedHashMap(); + map.put(key0, val1); + map.put(key2, val2); + map.put(key3, val3); + return exp(map); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/BaseExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/BaseExpressionTest.java new file mode 100644 index 000000000..5da0e4876 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/BaseExpressionTest.java @@ -0,0 +1,13 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.tests.model.basic.Order; + +public abstract class BaseExpressionTest extends BaseTestCase { + + protected DefaultExpressionRequest newExpressionRequest() { + BeanDescriptor desc = getBeanDescriptor(Order.class); + return new DefaultExpressionRequest(desc); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/BetweenExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/BetweenExpressionTest.java new file mode 100644 index 000000000..bec8bca44 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/BetweenExpressionTest.java @@ -0,0 +1,69 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebeaninternal.api.SpiExpression; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class BetweenExpressionTest extends BaseExpressionTest { + + @Test + public void addSql() throws Exception { + + DefaultExpressionRequest expReq = newExpressionRequest(); + + BetweenExpression exp = new BetweenExpression("startDate", 1, 2); + exp.addSql(expReq); + + assertThat(expReq.getSql()).isEqualTo("startDate between ? and ? "); + } + + @Test + public void copyForPlanKey_isSameInstance() throws Exception { + + BetweenExpression exp = new BetweenExpression("startDate", 1, 2); + SpiExpression other = exp.copyForPlanKey(); + + assertThat(exp).isSameAs(other); + } + + @Test + public void isSameByPlan_when_properties_match() throws Exception { + + BetweenExpression exp0 = new BetweenExpression("startDate", 1, 2); + BetweenExpression exp1 = new BetweenExpression("startDate", 3, 4); + + assertThat(exp0.isSameByPlan(exp1)).isTrue(); + assertThat(exp1.isSameByPlan(exp0)).isTrue(); + } + + @Test + public void isSameByPlan_when_properties_do_not_match() throws Exception { + + BetweenExpression exp0 = new BetweenExpression("startDate", 1, 2); + BetweenExpression exp1 = new BetweenExpression("endDate", 1, 2); + + assertThat(exp0.isSameByPlan(exp1)).isFalse(); + assertThat(exp1.isSameByPlan(exp0)).isFalse(); + } + + @Test + public void isSameByBind_when_values_do_not_match() throws Exception { + + BetweenExpression exp0 = new BetweenExpression("startDate", 1, 2); + BetweenExpression exp1 = new BetweenExpression("startDate", 1, 3); + + assertThat(exp0.isSameByBind(exp1)).isFalse(); + assertThat(exp1.isSameByBind(exp0)).isFalse(); + } + + @Test + public void isSameByBind_when_values_match() throws Exception { + + BetweenExpression exp0 = new BetweenExpression("startDate", 1, 2); + BetweenExpression exp1 = new BetweenExpression("startDate", 1, 2); + + assertThat(exp0.isSameByBind(exp1)).isTrue(); + assertThat(exp1.isSameByBind(exp0)).isTrue(); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpressionTest.java new file mode 100644 index 000000000..1e08d89d5 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpressionTest.java @@ -0,0 +1,45 @@ +package com.avaje.ebeaninternal.server.expression; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class BetweenPropertyExpressionTest { + + @NotNull + private BetweenPropertyExpression exp(String lowProperty, String highProperty, Object value) { + return new BetweenPropertyExpression(lowProperty, highProperty, value); + } + + @Test + public void isSameByPlan_when_same() { + assertThat(exp("a", "b", 10).isSameByPlan(exp("a", "b", 10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffValue() { + assertThat(exp("a", "b", 10).isSameByPlan(exp("a", "b", 20))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffProperty() { + assertThat(exp("a", "b", 10).isSameByPlan(exp("a", "c", 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffExpressionType() { + assertThat(exp("a", "b", 10).isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByBind_when_same() { + assertThat(exp("a", "b", 10).isSameByBind(exp("a", "b", 10))).isTrue(); + } + + @Test + public void isSameByBind_when_diff() { + assertThat(exp("a", "b", 10).isSameByBind(exp("a", "b", 20))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpressionTest.java new file mode 100644 index 000000000..03d8c388e --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpressionTest.java @@ -0,0 +1,50 @@ +package com.avaje.ebeaninternal.server.expression; + + +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class CaseInsensitiveEqualExpressionTest { + + CaseInsensitiveEqualExpression exp(String propName, String value) { + return new CaseInsensitiveEqualExpression(propName, value); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp("a", "10").isSameByPlan(exp("a", "10"))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_same() { + + assertThat(exp("a", "10").isSameByPlan(exp("a", "20"))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffProperty_diff() { + + assertThat(exp("a", "10").isSameByPlan(exp("b", "10"))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffType_diff() { + + assertThat(exp("a", "10").isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp("a", "10").isSameByBind(exp("b", "10"))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp("a", "10").isSameByBind(exp("b", "20"))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java index 4cdade37c..3c0bc757a 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java @@ -1,13 +1,11 @@ package com.avaje.ebeaninternal.server.expression; import com.avaje.ebean.BaseTestCase; -import com.avaje.ebean.Ebean; import com.avaje.ebean.LikeType; import com.avaje.ebean.Query; import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; -import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.server.core.OrmQueryRequest; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; @@ -22,42 +20,68 @@ import static org.assertj.core.api.Assertions.assertThat; public class DefaultExampleExpressionTest extends BaseTestCase { - @Test - public void test() { - - SpiEbeanServer server = (SpiEbeanServer)Ebean.getDefaultServer(); - BeanDescriptor desc = server.getBeanDescriptor(Customer.class); - - SpiQuery query = (SpiQuery)server.find(Customer.class); - + Customer customer() { Address address = new Address(); address.setCity("billingAddress.city"); Customer customer = new Customer(); customer.setName("name"); customer.setBillingAddress(address); + return customer; + } + DefaultExampleExpression exp() { + Customer customer = customer(); + return new DefaultExampleExpression((EntityBean)customer, false, LikeType.EQUAL_TO); + } - DefaultExampleExpression expr = new DefaultExampleExpression((EntityBean)customer, false, LikeType.EQUAL_TO); + DefaultExampleExpression expExtra() { + Customer customer = customer(); + customer.setSmallnote("smallNote"); + return new DefaultExampleExpression((EntityBean)customer, false, LikeType.EQUAL_TO); + } + DefaultExampleExpression expDiffName() { + Customer customer = customer(); + customer.setName("otherName"); + return new DefaultExampleExpression((EntityBean)customer, false, LikeType.EQUAL_TO); + } - BeanQueryRequest request = create(query, desc); + BeanDescriptor customerBeanDescriptor() { + return getBeanDescriptor(Customer.class); + } + + DefaultExampleExpression prepare(DefaultExampleExpression expr) { + + SpiQuery query = (SpiQuery)spiEbeanServer().find(Customer.class); + BeanQueryRequest request = create(query, customerBeanDescriptor()); expr.prepareExpression(request); + return expr; + } + + @Test + public void test() { + + DefaultExampleExpression expr = exp(); + + prepare(expr); + HashQueryPlanBuilder builder = new HashQueryPlanBuilder(); expr.queryPlanHash(builder); - TDSpiExpressionRequest req = new TDSpiExpressionRequest(desc); + TDSpiExpressionRequest req = new TDSpiExpressionRequest(customerBeanDescriptor()); expr.addBindValues(req); assertThat(req.bindValues).contains("name", "billingAddress.city"); - address.setCity("Auckland"); + Customer customer = customer(); customer.setName("Rob"); + customer.getBillingAddress().setCity("Auckland"); ResetBasicData.reset(); - Query query1 = server.find(Customer.class) + Query query1 = server().find(Customer.class) .where().exampleLike(customer) .query(); @@ -72,4 +96,28 @@ public class DefaultExampleExpressionTest extends BaseTestCase { return new OrmQueryRequest(null, null, query, desc, null); } + @Test + public void isSameByPlan_whenSame() { + + assertThat(prepare(exp()).isSameByPlan(prepare(exp()))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBindValue_stillSame() { + + assertThat(prepare(exp()).isSameByPlan(prepare(expDiffName()))).isTrue(); + } + + @Test + public void isSameByPlan_when_extraExpression_then_different() { + + assertThat(prepare(exp()).isSameByPlan(prepare(expExtra()))).isFalse(); + } + + @Test + public void isSameByPlan_when_lessExpression_then_different() { + + assertThat(prepare(expExtra()).isSameByPlan(prepare(exp()))).isFalse(); + } + } \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionListTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionListTest.java new file mode 100644 index 000000000..a5a688fd8 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionListTest.java @@ -0,0 +1,98 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.ExpressionList; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class DefaultExpressionListTest { + + + DefaultExpressionList exp() { + + return new DefaultExpressionList(null, new DefaultExpressionFactory(true), null); + } + + DefaultExpressionList spi(ExpressionList list) { + return (DefaultExpressionList)list; + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(spi(exp().eq("a", 10).eq("b", 20)) + .isSameByPlan(spi(exp().eq("a", 10).eq("b", 20)))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffExpressionType() { + + assertThat(spi(exp().eq("a", 10).eq("b", 20)) + .isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByPlan_when_less() { + + assertThat(spi(exp().eq("a", 10).eq("b", 20)) + .isSameByPlan(spi(exp().eq("a", 10)))).isFalse(); + } + + @Test + public void isSameByPlan_when_lessEmptyLast() { + + assertThat(spi(exp().eq("a", 10).eq("b", 20)) + .isSameByPlan(spi(exp()))).isFalse(); + } + + @Test + public void isSameByPlan_when_lessEmptyFirst() { + + assertThat(spi(exp()) + .isSameByPlan(spi(exp().eq("a", 10)))).isFalse(); + } + + @Test + public void isSameByPlan_when_more() { + + assertThat(spi(exp().eq("a", 10).eq("b", 20)) + .isSameByPlan(spi(exp().eq("a", 10).eq("b", 20).eq("c", 30)))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffProperties() { + + assertThat(spi(exp().eq("a", 10).eq("b", 20)) + .isSameByPlan(spi(exp().eq("c", 10).eq("b", 20)))).isFalse(); + } + + + @Test + public void isSameByBind_when_same() { + + assertThat(spi(exp().eq("a", 10).eq("b", 20)) + .isSameByBind(spi(exp().eq("a", 10).eq("b", 20)))).isTrue(); + } + + @Test + public void isSameByBind_when_diffValues() { + + assertThat(spi(exp().eq("a", 10).eq("b", 20)) + .isSameByBind(spi(exp().eq("a", 10).eq("b", 30)))).isFalse(); + } + + @Test + public void isSameByBind_when_less() { + + assertThat(spi(exp().eq("a", 10)) + .isSameByBind(spi(exp().eq("a", 10).eq("b", 20)))).isFalse(); + } + + @Test + public void isSameByBind_when_more() { + + assertThat(spi(exp().eq("a", 10)) + .isSameByBind(spi(exp().eq("a", 10).eq("b", 20)))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/ExistsExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/ExistsExpressionTest.java new file mode 100644 index 000000000..b3ff17e81 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/ExistsExpressionTest.java @@ -0,0 +1,85 @@ +package com.avaje.ebeaninternal.server.expression; + + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.util.Arrays; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class ExistsExpressionTest { + + + @NotNull + private ExistsExpression exp(boolean not, String sql, Object... bindValues) { + return new ExistsExpression(not, sql, Arrays.asList(bindValues)); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp(true,"a", 10).isSameByPlan(exp(true,"a", 10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_same() { + + assertThat(exp(true,"a", 10).isSameByPlan(exp(true,"a", 20))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffNot() { + + assertThat(exp(true,"a", 10).isSameByPlan(exp(false,"a", 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffSql() { + + assertThat(exp(true,"a", 10).isSameByPlan(exp(true,"b", 10))).isFalse(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp(true,"a", 10).isSameByBind(exp(true,"a", 10))).isTrue(); + } + + @Test + public void isSameByBind_when_sameMultipleBindValues() { + + assertThat(exp(true,"a", 10, "ABC", 20).isSameByBind(exp(true,"a", 10, "ABC", 20))).isTrue(); + } + + @Test + public void isSameByBind_when_diffMultipleBindValues() { + + assertThat(exp(true,"a", 10, "ABC", 20).isSameByBind(exp(true,"a", 10, "ABC", 21))).isFalse(); + } + + @Test + public void isSameByBind_when_diffMultipleBindValuesByOrder() { + + assertThat(exp(true,"a", 10, "ABC", 20).isSameByBind(exp(true,"a", 10, 20, "ABC"))).isFalse(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp(true,"a", 10).isSameByBind(exp(true,"a", 20))).isFalse(); + } + + @Test + public void isSameByBind_when_lessBindValues() { + + assertThat(exp(true,"a", 10, 20).isSameByBind(exp(true,"a", 20))).isFalse(); + } + + @Test + public void isSameByBind_when_moreBindValues() { + + assertThat(exp(true,"a", 10).isSameByBind(exp(true,"a", 10, 20))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/IdExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/IdExpressionTest.java new file mode 100644 index 000000000..e4836e0b9 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/IdExpressionTest.java @@ -0,0 +1,41 @@ +package com.avaje.ebeaninternal.server.expression; + + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class IdExpressionTest { + + + @NotNull + private IdExpression exp(Object value) { + return new IdExpression(value); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp(10).isSameByPlan(exp(10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_same() { + + assertThat(exp(10).isSameByPlan(exp(20))).isTrue(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp(10).isSameByBind(exp(10))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp(10).isSameByBind(exp(20))).isFalse(); + assertThat(exp(10).isSameByBind(exp("junk"))).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/IdInExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/IdInExpressionTest.java new file mode 100644 index 000000000..b1b2ab7b2 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/IdInExpressionTest.java @@ -0,0 +1,67 @@ +package com.avaje.ebeaninternal.server.expression; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.util.Arrays; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class IdInExpressionTest { + + + @NotNull + private IdInExpression exp(Object... values) { + return new IdInExpression(Arrays.asList(values)); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp(10).isSameByPlan(exp(10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_same() { + + assertThat(exp(10).isSameByPlan(exp(20))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBindCount() { + + assertThat(exp(10).isSameByPlan(exp(10, 20))).isFalse(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp(10).isSameByBind(exp(10))).isTrue(); + } + + @Test + public void isSameByBind_when_mulitpleSameBindValues() { + + assertThat(exp(10,"ABC", 20).isSameByBind(exp(10, "ABC", 20))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp(10).isSameByBind(exp("junk"))).isFalse(); + } + + @Test + public void isSameByBind_when_lessBindValues() { + + assertThat(exp(10,"ABC", 20).isSameByBind(exp(10, "ABC"))).isFalse(); + } + + + @Test + public void isSameByBind_when_moreBindValues() { + + assertThat(exp(10,"ABC").isSameByBind(exp(10, "ABC", 30))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/InExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/InExpressionTest.java index 6930d0942..b26b09917 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/expression/InExpressionTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/InExpressionTest.java @@ -1,12 +1,16 @@ package com.avaje.ebeaninternal.server.expression; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import static org.junit.Assert.*; +import static org.assertj.core.api.StrictAssertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; public class InExpressionTest { @@ -87,4 +91,70 @@ public class InExpressionTest { } return list; } + + @NotNull + private InExpression exp(String propName, boolean not, Object... values) { + return new InExpression(propName, Arrays.asList(values), not); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp("a", false, 10).isSameByPlan(exp("a", false, 10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffPropertyName() { + + assertThat(exp("a", false, 10).isSameByPlan(exp("b", false, 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffNot() { + + assertThat(exp("a", false, 10).isSameByPlan(exp("a", true, 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffBind_same() { + + assertThat(exp("a", false, 10).isSameByPlan(exp("a", false, 10, 20))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffBindCount() { + + assertThat(exp("a", false, 10).isSameByPlan(exp("a", false, 10, 20))).isFalse(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp("a", false, 10).isSameByBind(exp("a", false, 10))).isTrue(); + } + + @Test + public void isSameByBind_when_sameMultipleBindValues() { + + assertThat(exp("a", false, 10, "ABC", 20).isSameByBind(exp("a", false, 10, "ABC", 20))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp("a", false, 10).isSameByBind(exp("a", false, "foo"))).isFalse(); + } + + @Test + public void isSameByBind_when_lessBindValues() { + + assertThat(exp("a", false, 10, "ABC", 20).isSameByBind(exp("a", false, 10, "ABC"))).isFalse(); + } + + @Test + public void isSameByBind_when_moreBindValues() { + + assertThat(exp("a", false, 10, "ABC").isSameByBind(exp("a", false, 10, "ABC", 30))).isFalse(); + } + } \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/InQueryExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/InQueryExpressionTest.java new file mode 100644 index 000000000..c1e15dedf --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/InQueryExpressionTest.java @@ -0,0 +1,89 @@ +package com.avaje.ebeaninternal.server.expression; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.util.Arrays; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class InQueryExpressionTest { + + + @NotNull + private InQueryExpression exp(String propertyName, boolean not, String sql, Object... bindValues) { + return new InQueryExpression(propertyName, not, sql, Arrays.asList(bindValues)); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp("name",true,"sql", 10).isSameByPlan(exp("name",true,"sql", 10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_same() { + + assertThat(exp("name",true,"sql", 10).isSameByPlan(exp("name",true,"sql", 20))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffNPropertyName() { + + assertThat(exp("name",true,"sql", 10).isSameByPlan(exp("nameDiff",true,"sql", 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffNot() { + + assertThat(exp("name",true,"sql", 10).isSameByPlan(exp("name",false,"sql", 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffSql() { + + assertThat(exp("name",true,"sql", 10).isSameByPlan(exp("name",true,"sqlDiff", 10))).isFalse(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp("name",true,"sql", 10).isSameByBind(exp("name",true,"sql", 10))).isTrue(); + } + + @Test + public void isSameByBind_when_sameMultipleBindValues() { + + assertThat(exp("name",true,"sql", 10, "ABC", 20).isSameByBind(exp("name",true,"sql", 10, "ABC", 20))).isTrue(); + } + + @Test + public void isSameByBind_when_diffMultipleBindValues() { + + assertThat(exp("name",true,"sql", 10, "ABC", 20).isSameByBind(exp("name",true,"sql", 10, "ABC", 21))).isFalse(); + } + + @Test + public void isSameByBind_when_diffMultipleBindValuesByOrder() { + + assertThat(exp("name",true,"sql", 10, "ABC", 20).isSameByBind(exp("name",true,"sql", 10, 20, "ABC"))).isFalse(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp("name",true,"sql", 10).isSameByBind(exp("name",true,"sql", 20))).isFalse(); + } + + @Test + public void isSameByBind_when_lessBindValues() { + + assertThat(exp("name",true,"sql", 10, 20).isSameByBind(exp("name",true,"sql", 20))).isFalse(); + } + + @Test + public void isSameByBind_when_moreBindValues() { + + assertThat(exp("name",true,"sql", 10).isSameByBind(exp("name",true,"sql", 10, 20))).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/JsonPathExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/JsonPathExpressionTest.java new file mode 100644 index 000000000..5dda9c091 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/JsonPathExpressionTest.java @@ -0,0 +1,81 @@ +package com.avaje.ebeaninternal.server.expression; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class JsonPathExpressionTest { + + @NotNull + private JsonPathExpression exp(String propertyName, String path, Op operator, Object value) { + return new JsonPathExpression(propertyName, path, operator, value); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByPlan(exp("a", "path", Op.EQ, 10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_same() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByPlan(exp("a", "path", Op.EQ, 20))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffPath() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByPlan(exp("a", "pathDiff", Op.EQ, 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffProperty_diff() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByPlan(exp("b", "path", Op.EQ, 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffOperator_diff() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByPlan(exp("a", "path", Op.LT, 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffType_diff() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByBind(exp("a", "path", Op.LT, 10))).isTrue(); + } + + @Test + public void isSameByBind_when_sameNullBindValues() { + + assertThat(exp("a", "path", Op.EQ, null).isSameByBind(exp("a", "path", Op.LT, null))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByBind(exp("a", "path", Op.EQ, "junk"))).isFalse(); + } + + @Test + public void isSameByBind_when_diffFirstNullBindValues() { + + assertThat(exp("a", "path", Op.EQ, null).isSameByBind(exp("a", "path", Op.LT, 10))).isFalse(); + } + + @Test + public void isSameByBind_when_diffLastNullBindValues() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByBind(exp("a", "path", Op.LT, null))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/JunctionExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/JunctionExpressionTest.java new file mode 100644 index 000000000..a4bd1c166 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/JunctionExpressionTest.java @@ -0,0 +1,60 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.Expr; +import com.avaje.ebean.Expression; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class JunctionExpressionTest { + + Expression eq(String propName, int value) { + return Expr.eq(propName, value); + } + + + DefaultExpressionList exp(Expression... expressions) { + + DefaultExpressionList list = new DefaultExpressionList(null, new DefaultExpressionFactory(true), null); + for (Expression ex : expressions) { + list.add(ex); + } + return list; + } + + JunctionExpression and(DefaultExpressionList list) { + return new JunctionExpression.Conjunction(list); + } + + JunctionExpression or(DefaultExpressionList list) { + return new JunctionExpression.Disjunction(list); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(and(exp(eq("a", 10), eq("b", 10))) + .isSameByPlan(and(exp(eq("a", 10), eq("b", 10))))).isTrue(); + } + + @Test + public void copyForPlanKey_isSameByPlan_when_same() { + + assertThat(and(exp(eq("a", 10), eq("b", 10)).copyForPlanKey()) + .isSameByPlan(and(exp(eq("a", 10), eq("b", 10))))).isTrue(); + } + + @Test + public void copyForPlanKey_isSameByPlan_when_diff() { + + assertThat(and(exp(eq("a", 10), eq("b", 10)).copyForPlanKey()) + .isSameByPlan(and(exp(eq("a", 10), eq("c", 10))))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffType() { + + assertThat(and(exp(eq("a", 10), eq("b", 10))) + .isSameByPlan(or(exp(eq("a", 10), eq("b", 10))))).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/LikeExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/LikeExpressionTest.java new file mode 100644 index 000000000..85a2aef8f --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/LikeExpressionTest.java @@ -0,0 +1,66 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.LikeType; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class LikeExpressionTest extends BaseExpressionTest { + + + @NotNull + private LikeExpression exp(String propertyName, String value, boolean caseInsensitive, LikeType type) { + return new LikeExpression(propertyName, value, caseInsensitive, type); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp("a", "rob", true, LikeType.STARTS_WITH) + .isSameByPlan(exp("a", "rob", true, LikeType.STARTS_WITH))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_then_stillSame() { + + assertThat(exp("a", "rob", true, LikeType.STARTS_WITH) + .isSameByPlan(exp("a", "bor", true, LikeType.STARTS_WITH))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffCaseInsensitive() { + + assertThat(exp("a", "rob", true, LikeType.STARTS_WITH) + .isSameByPlan(exp("a", "rob", false, LikeType.STARTS_WITH))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffLikeType() { + + assertThat(exp("a", "rob", true, LikeType.STARTS_WITH) + .isSameByPlan(exp("a", "rob", true, LikeType.ENDS_WITH))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffProperty() { + + assertThat(exp("a", "rob", true, LikeType.STARTS_WITH) + .isSameByPlan(exp("b", "rob", true, LikeType.STARTS_WITH))).isFalse(); + } + + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp("a", "rob", true, LikeType.STARTS_WITH) + .isSameByBind(exp("a", "rob", true, LikeType.STARTS_WITH))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp("a", "rob", true, LikeType.STARTS_WITH) + .isSameByBind(exp("a", "bor", true, LikeType.STARTS_WITH))).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/LogicExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/LogicExpressionTest.java new file mode 100644 index 000000000..742a6ebd0 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/LogicExpressionTest.java @@ -0,0 +1,78 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.Expr; +import com.avaje.ebean.Expression; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LogicExpressionTest extends BaseExpressionTest { + + Expression eq(String propName, int value) { + return Expr.eq(propName, value); + } + + LogicExpression and(Expression a, Expression b) { + return new LogicExpression.And(a, b); + } + + LogicExpression or(Expression a, Expression b) { + return new LogicExpression.Or(a, b); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(and(eq("a", 10), eq("b", 10)) + .isSameByPlan(and(eq("a", 10), eq("b", 10)))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_then_stillSame() { + + assertThat(and(eq("a", 10), eq("b", 10)) + .isSameByPlan(and(eq("a", 20), eq("b", 20)))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffExp1_then_diff() { + + assertThat(and(eq("a", 10), eq("b", 10)) + .isSameByPlan(and(eq("c", 10), eq("b", 10)))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffExp2_then_diff() { + + assertThat(and(eq("a", 10), eq("b", 10)) + .isSameByPlan(and(eq("a", 10), eq("c", 10)))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffType_then_diff() { + + assertThat(or(eq("a", 10), eq("b", 10)) + .isSameByPlan(and(eq("a", 10), eq("c", 10)))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffExpressionType() { + + assertThat(or(eq("a", 10), eq("b", 10)) + .isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(and(eq("a", 10), eq("b", 10)) + .isSameByBind(and(eq("a", 10), eq("c", 10)))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(and(eq("a", 10), eq("b", 10)) + .isSameByBind(and(eq("a", 10), eq("c", 20)))).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/NoopExpressionQueryTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/NoopExpressionTest.java similarity index 64% rename from src/test/java/com/avaje/ebeaninternal/server/expression/NoopExpressionQueryTest.java rename to src/test/java/com/avaje/ebeaninternal/server/expression/NoopExpressionTest.java index 9a42ea729..2f473c454 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/expression/NoopExpressionQueryTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/NoopExpressionTest.java @@ -8,7 +8,8 @@ import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; -public class NoopExpressionQueryTest extends BaseTestCase { + +public class NoopExpressionTest extends BaseTestCase { @Test public void test() { @@ -39,4 +40,29 @@ public class NoopExpressionQueryTest extends BaseTestCase { assertThat(generatedSql).contains("select t0.id c0 from o_customer t0 where t0.name is null and 1=1 and t0.status is not null"); } + + @Test + public void isSameByPlan_when_same() { + + assertThat(new NoopExpression().isSameByPlan(new NoopExpression())).isTrue(); + } + + @Test + public void isSameByPlan_when_diffExpressionType() { + + assertThat(new NoopExpression().isSameByPlan(null)).isFalse(); + } + + @Test + public void isSameByBind_when_same() { + + assertThat(new NoopExpression().isSameByBind(new NoopExpression())).isTrue(); + } + + @Test + public void isSameByBind_when_diffExpressionType() { + + assertThat(new NoopExpression().isSameByBind(null)).isTrue(); + } + } diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/NotExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/NotExpressionTest.java new file mode 100644 index 000000000..4bb5abe33 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/NotExpressionTest.java @@ -0,0 +1,58 @@ +package com.avaje.ebeaninternal.server.expression; + + +import com.avaje.ebean.Expression; +import org.junit.Test; + +import static com.avaje.ebean.Expr.eq; +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class NotExpressionTest { + + + NotExpression not(Expression expression) { + return new NotExpression(expression); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(not(eq("a", 10)) + .isSameByPlan(not(eq("a", 10)))).isTrue(); + } + + @Test + public void isSameByPlan_when_sameByPlan() { + + assertThat(not(eq("a", 10)) + .isSameByPlan(not(eq("a", 20)))).isTrue(); + } + + @Test + public void isSameByPlan_when_different() { + + assertThat(not(eq("a", 10)) + .isSameByPlan(not(eq("b", 10)))).isFalse(); + } + + @Test + public void isSameByPlan_when_differentExpressionType() { + + assertThat(not(eq("a", 10)) + .isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByBind_when_same() { + + assertThat(not(eq("a", 10)) + .isSameByBind(not(eq("b", 10)))).isTrue(); + } + + @Test + public void isSameByBind_when_different() { + + assertThat(not(eq("a", 10)) + .isSameByBind(not(eq("a", 20)))).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/NullExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/NullExpressionTest.java new file mode 100644 index 000000000..ec2187d14 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/NullExpressionTest.java @@ -0,0 +1,91 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebeaninternal.api.SpiExpression; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class NullExpressionTest extends BaseExpressionTest { + + NullExpression nullExp(String propertyName, boolean notNull) { + return new NullExpression(propertyName, notNull); + } + + @Test + public void addSql_when_notNull() throws Exception { + + DefaultExpressionRequest expReq = newExpressionRequest(); + + nullExp("id", true).addSql(expReq); + + assertThat(expReq.getSql()).isEqualTo("id is not null "); + } + + @Test + public void addSql_when_null() throws Exception { + + DefaultExpressionRequest expReq = newExpressionRequest(); + + nullExp("id", false).addSql(expReq); + + assertThat(expReq.getSql()).isEqualTo("id is null "); + } + + @Test + public void addSql_when_notNull_and_assocOne() throws Exception { + + DefaultExpressionRequest expReq = newExpressionRequest(); + + nullExp("customer", true).addSql(expReq); + + assertThat(expReq.getSql()).isEqualTo("customer.id is not null "); + } + + @Test + public void addSql_when_null_and_assocOne() throws Exception { + + DefaultExpressionRequest expReq = newExpressionRequest(); + + nullExp("customer", false).addSql(expReq); + + assertThat(expReq.getSql()).isEqualTo("customer.id is null "); + } + + @Test + public void copyForPlanKey_isSameInstance() throws Exception { + + NullExpression exp = nullExp("customer.name", false); + SpiExpression other = exp.copyForPlanKey(); + + assertThat(exp).isSameAs(other); + } + + @Test + public void isSameByBind_true() throws Exception { + + assertThat(nullExp("customer.name", false) + .isSameByBind(nullExp("customer.name", false))).isTrue(); + } + + @Test + public void isSameByPlan_true() throws Exception { + + assertThat(nullExp("customer.name", false) + .isSameByPlan(nullExp("customer.name", false))).isTrue(); + } + + @Test + public void isSameByPlan_false_when_notNullDiff() throws Exception { + + assertThat(new NullExpression("customer.name", false) + .isSameByPlan(new NullExpression("customer.name", true))).isFalse(); + } + + @Test + public void isSameByPlan_false_when_propertyNameDiff() throws Exception { + + assertThat(new NullExpression("customer.startDate", true) + .isSameByPlan(new NullExpression("customer.name", true))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/RawExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/RawExpressionTest.java new file mode 100644 index 000000000..f5382dc6d --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/RawExpressionTest.java @@ -0,0 +1,50 @@ +package com.avaje.ebeaninternal.server.expression; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class RawExpressionTest { + + @NotNull + private RawExpression exp(String sql, Object... values) { + return new RawExpression(sql, values); + } + + @Test + public void isSameByPlan_when_same() { + assertThat(exp("a", 10).isSameByPlan(exp("a", 10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBindValues() { + assertThat(exp("a", 10).isSameByPlan(exp("a", 20))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffSql() { + assertThat(exp("a", 10).isSameByPlan(exp("b", 10))).isFalse(); + } + + @Test + public void isSameByBind_when_same() { + assertThat(exp("a", 10).isSameByBind(exp("a", 10))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + assertThat(exp("a", 10).isSameByBind(exp("a", 20))).isFalse(); + } + + @Test + public void isSameByBind_when_moreBindValues() { + assertThat(exp("a", 10).isSameByBind(exp("a", 10, 20))).isFalse(); + } + + @Test + public void isSameByBind_when_lessBindValues() { + assertThat(exp("a", 10, 20).isSameByBind(exp("a", 10))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/SameTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/SameTest.java new file mode 100644 index 000000000..35c9afed0 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/SameTest.java @@ -0,0 +1,29 @@ +package com.avaje.ebeaninternal.server.expression; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class SameTest { + + @Test + public void sameByNull() throws Exception { + + assertThat(Same.sameByNull("a", "a")).isTrue(); + assertThat(Same.sameByNull("a", "b")).isTrue(); + assertThat(Same.sameByNull(null, null)).isTrue(); + assertThat(Same.sameByNull("a", null)).isFalse(); + assertThat(Same.sameByNull(null, "a")).isFalse(); + } + + @Test + public void sameByValue() throws Exception { + + assertThat(Same.sameByValue("a", "a")).isTrue(); + assertThat(Same.sameByValue("a", "b")).isFalse(); + assertThat(Same.sameByValue(null, null)).isTrue(); + assertThat(Same.sameByValue("a", null)).isFalse(); + assertThat(Same.sameByValue(null, "a")).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/SimpleExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/SimpleExpressionTest.java new file mode 100644 index 000000000..b6fd1be47 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/SimpleExpressionTest.java @@ -0,0 +1,58 @@ +package com.avaje.ebeaninternal.server.expression; + + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SimpleExpressionTest extends BaseExpressionTest { + + @NotNull + private SimpleExpression exp(String propertyName, Op operator, Object value) { + return new SimpleExpression(propertyName, operator, value); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp("a", Op.EQ, 10).isSameByPlan(exp("a", Op.EQ, 10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_same() { + + assertThat(exp("a", Op.EQ, 10).isSameByPlan(exp("a", Op.EQ, 20))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffProperty_diff() { + + assertThat(exp("a", Op.EQ, 10).isSameByPlan(exp("b", Op.EQ, 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffOperator_diff() { + + assertThat(exp("a", Op.EQ, 10).isSameByPlan(exp("a", Op.LT, 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffType_diff() { + + assertThat(exp("a", Op.EQ, 10).isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp("a", Op.EQ, 10).isSameByBind(exp("a", Op.LT, 10))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp("a", Op.EQ, 10).isSameByBind(exp("a", Op.EQ, "junk"))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/query/RawSqlQueryPlanKeyTest.java b/src/test/java/com/avaje/ebeaninternal/server/query/RawSqlQueryPlanKeyTest.java new file mode 100644 index 000000000..2d8c10083 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/query/RawSqlQueryPlanKeyTest.java @@ -0,0 +1,61 @@ +package com.avaje.ebeaninternal.server.query; + + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RawSqlQueryPlanKeyTest { + + + @Test + public void test_equals_same_instance() { + + RawSqlQueryPlanKey key = key("select foo", true, true, ""); + assertThat(key).isEqualTo(key); + assertThat(key.hashCode()).isEqualTo(key.hashCode()); + } + + @Test + public void test_equals_diff_instance() { + + assertThat(key("select foo", true, true, "")).isEqualTo(key("select foo", true, true, "")); + assertThat(key("select foo", true, true, "").hashCode()).isEqualTo(key("select foo", true, true, "").hashCode()); + } + + @Test + public void test_notEquals_diff_sql() { + + assertThat(key("select foo", true, true, "")).isNotEqualTo(key("select bar", true, true, "")); + assertThat(key("select foo", true, true, "").hashCode()).isNotEqualTo(key("select bar", true, true, "").hashCode()); + } + + @Test + public void test_notEquals_diff_rawSqlFlag() { + + assertThat(key("select foo", true, true, "")).isNotEqualTo(key("select foo", false, true, "")); + assertThat(key("select foo", true, true, "").hashCode()).isNotEqualTo(key("select foo", false, true, "").hashCode()); + } + + @Test + public void test_notEquals_diff_rowNumberIncluded() { + + assertThat(key("select foo", true, true, "")).isNotEqualTo(key("select foo", true, false, "")); + assertThat(key("select foo", true, true, "").hashCode()).isNotEqualTo(key("select foo", true, false, "").hashCode()); + } + + + @Test + public void test_notEquals_diff_logWhereSql() { + + assertThat(key("select foo", true, true, "")).isNotEqualTo(key("select foo", true, true, "a")); + assertThat(key("select foo", true, true, "").hashCode()).isNotEqualTo(key("select foo", true, true, "a").hashCode()); + } + + @NotNull + private RawSqlQueryPlanKey key(String sql, boolean rawSql, boolean rowNumberIncluded, String logWhereSql) { + return new RawSqlQueryPlanKey(sql, rawSql, rowNumberIncluded, logWhereSql); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java index e3c5dbf9a..50b4f558a 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java @@ -16,7 +16,7 @@ public class DefaultOrmQueryTest { DefaultOrmQuery q1 = (DefaultOrmQuery)Ebean.find(Order.class).where().in("name", "a","b","c").query(); DefaultOrmQuery q2 = (DefaultOrmQuery)Ebean.find(Order.class).where().in("id", 2,2,3).query(); - assertThat(q1.calculateQueryPlanHash()).isNotEqualTo(q2.calculateQueryPlanHash()); + assertThat(q1.createQueryPlanKey()).isNotEqualTo(q2.createQueryPlanKey()); assertThat(q1.queryBindHash()).isNotEqualTo(q2.queryBindHash()); } @@ -26,7 +26,7 @@ public class DefaultOrmQueryTest { DefaultOrmQuery q1 = (DefaultOrmQuery)Ebean.find(Order.class).where().in("id", 1,2,3).query(); DefaultOrmQuery q2 = (DefaultOrmQuery)Ebean.find(Order.class).where().in("id", 2,2,3).query(); - assertThat(q1.calculateQueryPlanHash()).isEqualTo(q2.calculateQueryPlanHash()); + assertThat(q1.createQueryPlanKey()).isEqualTo(q2.createQueryPlanKey()); assertThat(q1.queryBindHash()).isNotEqualTo(q2.queryBindHash()); } @@ -36,7 +36,7 @@ public class DefaultOrmQueryTest { DefaultOrmQuery q1 = (DefaultOrmQuery)Ebean.find(Order.class).where().in("id", 1,2,3).query(); DefaultOrmQuery q2 = (DefaultOrmQuery)Ebean.find(Order.class).where().in("id", 1,2,3).query(); - assertThat(q1.calculateQueryPlanHash()).isEqualTo(q2.calculateQueryPlanHash()); + assertThat(q1.createQueryPlanKey()).isEqualTo(q2.createQueryPlanKey()); assertThat(q1.queryBindHash()).isEqualTo(q2.queryBindHash()); } @@ -55,7 +55,7 @@ public class DefaultOrmQueryTest { .fetch("details", "orderQty, unitPrice") .fetch("details.product", "sku, name"); - assertThat(query1.calculateQueryPlanHash()).isNotEqualTo(query2.calculateQueryPlanHash()); + assertThat(query1.createQueryPlanKey()).isNotEqualTo(query2.createQueryPlanKey()); } @Test @@ -69,7 +69,7 @@ public class DefaultOrmQueryTest { .setFirstRow(1) .setMaxRows(0); - assertThat(query1.calculateQueryPlanHash()).isNotEqualTo(query2.calculateQueryPlanHash()); + assertThat(query1.createQueryPlanKey()).isNotEqualTo(query2.createQueryPlanKey()); } } \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java new file mode 100644 index 000000000..f6cd5df9b --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java @@ -0,0 +1,432 @@ +package com.avaje.ebeaninternal.server.querydefn; + + +import com.avaje.ebean.OrderBy; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionList; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.deploy.TableJoin; +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoin; +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoinColumn; +import com.avaje.ebeaninternal.server.expression.BaseExpressionTest; +import com.avaje.tests.model.basic.Customer; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OrmQueryPlanKeyTest extends BaseExpressionTest { + + + @Test + public void equals_when_defaults() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertSame(key1, key2); + } + + @Test + public void equals_when_diffTableJoinNull() { + + TableJoin tableJoin = tableJoin("id", "customer_id"); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffTableJoin() { + + TableJoin tableJoin1 = tableJoin("id", "customer_id"); + TableJoin tableJoin2 = tableJoin("id", "other_customer_id"); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin1, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(tableJoin2, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_sameTableJoin() { + + TableJoin tableJoin1 = tableJoin("id", "customer_id"); + TableJoin tableJoin2 = tableJoin("id", "customer_id"); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin1, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(tableJoin2, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertSame(key1, key2); + } + + @NotNull + private TableJoin tableJoin(String col1, String col2) { + DeployTableJoin deploy = new DeployTableJoin(); + deploy.setTable("myTable"); + deploy.addJoinColumn(new DeployTableJoinColumn(col1, col2)); + return new TableJoin(deploy); + } + + @Test + public void equals_when_diffQueryType() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.LIST, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_firstRowsDifferent() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 10, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_maxRowsDifferent() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_firstRowsMaxRowsSame() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 20, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 20, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertSame(key1, key2); + } + + @Test + public void equals_when_diffDisableLazyLoading() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, true, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffRawWhereNull() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, "rawWhere", null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffRawWhere() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, "rawWhere", null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, "rawDiff", null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffOrderByNull() { + + OrderBy o1 = new OrderBy("id"); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, o1, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_orderBySame() { + + OrderBy o1 = new OrderBy("id, name"); + OrderBy o2 = new OrderBy("id, name"); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, o1, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, o2, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertSame(key1, key2); + } + + @Test + public void equals_when_diffQueryNull() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, "query", null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + + @Test + public void equals_when_diffQuery() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, "query", null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, "queryDiff", null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_querySame() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, "query", null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, "query", null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + @Test + public void equals_when_diffAddWhereNull() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, "addWhere", null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffAddWhere() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, "addWhere", null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, "diff", null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_sameAddWhere() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, "addWhere", null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, "addWhere", null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + @Test + public void equals_when_diffAddHavingNull() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, "addHaving", false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffAddHaving() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, "addHaving", false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, "diff", false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_sameAddHaving() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, "addHaving", false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, "addHaving", false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + + @Test + public void equals_when_diffDistinct() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_sameDistinct() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + @Test + public void equals_when_diffSqlDistinct() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_sameSqlDistinct() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + @Test + public void equals_when_diffMapKeyNull() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffMapKey() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, "diff", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_sameMapKey() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + @Test + public void equals_when_diffIdNull() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, 42, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_idBothGiven() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, 42, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, 23, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + @Test + public void equals_when_diffTemporalMode() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.DRAFT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffForUpdate() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, true, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffRootAliasNull() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "rootAlias", null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffRootAlias() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "rootAlias", null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "diff", null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_sameRootAlias() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "rootAlias", null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "rootAlias", null); + assertSame(key1, key2); + } + + SpiExpressionList list_id_eq_42() { + return (SpiExpressionList) server().find(Customer.class) + .where().eq("id", 42); + } + + SpiExpressionList list_id_eq_43() { + return (SpiExpressionList) server().find(Customer.class) + .where().eq("id", 43); + } + + SpiExpressionList list_id_eq_42_and_name_eq_rob() { + return (SpiExpressionList) server().find(Customer.class) + .where().eq("id", 43).eq("name", "rob"); + } + + + @Test + public void equals_when_sameWhere() { + + + SpiExpressionList list1 = list_id_eq_42(); + SpiExpressionList list2 = list_id_eq_43(); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, list2, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + + @Test + public void equals_when_diffWhere() { + + SpiExpressionList where1 = list_id_eq_42(); + SpiExpressionList where2 = list_id_eq_42_and_name_eq_rob(); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, where1, null, SpiQuery.TemporalMode.DRAFT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, where2, null, SpiQuery.TemporalMode.DRAFT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffWhereNullLast() { + + SpiExpressionList list1 = list_id_eq_42(); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffWhereNullFirst() { + + SpiExpressionList list1 = list_id_eq_42(); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffHaving() { + + SpiExpression having1 = list_id_eq_42().copyForPlanKey(); + SpiExpression having2 = list_id_eq_42_and_name_eq_rob().copyForPlanKey(); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, having2, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_sameHaving() { + + SpiExpression having1 = list_id_eq_42().copyForPlanKey(); + SpiExpression having2 = list_id_eq_42().copyForPlanKey(); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, having2, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + @Test + public void equals_when_havingNullLast() { + + SpiExpression having1 = list_id_eq_42().copyForPlanKey(); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + @Test + public void equals_when_havingNullFirst() { + + SpiExpression having1 = list_id_eq_42().copyForPlanKey(); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + private void assertDifferent(OrmQueryPlanKey key1, OrmQueryPlanKey key2) { + assertThat(key1).isNotEqualTo(key2); + assertThat(key1.hashCode()).isNotEqualTo(key2.hashCode()); + } + + private void assertSame(OrmQueryPlanKey key1, OrmQueryPlanKey key2) { + assertThat(key1).isEqualTo(key2); + assertThat(key1.hashCode()).isEqualTo(key2.hashCode()); + } +} \ No newline at end of file