diff --git a/src/main/java/com/avaje/ebean/DRawSqlParser.java b/src/main/java/com/avaje/ebean/DRawSqlParser.java index 4353403d7..33622f356 100644 --- a/src/main/java/com/avaje/ebean/DRawSqlParser.java +++ b/src/main/java/com/avaje/ebean/DRawSqlParser.java @@ -71,7 +71,7 @@ class DRawSqlParser { preFrom = trimSelectKeyword(preFrom); - return new Sql(sql.hashCode(), preFrom, preWhere, whereExprAnd, preHaving, havingExprAnd, orderByPrefix, orderBySql, (distinctPos > -1)); + return new Sql(sql, preFrom, preWhere, whereExprAnd, preHaving, havingExprAnd, orderByPrefix, orderBySql, (distinctPos > -1)); } /** diff --git a/src/main/java/com/avaje/ebean/FetchConfig.java b/src/main/java/com/avaje/ebean/FetchConfig.java index b885ce3ff..bf2904531 100644 --- a/src/main/java/com/avaje/ebean/FetchConfig.java +++ b/src/main/java/com/avaje/ebean/FetchConfig.java @@ -249,4 +249,22 @@ public class FetchConfig implements Serializable { return queryAll; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FetchConfig that = (FetchConfig) o; + if (lazyBatchSize != that.lazyBatchSize) return false; + if (queryBatchSize != that.queryBatchSize) return false; + return queryAll == that.queryAll; + } + + @Override + public int hashCode() { + int result = lazyBatchSize; + result = 92821 * result + queryBatchSize; + result = 92821 * result + (queryAll ? 1 : 0); + return result; + } } diff --git a/src/main/java/com/avaje/ebean/RawSql.java b/src/main/java/com/avaje/ebean/RawSql.java index 51760d84f..048532af9 100644 --- a/src/main/java/com/avaje/ebean/RawSql.java +++ b/src/main/java/com/avaje/ebean/RawSql.java @@ -206,6 +206,14 @@ public final class RawSql implements Serializable { return sql; } + /** + * Return the key; + */ + public Key getKey() { + boolean parsed = sql != null && sql.parsed; + String unParsedSql = (sql == null) ? "" : sql.unparsedSql; + return new Key(parsed, unParsedSql, columnMapping); + } /** * Return the resultSet if this is a ResultSet based RawSql. @@ -221,16 +229,6 @@ public final class RawSql implements Serializable { return columnMapping; } - /** - * Return the hash for this query. - */ - public int queryHash() { - if (resultSet != null) { - return 31 * columnMapping.queryHash(); - } - return 31 * sql.queryHash() + columnMapping.queryHash(); - } - /** * Represents the sql part of the query. For parsed RawSql the sql is broken * up so that Ebean can insert extra WHERE and HAVING expressions into the @@ -260,13 +258,10 @@ public final class RawSql implements Serializable { private final boolean distinct; - private final int queryHashCode; - /** * Construct for unparsed SQL. */ protected Sql(String unparsedSql) { - this.queryHashCode = unparsedSql.hashCode(); this.parsed = false; this.unparsedSql = unparsedSql; this.preFrom = null; @@ -282,12 +277,11 @@ public final class RawSql implements Serializable { /** * Construct for parsed SQL. */ - protected Sql(int queryHashCode, String preFrom, String preWhere, boolean andWhereExpr, + protected Sql(String unparsedSql, String preFrom, String preWhere, boolean andWhereExpr, String preHaving, boolean andHavingExpr, String orderByPrefix, String orderBy, boolean distinct) { - this.queryHashCode = queryHashCode; + this.unparsedSql = unparsedSql; this.parsed = true; - this.unparsedSql = null; this.preFrom = preFrom; this.preHaving = preHaving; this.preWhere = preWhere; @@ -298,13 +292,6 @@ public final class RawSql implements Serializable { this.distinct = distinct; } - /** - * Return a hash for this query. - */ - public int queryHash() { - return queryHashCode; - } - public String toString() { if (!parsed) { return "unparsed[" + unparsedSql + "]"; @@ -406,13 +393,10 @@ public final class RawSql implements Serializable { private final boolean immutable; - private final int queryHashCode; - /** * Construct from parsed sql where the columns have been identified. */ protected ColumnMapping(List columns) { - this.queryHashCode = 0; this.immutable = false; this.parsed = true; this.propertyMap = null; @@ -428,7 +412,6 @@ public final class RawSql implements Serializable { * Construct for unparsed sql. */ protected ColumnMapping() { - this.queryHashCode = 0; this.immutable = false; this.parsed = false; this.propertyMap = null; @@ -443,17 +426,13 @@ public final class RawSql implements Serializable { this.immutable = false; this.parsed = false; this.propertyMap = null; - //this.propertyColumnMap = null; this.dbColumnMap = new LinkedHashMap(); - - int hc = 31; + int pos = 0; for (String prop : propertyNames) { - hc = 31 * hc + prop.hashCode(); dbColumnMap.put(prop, new Column(pos++, prop, null, prop)); } propertyColumnMap = dbColumnMap; - this.queryHashCode = hc; } /** @@ -464,20 +443,28 @@ public final class RawSql implements Serializable { this.parsed = parsed; this.dbColumnMap = dbColumnMap; - int hc = ColumnMapping.class.getName().hashCode(); - HashMap pcMap = new HashMap(); HashMap pMap = new HashMap(); for (Column c : dbColumnMap.values()) { pMap.put(c.getPropertyName(), c.getDbColumn()); pcMap.put(c.getPropertyName(), c); - hc = 31 * hc + ((c.getPropertyName() == null) ? 0 : c.getPropertyName().hashCode()); - hc = 31 * hc + ((c.getDbColumn() == null) ? 0 : c.getDbColumn().hashCode()); } this.propertyMap = Collections.unmodifiableMap(pMap); this.propertyColumnMap = Collections.unmodifiableMap(pcMap); - this.queryHashCode = hc; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ColumnMapping that = (ColumnMapping) o; + return dbColumnMap.equals(that.dbColumnMap); + } + + @Override + public int hashCode() { + return dbColumnMap.hashCode(); } /** @@ -521,16 +508,6 @@ public final class RawSql implements Serializable { } } - /** - * Return the query hash for this column mapping. - */ - public int queryHash() { - if (queryHashCode == 0) { - throw new RuntimeException("Bug: queryHashCode == 0"); - } - return queryHashCode; - } - /** * Returns true if the Columns where supplied by parsing the sql select * clause. @@ -648,6 +625,27 @@ public final class RawSql implements Serializable { } } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Column that = (Column) o; + if (indexPos != that.indexPos) return false; + if (!dbColumn.equals(that.dbColumn)) return false; + if (dbAlias != null ? !dbAlias.equals(that.dbAlias) : that.dbAlias != null) return false; + return propertyName != null ? propertyName.equals(that.propertyName) : that.propertyName == null; + } + + @Override + public int hashCode() { + int result = indexPos; + result = 31 * result + dbColumn.hashCode(); + result = 31 * result + (dbAlias != null ? dbAlias.hashCode() : 0); + result = 31 * result + (propertyName != null ? propertyName.hashCode() : 0); + return result; + } + public String toString() { return dbColumn + "->" + propertyName; } @@ -699,4 +697,39 @@ public final class RawSql implements Serializable { } } } + + /** + * A key for the RawSql object using for the query plan. + */ + public static final class Key { + + private final boolean parsed; + private final ColumnMapping columnMapping; + private final String unParsedSql; + + Key(boolean parsed, String unParsedSql, ColumnMapping columnMapping) { + this.parsed = parsed; + this.unParsedSql = unParsedSql; + this.columnMapping = columnMapping; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Key that = (Key) o; + return parsed == that.parsed + && columnMapping.equals(that.columnMapping) + && unParsedSql.equals(that.unParsedSql); + } + + @Override + public int hashCode() { + int result = (parsed ? 1 : 0); + result = 31 * result + columnMapping.hashCode(); + result = 31 * result + unParsedSql.hashCode(); + return result; + } + } } diff --git a/src/main/java/com/avaje/ebean/text/PathProperties.java b/src/main/java/com/avaje/ebean/text/PathProperties.java index ad0d96f6a..5dac251b4 100644 --- a/src/main/java/com/avaje/ebean/text/PathProperties.java +++ b/src/main/java/com/avaje/ebean/text/PathProperties.java @@ -92,7 +92,7 @@ public class PathProperties { /** * Get the properties for a given path. */ - public Set get(String path) { + public LinkedHashSet get(String path) { Props props = pathMap.get(path); return props == null ? null : props.getProperties(); } @@ -109,7 +109,7 @@ public class PathProperties { /** * Set the properties for a given path. */ - public void put(String path, Set properties) { + public void put(String path, LinkedHashSet properties) { pathMap.put(path, new Props(this, null, path, properties)); } @@ -160,9 +160,9 @@ public class PathProperties { private final String parentPath; private final String path; - private final Set propSet; + private final LinkedHashSet propSet; - private Props(PathProperties owner, String parentPath, String path, Set propSet) { + private Props(PathProperties owner, String parentPath, String path, LinkedHashSet propSet) { this.owner = owner; this.path = path; this.parentPath = parentPath; @@ -195,7 +195,7 @@ public class PathProperties { /** * Return the properties for this property set. */ - public Set getProperties() { + public LinkedHashSet getProperties() { return propSet; } 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..c1f2ad696 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/HashQueryPlanBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/api/HashQueryPlanBuilder.java @@ -1,5 +1,7 @@ package com.avaje.ebeaninternal.api; +import java.util.Set; + /** * Used to build HashQueryPlan instances. */ @@ -9,14 +11,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; } /** @@ -35,6 +35,21 @@ public class HashQueryPlanBuilder { return this; } + /** + * Add the set with order being important. + */ + public HashQueryPlanBuilder addOrdered(Set set) { + if (set == null) { + add(false); + } else { + add(true); + for (Object o : set) { + add(o); + } + } + return this; + } + /** * Add an integer to the hash calculation. */ @@ -58,19 +73,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/LoadContext.java b/src/main/java/com/avaje/ebeaninternal/api/LoadContext.java index 3a29927e2..c22199e9b 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/LoadContext.java +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadContext.java @@ -5,6 +5,9 @@ import com.avaje.ebean.bean.EntityBeanIntercept; import com.avaje.ebean.bean.ObjectGraphNode; import com.avaje.ebean.bean.PersistenceContext; import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; + +import java.util.List; /** * Controls the loading of reference objects for a query instance. @@ -22,17 +25,7 @@ public interface LoadContext { */ void executeSecondaryQueries(OrmQueryRequest parentRequest); - /** - * Register any secondary queries (+query or +lazy) with their - * appropriate LoadBeanContext or LoadManyContext. - *

- * This is so the LoadBeanContext or LoadManyContext use the - * defined query for +query and +lazy execution. - *

- */ - void registerSecondaryQueries(SpiQuery query); - - /** + /** * Return the node for a given path which is used by AutoTune profiling. */ ObjectGraphNode getObjectGraphNode(String path); diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java index 7e8b76d9b..24830422f 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java @@ -13,11 +13,8 @@ import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.config.dbplatform.DatabasePlatform; import com.avaje.ebean.event.readaudit.ReadAuditLogger; import com.avaje.ebean.event.readaudit.ReadAuditPrepare; -import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest; -import com.avaje.ebean.dbmigration.DdlGenerator; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.query.CQuery; -import com.avaje.ebeaninternal.server.query.CQueryEngine; import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent; import java.util.List; @@ -130,22 +127,11 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL */ void remoteTransactionEvent(RemoteTransactionEvent event); - /** - * Create a query request object. - */ - SpiOrmQueryRequest createQueryRequest(BeanDescriptor desc, SpiQuery q, - Transaction t); - /** * Compile a query. */ CQuery compileQuery(Query query, Transaction t); - /** - * Return the queryEngine for this server. - */ - CQueryEngine getQueryEngine(); - /** * Execute the findId's query but without copying the query. *

diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java index 28cdf61c5..bacd508de 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java @@ -17,32 +17,36 @@ public interface SpiExpression extends Expression { *

*/ void containsMany(BeanDescriptor desc, ManyWhereJoins whereManyJoins); - - /** + + /** + * Prepare the expression. For example, compile sub-query expressions etc. + */ + void prepareExpression(BeanQueryRequest request); + + /** * Calculate a hash value used to identify a query for AutoTune tuning. *

* That is, if the hash changes then the query will be considered different * from an AutoTune perspective and get different tuning. *

*/ - void queryAutoTuneHash(HashQueryPlanBuilder builder); - - /** - * Calculate a hash value for the expression. - * This includes the expression type and property but should exclude - * the bind values. - *

- * This is used where queries are the same except for the bind values, in which - * case the query execution plan can be reused. - *

- */ - void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder); - + void queryPlanHash(HashQueryPlanBuilder builder); + /** * Return the hash value for the values that will be bound. */ 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. *

@@ -61,7 +65,7 @@ public interface SpiExpression extends Expression { /** * Add the parameter values to be set against query. For each ? place holder * there should be a corresponding value that is added to the bindList. - * + * * @param request * the associated request. */ @@ -71,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 1cacf0a45..de075203c 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java @@ -1,17 +1,13 @@ package com.avaje.ebeaninternal.api; -import java.util.ArrayList; -import java.util.List; - -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. @@ -23,52 +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. - */ - ArrayList buildBindValues(SpiExpressionRequest request); - - /** - * Calculate a hash based on the expressions but excluding the actual bind - * values. - */ - void queryPlanHash(BeanQueryRequest request, 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/SpiExpressionRequest.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java index 913b6ac73..98069d29e 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java @@ -1,11 +1,11 @@ package com.avaje.ebeaninternal.api; -import java.util.ArrayList; - import com.avaje.ebeaninternal.server.core.JsonExpressionHandler; import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import java.util.List; + /** * Request object used for gathering expression sql and bind values. */ @@ -54,7 +54,7 @@ public interface SpiExpressionRequest { /** * Return the ordered list of bind values for all expressions in this request. */ - ArrayList getBindValues(); + List getBindValues(); /** * Increments the parameter index and returns that value. diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java index b96a08987..69831f8f3 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -289,11 +289,6 @@ public interface SpiQuery extends Query { */ void setBeanDescriptor(BeanDescriptor desc); - /** - * Initialise/determine the joins required to support 'many' where clause predicates. - */ - boolean initManyWhereJoins(); - /** * Return the joins required to support predicates on the many properties. */ @@ -319,31 +314,15 @@ public interface SpiQuery extends Query { */ void setFilterMany(String prop, ExpressionList filterMany); - /** - * Remove the query joins from query detail. - *

- * These are registered with the Load Context. - *

- */ - List removeQueryJoins(); - - /** - * Remove the lazy joins from query detail. - *

- * These are registered with the Load Context. - *

- */ - List removeLazyJoins(); - /** * Set the path of the many when +query/+lazy loading query is executed. */ void setLazyLoadManyPath(String lazyLoadManyPath); /** - * Convert any many joins fetch joins to query joins. + * Convert joins as necessary to query joins etc. */ - void convertManyFetchJoinsToQueryJoins(boolean allowOne, int queryBatch); + SpiQuerySecondary convertJoins(); /** * Return the TransactionContext. @@ -467,27 +446,13 @@ public interface SpiQuery extends Query { String getName(); /** - * Calculate a hash used by AutoTune to identify when a query has changed - * (and hence potentially needs a new tuned query plan to be developed). + * Prepare the query which prepares sub-query expressions and calculates + * and returns the query plan key. *

- * Excludes bind values and occurs prior to AutoTune potentially - * tuning/modifying the query. + * The query plan excludes actual bind values (as they don't effect the query plan). *

*/ - HashQueryPlan queryAutoTuneHash(HashQueryPlanBuilder builder); - - /** - * Identifies queries that are the same bar the bind variables. - *

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

- */ - 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/api/SpiQuerySecondary.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuerySecondary.java new file mode 100644 index 000000000..172ea4270 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuerySecondary.java @@ -0,0 +1,21 @@ +package com.avaje.ebeaninternal.api; + +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; + +import java.util.List; + +/** + * The secondary query paths for 'query joins' and 'lazy loading'. + */ +public interface SpiQuerySecondary { + + /** + * Return a list of path/properties that are query join loaded. + */ + List getQueryJoins(); + + /** + * Return the list of path/properties that are lazy loaded. + */ + List getLazyJoins(); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autotune/service/ProfileOrigin.java b/src/main/java/com/avaje/ebeaninternal/server/autotune/service/ProfileOrigin.java index 626285149..c89a9db87 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/autotune/service/ProfileOrigin.java +++ b/src/main/java/com/avaje/ebeaninternal/server/autotune/service/ProfileOrigin.java @@ -110,7 +110,7 @@ public class ProfileOrigin { Collection pathProperties = pathProps.getPathProps(); for (Props props : pathProperties) { if (!props.isEmpty()) { - detail.addFetch(props.getPath(), props.getPropertiesAsString(), null); + detail.fetch(props.getPath(), props.getPropertiesAsString(), null); } } 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..eba6fd89d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -15,7 +15,6 @@ import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.config.dbplatform.DatabasePlatform; import com.avaje.ebean.dbmigration.DdlGenerator; import com.avaje.ebean.event.BeanPersistController; -import com.avaje.ebean.event.BeanQueryAdapter; import com.avaje.ebean.event.readaudit.ReadAuditLogger; import com.avaje.ebean.event.readaudit.ReadAuditPrepare; import com.avaje.ebean.meta.MetaInfoManager; @@ -32,7 +31,6 @@ import com.avaje.ebeaninternal.api.SpiBackgroundExecutor; import com.avaje.ebeaninternal.api.SpiEbeanPlugin; import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.api.SpiQuery.Mode; import com.avaje.ebeaninternal.api.SpiQuery.Type; import com.avaje.ebeaninternal.api.SpiSqlQuery; import com.avaje.ebeaninternal.api.SpiTransaction; @@ -463,19 +461,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return cqueryEngine.buildQuery(orm); } - public CQueryEngine getQueryEngine() { - return cqueryEngine; - } - public ServerCacheManager getServerCacheManager() { return serverCacheManager; } - public void refreshMany(Object parentBean, String propertyName, Transaction t) { - - beanLoader.refreshMany(checkEntityBean(parentBean), propertyName, t); - } - public void refreshMany(Object parentBean, String propertyName) { beanLoader.refreshMany(checkEntityBean(parentBean), propertyName); @@ -1048,7 +1037,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return findId(query, t); } - private SpiOrmQueryRequest createQueryRequest(Type type, Query query, Transaction t) { + SpiOrmQueryRequest createQueryRequest(Type type, Query query, Transaction t) { SpiQuery spiQuery = (SpiQuery) query; spiQuery.setType(type); @@ -1059,7 +1048,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return createQueryRequest(desc, spiQuery, t); } - public SpiOrmQueryRequest createQueryRequest(BeanDescriptor desc, SpiQuery query, Transaction t) { + private SpiOrmQueryRequest createQueryRequest(BeanDescriptor desc, SpiQuery query, Transaction t) { if (desc.isAutoTunable() && !query.isSqlSelect() && !autoTuneService.tuneQuery(query)) { // use deployment FetchType.LAZY/EAGER annotations @@ -1067,50 +1056,15 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { query.setDefaultSelectClause(); } - if (query.selectAllForLazyLoadProperty()) { - // we need to select all properties to ensure the lazy load property - // was included (was not included by default or via autoTune). - if (logger.isDebugEnabled()) { - logger.debug("Using selectAllForLazyLoadProperty"); - } - } + query.selectAllForLazyLoadProperty(); // if determine cost and no origin for AutoTune if (query.getParentNode() == null) { query.setOrigin(createCallStack()); } - // determine extra joins required to support where clause - // predicates on *ToMany properties - if (query.initManyWhereJoins()) { - // we need a sql distinct now - query.setSqlDistinct(true); - } - - boolean allowOneManyFetch = true; - if (Mode.LAZYLOAD_MANY.equals(query.getMode())) { - allowOneManyFetch = false; - - } else if (query.hasMaxRowsOrFirstRow() && !query.isRawSql() && !query.isSqlSelect()) { - // convert ALL fetch joins to Many's to be query joins - // so that limit offset type SQL clauses work - allowOneManyFetch = false; - } - - query.convertManyFetchJoinsToQueryJoins(allowOneManyFetch, queryBatchSize); - - SpiTransaction serverTrans = (SpiTransaction) t; - OrmQueryRequest request = new OrmQueryRequest(this, queryEngine, query, desc, serverTrans); - - BeanQueryAdapter queryAdapter = desc.getQueryAdapter(); - if (queryAdapter != null) { - // adaption of the query probably based on the - // current user - queryAdapter.preQuery(request); - } - - // the query hash after any tuning - request.calculateQueryPlanHash(); + OrmQueryRequest request = new OrmQueryRequest(this, queryEngine, query, desc, (SpiTransaction) t); + 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..76ea1aaae 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java @@ -1,26 +1,25 @@ 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; import com.avaje.ebean.event.BeanFindController; +import com.avaje.ebean.event.BeanQueryAdapter; import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.BeanIdList; +import com.avaje.ebeaninternal.api.CQueryPlanKey; import com.avaje.ebeaninternal.api.HashQuery; -import com.avaje.ebeaninternal.api.HashQueryPlan; import com.avaje.ebeaninternal.api.LoadContext; import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.api.SpiQuery.Type; +import com.avaje.ebeaninternal.api.SpiQuerySecondary; import com.avaje.ebeaninternal.api.SpiTransaction; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanProperty; @@ -32,6 +31,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 +61,9 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe private HashQuery cacheKey; - private HashQueryPlan queryPlanHash; + private CQueryPlanKey queryPlanKey; + + private SpiQuerySecondary secondaryQueries; /** * Create the InternalQueryRequest. @@ -126,10 +134,24 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe } /** - * Calculate the query plan hash AFTER any potential AutoTune tuning. + * Run BeanQueryAdapter preQuery() if needed. */ - public void calculateQueryPlanHash() { - this.queryPlanHash = query.queryPlanHash(this); + private void adapterPreQuery() { + BeanQueryAdapter queryAdapter = beanDescriptor.getQueryAdapter(); + if (queryAdapter != null) { + queryAdapter.preQuery(this); + } + } + + /** + * Prepare the query and calculate the query plan key. + */ + public void prepareQuery() { + + adapterPreQuery(); + + this.secondaryQueries = query.convertJoins(); + this.queryPlanKey = query.prepare(this); } public boolean isRawSql() { @@ -190,8 +212,7 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe } // initialise the persistenceContext and loadContext this.persistenceContext = getPersistenceContext(query, transaction); - this.loadContext = new DLoadContext(this); - this.loadContext.registerSecondaryQueries(query); + this.loadContext = new DLoadContext(this, secondaryQueries); } /** @@ -215,7 +236,7 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe // determine the scope (from the query and then server) PersistenceContextScope scope = ebeanServer.getPersistenceContextScope(query); - return (scope == PersistenceContextScope.QUERY) ? new DefaultPersistenceContext() : t.getPersistenceContext(); + return (scope == PersistenceContextScope.QUERY) ? new DefaultPersistenceContext() : t.getPersistenceContext(); } /** @@ -305,7 +326,7 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe */ @SuppressWarnings("unchecked") public Set findSet() { - return (Set)queryEngine.findMany(this); + return (Set) queryEngine.findMany(this); } /** @@ -324,10 +345,6 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe return (Map) queryEngine.findMany(this); } - public SpiQuery.Type getQueryType() { - return query.getType(); - } - /** * Return a bean specific finder if one has been set. */ @@ -355,7 +372,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 +383,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 +418,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..2e14bd300 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; @@ -73,9 +73,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** @@ -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(); @@ -347,7 +347,7 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType { private final BeanDescriptorJsonHelp jsonHelp; private final String defaultSelectClause; - private final Set defaultSelectClauseSet; + private final LinkedHashSet defaultSelectClauseSet; private SpiEbeanServer ebeanServer; @@ -856,7 +856,7 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType { /** * Return the default select clause already parsed into an ordered Set. */ - public Set getDefaultSelectClauseSet() { + public LinkedHashSet getDefaultSelectClauseSet() { return defaultSelectClauseSet; } @@ -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); } @@ -1252,7 +1252,7 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType { OrmQueryDetail detail = query.getDetail(); for (int i = 0; i < propertiesMany.length; i++) { - if (detail.includes(propertiesMany[i].getName())) { + if (detail.includesPath(propertiesMany[i].getName())) { return propertiesMany[i]; } } 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 68e0d5474..3d0a48a65 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/IntersectionRow.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/IntersectionRow.java @@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.deploy; import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import com.avaje.ebean.EbeanServer; @@ -9,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 { @@ -98,7 +99,7 @@ public class IntersectionRow { sb.append(er.getSql()); sb.append(" ) "); - ArrayList bindValues = er.getBindValues(); + List bindValues = er.getBindValues(); for (int i = 0; i < bindValues.size(); i++) { bindParams.setParameter(++count, bindValues.get(i)); } 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/deploy/meta/DeployBeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java index d8d630781..e8564a6e0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java @@ -33,7 +33,6 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Set; /** * Describes Beans including their deployment information. @@ -763,7 +762,7 @@ public class DeployBeanDescriptor { /** * Parse the include separating by comma or semicolon. */ - public Set parseDefaultSelectClause(String rawList) { + public LinkedHashSet parseDefaultSelectClause(String rawList) { if (rawList == null) { return null; @@ -780,7 +779,7 @@ public class DeployBeanDescriptor { set.add(temp); } } - return Collections.unmodifiableSet(set); + return set; } /** 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 fd4e8c926..d471a8d57 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/AbstractExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/AbstractExpression.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.expression; +import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.ManyWhereJoins; import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; @@ -21,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 @@ -44,13 +46,17 @@ public abstract class AbstractExpression implements SpiExpression { } @Override - public void validate(SpiExpressionValidation validation) { - validation.validate(getPropertyName()); + public void prepareExpression(BeanQueryRequest request) { + // do nothing } - protected ElPropertyValue getElProp(SpiExpressionRequest request) { + @Override + public void validate(SpiExpressionValidation validation) { + validation.validate(propName); + } - String propertyName = getPropertyName(); - return request.getBeanDescriptor().getElGetValue(propertyName); + protected final ElPropertyValue getElProp(SpiExpressionRequest request) { + + 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 8f4edfdca..f7a372b98 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java @@ -1,9 +1,5 @@ package com.avaje.ebeaninternal.server.expression; -import java.util.Map; -import java.util.Map.Entry; - -import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.ManyWhereJoins; import com.avaje.ebeaninternal.api.SpiExpression; @@ -12,7 +8,11 @@ import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; -class AllEqualsExpression implements SpiExpression { +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +class AllEqualsExpression extends NonPrepareExpression { private static final long serialVersionUID = -8691773558205937025L; @@ -26,6 +26,7 @@ class AllEqualsExpression implements SpiExpression { return propName; } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { if (propMap != null) { for (String propertyName : propMap.keySet()) { @@ -44,6 +45,7 @@ class AllEqualsExpression implements SpiExpression { } } + @Override public void addBindValues(SpiExpressionRequest request) { if (propMap.isEmpty()) { @@ -57,6 +59,7 @@ class AllEqualsExpression implements SpiExpression { } } + @Override public void addSql(SpiExpressionRequest request) { if (propMap.isEmpty()) { @@ -92,7 +95,8 @@ class AllEqualsExpression implements SpiExpression { * The null check is required due to the "is null" sql being generated. *

*/ - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(AllEqualsExpression.class); @@ -100,14 +104,11 @@ class AllEqualsExpression implements SpiExpression { 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); } } - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - queryAutoTuneHash(builder); - } - + @Override public int queryBindHash() { int hc = 31; @@ -117,4 +118,48 @@ class AllEqualsExpression implements SpiExpression { 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 d47c4f594..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; @@ -21,28 +20,45 @@ class BetweenExpression extends AbstractExpression { this.valueHigh = valHigh; } + @Override public void addBindValues(SpiExpressionRequest request) { request.addBindValue(valueLow); request.addBindValue(valueHigh); } + @Override public void addSql(SpiExpressionRequest request) { - request.append(getPropertyName()).append(BETWEEN).append(" ? and ? "); + request.append(propName).append(BETWEEN).append(" ? and ? "); } - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(BetweenExpression.class).add(propName); builder.bind(2); } - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - queryAutoTuneHash(builder); - } - + @Override public int queryBindHash() { int hc = valueLow.hashCode(); 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 c8f8193e1..06fdba136 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java @@ -1,6 +1,5 @@ package com.avaje.ebeaninternal.server.expression; -import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.ManyWhereJoins; import com.avaje.ebeaninternal.api.SpiExpression; @@ -12,7 +11,7 @@ import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; /** * Between expression where a value is between two properties. */ -class BetweenPropertyExpression implements SpiExpression { +class BetweenPropertyExpression extends NonPrepareExpression { private static final long serialVersionUID = 2078918165221454910L; @@ -32,6 +31,7 @@ class BetweenPropertyExpression implements SpiExpression { return propName; } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { ElPropertyDeploy elProp = desc.getElPropertyDeploy(name(lowProperty)); @@ -51,25 +51,42 @@ class BetweenPropertyExpression implements SpiExpression { validation.validate(highProperty); } + @Override public void addBindValues(SpiExpressionRequest request) { request.addBindValue(value); } + @Override public void addSql(SpiExpressionRequest request) { request.append(" ? ").append(BETWEEN).append(name(lowProperty)).append(" and ").append(name(highProperty)); } - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(BetweenPropertyExpression.class).add(lowProperty).add(highProperty); builder.bind(1); } - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - queryAutoTuneHash(builder); - } - + @Override 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 3fd9f08e5..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,29 +29,42 @@ 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(") =? "); } - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(CaseInsensitiveEqualExpression.class).add(propName); builder.bind(1); } - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - queryAutoTuneHash(builder); - } - + @Override 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 c21867998..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,20 @@ 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) { for (int i = 0; i < list.size(); i++) { @@ -94,31 +108,37 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio } } + @Override public ExampleExpression includeZeros() { includeZeros = true; return this; } + @Override public ExampleExpression caseInsensitive() { caseInsensitive = true; return this; } + @Override public ExampleExpression useStartsWith() { likeType = LikeType.STARTS_WITH; return this; } + @Override public ExampleExpression useContains() { likeType = LikeType.CONTAINS; return this; } + @Override public ExampleExpression useEndsWith() { likeType = LikeType.ENDS_WITH; return this; } + @Override public ExampleExpression useEqualTo() { likeType = LikeType.EQUAL_TO; return this; @@ -134,6 +154,7 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio /** * Adds bind values to the request. */ + @Override public void addBindValues(SpiExpressionRequest request) { for (int i = 0; i < list.size(); i++) { @@ -145,6 +166,7 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio /** * Generates and adds the sql to the request. */ + @Override public void addSql(SpiExpressionRequest request) { if (!list.isEmpty()) { @@ -162,34 +184,27 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio } } - /** - * Return a hash for AutoTune query identification. - */ - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { - // we have not yet built the list of expressions - // so just based on the class name - builder.add(DefaultExampleExpression.class); + @Override + public void prepareExpression(BeanQueryRequest request) { + list = buildExpressions(request); } /** - * Return a hash for query plan identification. + * Return a hash for AutoTune query identification. */ - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - - // this is always called once, and always called before - // addSql() and addBindValues() methods - list = buildExpressions(request); + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(DefaultExampleExpression.class); - for (int i = 0; i < list.size(); i++) { - list.get(i).queryPlanHash(request, builder); + list.get(i).queryPlanHash(builder); } } /** * Return a hash for the actual bind values used. */ + @Override public int queryBindHash() { int hc = DefaultExampleExpression.class.getName().hashCode(); for (int i = 0; i < list.size(); i++) { @@ -199,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 90% rename from src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java rename to src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java index 7715da72c..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,28 +337,19 @@ public class DefaultExpressionList implements SpiExpressionList { expression.addSql(request); } request.append(listAndEnd); - return request.getSql(); } @Override - public ArrayList 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(); } - /** - * Calculate a hash based on the expressions but excluding the actual bind - * values. - */ - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { - builder.add(DefaultExpressionList.class); + @Override + public void prepareExpression(BeanQueryRequest request) { for (int i = 0, size = list.size(); i < size; i++) { - SpiExpression expression = list.get(i); - expression.queryAutoTuneHash(builder); + list.get(i).prepareExpression(request); } } @@ -366,26 +358,57 @@ public class DefaultExpressionList implements SpiExpressionList { * values. */ @Override - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(DefaultExpressionList.class); for (int i = 0, size = list.size(); i < size; i++) { - SpiExpression expression = list.get(i); - expression.queryPlanHash(request, builder); + list.get(i).queryPlanHash(builder); } } /** * 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++) { - SpiExpression expression = list.get(i); - hash = hash * 31 + expression.queryBindHash(); + hash = hash * 31 + list.get(i).queryBindHash(); } return hash; } + @Override + public boolean isSameByPlan(SpiExpression other) { + if (!(other instanceof DefaultExpressionList)) { + return false; + } + + DefaultExpressionList that = (DefaultExpressionList)other; + if (list.size() != that.list.size()) { + return false; + } + for (int i = 0, size = list.size(); i < size; i++) { + if (!list.get(i).isSameByPlan(that.list.get(i))) { + return false; + } + } + return true; + } + + @Override + public boolean isSameByBind(SpiExpression other) { + DefaultExpressionList that = (DefaultExpressionList)other; + if (list.size() != that.list.size()) { + return false; + } + for (int i = 0, size = list.size(); i < size; i++) { + if (!list.get(i).isSameByBind(that.list.get(i))) { + return false; + } + } + return true; + } + /** * Path exists - for the given path in a JSON document. */ 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 89% rename from src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java rename to src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionRequest.java index 510d62ba0..c87126df4 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionRequest.java @@ -1,7 +1,8 @@ -package com.avaje.ebeaninternal.util; +package com.avaje.ebeaninternal.server.expression; import java.sql.SQLException; import java.util.ArrayList; +import java.util.List; import com.avaje.ebeaninternal.api.SpiExpressionList; import com.avaje.ebeaninternal.api.SpiExpressionRequest; @@ -20,7 +21,7 @@ public class DefaultExpressionRequest implements SpiExpressionRequest { private final StringBuilder sql = new StringBuilder(); - private final ArrayList bindValues = new ArrayList(); + private final List bindValues = new ArrayList(); private final DeployParser deployParser; @@ -39,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) { @@ -54,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(); } /** @@ -70,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); @@ -93,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; } @@ -108,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); @@ -136,11 +146,13 @@ public class DefaultExpressionRequest implements SpiExpressionRequest { return bindLog == null ? "" : bindLog.toString(); } + @Override public String getSql() { return sql.toString(); } - public ArrayList getBindValues() { + @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 d0b65a6ed..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,71 +17,103 @@ 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 transient CQuery compiledSubQuery; + protected List bindParams; + + protected String sql; public ExistsExpression(SpiQuery subQuery, boolean not) { this.subQuery = subQuery; this.not = not; } - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { - builder.add(ExistsExpression.class).add(not); - - subQuery.queryAutoTuneHash(builder); + ExistsExpression(boolean not, String sql , List bindParams) { + this.not = not; + this.sql = sql; + this.bindParams = bindParams; + this.subQuery = null; } - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { + @Override + public void prepareExpression(BeanQueryRequest request) { - // queryPlanHash executes prior to addSql() or addBindValues() - // ... so compiledQuery will exist - compiledSubQuery = compileSubQuery(request); + CQuery subQuery = compileSubQuery(request); + this.bindParams = subQuery.getPredicates().getWhereExprBindValues(); + this.sql = subQuery.getGeneratedSql().replace('\n', ' '); + } - queryAutoTuneHash(builder); + @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()); } + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { + builder.add(ExistsExpression.class).add(not); + builder.add(sql).add(bindParams.size()); + } + + @Override public int queryBindHash() { return subQuery.queryBindHash(); } + @Override public void addSql(SpiExpressionRequest request) { - String subSelect = compiledSubQuery.getGeneratedSql(); - subSelect = subSelect.replace('\n', ' '); - if (not) { request.append(" not"); } request.append(" exists ("); - request.append(subSelect); + request.append(sql); request.append(") "); } + @Override public void addBindValues(SpiExpressionRequest request) { - List bindParams = compiledSubQuery.getPredicates().getWhereExprBindValues(); - - if (bindParams == null) { - return; - } - for (int i = 0; i < bindParams.size(); i++) { request.addBindValue(bindParams.get(i)); } } + @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 ce122251b..41aaec673 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java @@ -1,18 +1,16 @@ package com.avaje.ebeaninternal.server.expression; -import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.ManyWhereJoins; import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; 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. */ -class IdExpression implements SpiExpression { +class IdExpression extends NonPrepareExpression implements SpiExpression { private static final long serialVersionUID = -3065936341718489842L; @@ -25,6 +23,7 @@ class IdExpression implements SpiExpression { /** * Always returns false. */ + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { } @@ -34,6 +33,7 @@ class IdExpression implements SpiExpression { // always valid } + @Override public void addBindValues(SpiExpressionRequest request) { // 'flatten' EmbeddedId and multiple Id cases @@ -45,6 +45,7 @@ class IdExpression implements SpiExpression { } } + @Override public void addSql(SpiExpressionRequest request) { DefaultExpressionRequest r = (DefaultExpressionRequest) request; @@ -56,17 +57,25 @@ class IdExpression implements SpiExpression { /** * No properties so this is just a unique static number. */ - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(IdExpression.class); builder.bind(1); } - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - queryAutoTuneHash(builder); - } - + @Override public int queryBindHash() { 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 ee3fa0bad..cf67e92c3 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java @@ -1,8 +1,5 @@ package com.avaje.ebeaninternal.server.expression; -import java.util.List; - -import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.ManyWhereJoins; import com.avaje.ebeaninternal.api.SpiExpression; @@ -10,12 +7,13 @@ 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; /** * Slightly redundant as Query.setId() ultimately also does the same job. */ -public class IdInExpression implements SpiExpression { +public class IdInExpression extends NonPrepareExpression { private static final long serialVersionUID = 1L; @@ -25,6 +23,7 @@ public class IdInExpression implements SpiExpression { this.idList = idList; } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { } @@ -33,6 +32,7 @@ public class IdInExpression implements SpiExpression { // always valid } + @Override public void addBindValues(SpiExpressionRequest request) { // Bind the Id values including EmbeddedId and multiple Id @@ -60,6 +60,7 @@ public class IdInExpression implements SpiExpression { request.append(inClause); } + @Override public void addSql(SpiExpressionRequest request) { DefaultExpressionRequest r = (DefaultExpressionRequest) request; @@ -74,17 +75,38 @@ public class IdInExpression implements SpiExpression { /** * Incorporates the number of Id values to bind. */ - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(IdInExpression.class).add(idList.size()); builder.bind(idList.size()); } - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - queryAutoTuneHash(builder); - } - + @Override public int queryBindHash() { 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 f4e23718d..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; @@ -28,6 +28,7 @@ class InExpression extends AbstractExpression { this.not = not; } + @Override public void addBindValues(SpiExpressionRequest request) { ElPropertyValue prop = getElProp(request); @@ -51,6 +52,7 @@ class InExpression extends AbstractExpression { } } + @Override public void addSql(SpiExpressionRequest request) { if (values.length == 0) { @@ -59,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"); } @@ -88,15 +88,13 @@ class InExpression extends AbstractExpression { /** * Based on the number of values in the in clause. */ - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(InExpression.class).add(propName).add(values.length).add(not); builder.bind(values.length); } - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - queryAutoTuneHash(builder); - } - + @Override public int queryBindHash() { int hc = 31; for (int i = 0; i < values.length; i++) { @@ -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 2781d0897..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. */ @@ -20,26 +21,36 @@ class InQueryExpression extends AbstractExpression { private final SpiQuery subQuery; - private transient CQuery compiledSubQuery; + private List bindParams; - public InQueryExpression(String propertyName, SpiQuery subQuery, boolean not) { + private String sql; + + InQueryExpression(String propertyName, SpiQuery subQuery, boolean not) { super(propertyName); this.subQuery = subQuery; this.not = not; } - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { - builder.add(InQueryExpression.class).add(propName).add(not); - subQuery.queryAutoTuneHash(builder); + InQueryExpression(String propertyName, boolean not, String sql, List bindParams) { + super(propertyName); + this.subQuery = null; + this.not = not; + this.sql = sql; + this.bindParams = bindParams; } - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { + @Override + public void prepareExpression(BeanQueryRequest request) { - // queryPlanHash executes prior to addSql() or addBindValues() - // ... so compiledQuery will exist - compiledSubQuery = compileSubQuery(request); + CQuery subQuery = compileSubQuery(request); + this.bindParams = subQuery.getPredicates().getWhereExprBindValues(); + this.sql = subQuery.getGeneratedSql().replace('\n', ' '); + } - queryAutoTuneHash(builder); + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { + builder.add(InQueryExpression.class).add(propName).add(not); + builder.add(sql).add(bindParams.size()); } /** @@ -51,35 +62,55 @@ class InQueryExpression extends AbstractExpression { return ebeanServer.compileQuery(subQuery, queryRequest.getTransaction()); } + @Override public int queryBindHash() { return subQuery.queryBindHash(); } + @Override public void addSql(SpiExpressionRequest request) { - String subSelect = compiledSubQuery.getGeneratedSql(); - subSelect = subSelect.replace('\n', ' '); - - String propertyName = getPropertyName(); - request.append(" (").append(propertyName).append(")"); + request.append(" (").append(propName).append(")"); if (not) { request.append(" not"); } request.append(" in ("); - request.append(subSelect); + request.append(sql); request.append(") "); } + @Override public void addBindValues(SpiExpressionRequest request) { - List bindParams = compiledSubQuery.getPredicates().getWhereExprBindValues(); - - if (bindParams == null) { - return; - } - for (int i = 0; i < bindParams.size(); i++) { 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 5489c0f72..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; /** @@ -58,13 +58,10 @@ class JsonPathExpression extends AbstractExpression { } @Override - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(JsonPathExpression.class).add(propName).add(path).add(operator); - } - - @Override - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - queryAutoTuneHash(builder); + builder.bindIfNotNull(value); + builder.bindIfNotNull(upperValue); } @Override @@ -74,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 08916044c..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) { @@ -131,24 +156,23 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr } } + @Override + public void prepareExpression(BeanQueryRequest request) { + List list = exprList.internalList(); + for (int i = 0; i < list.size(); i++) { + list.get(i).prepareExpression(request); + } + } + /** * Based on Junction type and all the expression contained. */ @Override - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(JunctionExpression.class).add(joinType); List list = exprList.internalList(); for (int i = 0; i < list.size(); i++) { - list.get(i).queryAutoTuneHash(builder); - } - } - - @Override - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - builder.add(JunctionExpression.class).add(joinType); - List list = exprList.internalList(); - for (int i = 0; i < list.size(); i++) { - list.get(i).queryPlanHash(request, builder); + list.get(i).queryPlanHash(builder); } } @@ -164,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 9f0d9f475..56e58092f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java @@ -1,8 +1,8 @@ package com.avaje.ebeaninternal.server.expression; import com.avaje.ebean.LikeType; -import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.server.el.ElPropertyValue; @@ -23,6 +23,7 @@ class LikeExpression extends AbstractExpression { this.val = value; } + @Override public void addBindValues(SpiExpressionRequest request) { ElPropertyValue prop = getElProp(request); @@ -36,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(")"); @@ -61,19 +61,35 @@ class LikeExpression extends AbstractExpression { /** * Based on caseInsensitive and the property name. */ - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(LikeExpression.class).add(caseInsensitive).add(propName); builder.bind(1); } - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - queryAutoTuneHash(builder); - } - + @Override public int queryBindHash() { 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 981e6b563..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("("); @@ -74,25 +88,47 @@ abstract class LogicExpression implements SpiExpression { request.append(") "); } + @Override + public void prepareExpression(BeanQueryRequest request) { + expOne.prepareExpression(request); + expTwo.prepareExpression(request); + } + /** * Based on the joinType plus the two expressions. */ - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(LogicExpression.class).add(joinType); - expOne.queryAutoTuneHash(builder); - expTwo.queryAutoTuneHash(builder); - } - - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - builder.add(LogicExpression.class).add(joinType); - expOne.queryPlanHash(request, builder); - expTwo.queryPlanHash(request, builder); + 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 new file mode 100644 index 000000000..40dc0b66d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NonPrepareExpression.java @@ -0,0 +1,20 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.SpiExpression; + +/** + * Base abstract expression that does nothing for prepareExpression(). + */ +abstract class NonPrepareExpression implements SpiExpression { + + @Override + 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 549ecb60d..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 @@ -26,13 +31,13 @@ class NoopExpression implements SpiExpression { } @Override - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { - builder.add(NoopExpression.class); + public void prepareExpression(BeanQueryRequest request) { + // do nothing } @Override - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - queryAutoTuneHash(builder); + public void queryPlanHash(HashQueryPlanBuilder builder) { + builder.add(NoopExpression.class); } @Override @@ -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 a34fc1bbe..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,31 +37,49 @@ 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 + public void prepareExpression(BeanQueryRequest request) { + exp.prepareExpression(request); } /** * Based on the expression. */ - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(NotExpression.class); - exp.queryAutoTuneHash(builder); - } - - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - builder.add(NotExpression.class); - exp.queryPlanHash(request, builder); + 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 bcbab597d..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; @@ -20,13 +20,15 @@ class NullExpression extends AbstractExpression { this.notNull = notNull; } + @Override public void addBindValues(SpiExpressionRequest request) { } + @Override public void addSql(SpiExpressionRequest request) { - String propertyName = getPropertyName(); + String propertyName = propName; String nullExpr = notNull ? " is not null " : " is null "; @@ -39,17 +41,32 @@ 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. */ - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(NullExpression.class).add(notNull).add(propName); } - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - queryAutoTuneHash(builder); - } - + @Override public int queryBindHash() { return (notNull ? 1 : 0); } 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 e078b87c9..93261f6b2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java @@ -1,6 +1,5 @@ package com.avaje.ebeaninternal.server.expression; -import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.ManyWhereJoins; import com.avaje.ebeaninternal.api.SpiExpression; @@ -8,7 +7,7 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -class RawExpression implements SpiExpression { +class RawExpression extends NonPrepareExpression { private static final long serialVersionUID = 7973903141340334606L; @@ -21,6 +20,7 @@ class RawExpression implements SpiExpression { this.values = values; } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { } @@ -30,6 +30,7 @@ class RawExpression implements SpiExpression { // always ignored } + @Override public void addBindValues(SpiExpressionRequest request) { if (values != null) { for (int i = 0; i < values.length; i++) { @@ -38,6 +39,7 @@ class RawExpression implements SpiExpression { } } + @Override public void addSql(SpiExpressionRequest request) { request.append(sql); } @@ -45,15 +47,40 @@ class RawExpression implements SpiExpression { /** * Based on the sql. */ - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(RawExpression.class).add(sql); } - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - queryAutoTuneHash(builder); - } - + @Override 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..05e483d94 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/Same.java @@ -0,0 +1,56 @@ +package com.avaje.ebeaninternal.server.expression; + +import java.util.Collection; +import java.util.Iterator; + +/** + * 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); + } + + /** + * Return true if both collections are the same by value and order is taken into account. + */ + public static boolean sameByValue(Collection v1, Collection v2) { + if (v1 == null) { + return v2 == null; + } + if (v2 == null || v1.size() != v2.size()) { + return false; + } + Iterator thisIt = v1.iterator(); + Iterator thatIt = v2.iterator(); + while (thisIt.hasNext() && thatIt.hasNext()) { + if (!thisIt.next().equals(thatIt.next())) { + return false; + } + } + return true; + } + + /** + * 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 8cde6e3ef..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,16 +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]); @@ -50,14 +59,13 @@ public class SimpleExpression extends AbstractExpression { request.addBindValue(value); } + @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()) { @@ -66,27 +74,37 @@ public class SimpleExpression extends AbstractExpression { return; } } - request.append(propertyName).append(type.bind()); + request.append(propName).append(type.bind()); } /** * Based on the type and propertyName. */ - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(SimpleExpression.class).add(propName).add(type.name()); builder.bind(1); } - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - queryAutoTuneHash(builder); - } - + @Override public int queryBindHash() { 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/loadcontext/DLoadBaseContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBaseContext.java index d94120dc1..249d9f998 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBaseContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBaseContext.java @@ -54,18 +54,7 @@ public abstract class DLoadBaseContext { return batchSize; } - int queryFetchBatch = queryProps.getQueryFetchBatch(); - if (queryFetchBatch > 0) { - // property join was automatically set to a 'query join' - return queryFetchBatch; - } - - FetchConfig fetchConfig = queryProps.getFetchConfig(); - if (fetchConfig == null) { - return batchSize; - } - - int queryBatchSize = fetchConfig.getQueryBatchSize(); + int queryBatchSize = queryProps.getQueryFetchBatch(); if (queryBatchSize == -1) { // not eager query fetch, just lazy loading return batchSize; @@ -84,9 +73,6 @@ public abstract class DLoadBaseContext { return defaultBatchSize; } FetchConfig fetchConfig = queryProps.getFetchConfig(); - if (fetchConfig == null) { - return defaultBatchSize; - } if (fetchConfig.isQueryAll()) { return firstBatchSize; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java index 254fd6d78..966773413 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java @@ -58,7 +58,6 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex if (currentBuffer.isFull()) { currentBuffer = createBuffer(secondaryBatchSize); } - // set the persistenceContext on the bean first ebi.setBeanLoader(currentBuffer, getPersistenceContext()); currentBuffer.add(ebi); } @@ -124,7 +123,7 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex } /** - * Return true if the buffer is full. + * Add the bean to the load buffer. */ public void add(EntityBeanIntercept ebi) { if (persistenceContext == null) { @@ -180,11 +179,10 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex } if (context.hitCache) { - // Check each of the beans in the batch to see if they are in the L2 cache. + // check each of the beans in the batch to see if they are in the L2 cache. Iterator iterator = list.iterator(); while (iterator.hasNext()) { - EntityBeanIntercept bean = iterator.next(); - if (context.desc.cacheBeanLoad(bean)) { + if (context.desc.cacheBeanLoad(iterator.next())) { iterator.remove(); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java index db7f1c366..cb71ec63e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java @@ -9,6 +9,7 @@ import com.avaje.ebeaninternal.api.LoadContext; import com.avaje.ebeaninternal.api.LoadSecondaryQuery; import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiQuerySecondary; import com.avaje.ebeaninternal.server.core.OrmQueryRequest; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanProperty; @@ -58,7 +59,7 @@ public class DLoadContext implements LoadContext { private List secQuery; - public DLoadContext(OrmQueryRequest request) { + public DLoadContext(OrmQueryRequest request, SpiQuerySecondary secondaryQueries) { this.persistenceContext = request.getPersistenceContext(); this.ebeanServer = request.getServer(); @@ -86,6 +87,41 @@ public class DLoadContext implements LoadContext { // initialise rootBeanContext after origin and relativePath have been set this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null); + + registerSecondaryQueries(secondaryQueries); + } + + + /** + * Register the +query and +lazy secondary queries with their appropriate LoadBeanContext or LoadManyContext. + */ + private void registerSecondaryQueries(SpiQuerySecondary secondaryQueries) { + + this.secQuery = secondaryQueries.getQueryJoins(); + if (secQuery != null) { + for (OrmQueryProperties pathProperties : secQuery) { + registerSecondaryQuery(pathProperties); + } + } + + List lazyJoins = secondaryQueries.getLazyJoins(); + if (lazyJoins != null) { + for (OrmQueryProperties lazyJoin : lazyJoins) { + registerSecondaryQuery(lazyJoin); + } + } + } + + /** + * Setup the load context at this path with OrmQueryProperties which is + * used to build the appropriate query for +query or +lazy loading. + */ + private void registerSecondaryQuery(OrmQueryProperties props) { + + ElPropertyValue elGetValue = rootDescriptor.getElGetValue(props.getPath()); + + boolean many = elGetValue.getBeanProperty().containsMany(); + registerSecondaryNode(many, props); } protected boolean isExcludeBeanCache() { @@ -119,8 +155,7 @@ public class DLoadContext implements LoadContext { if (secQuery != null) { for (int i = 0; i < secQuery.size(); i++) { - OrmQueryProperties properties = secQuery.get(i); - LoadSecondaryQuery load = getLoadSecondaryQuery(properties.getPath()); + LoadSecondaryQuery load = getLoadSecondaryQuery(secQuery.get(i).getPath()); load.loadSecondaryQuery(parentRequest); } } @@ -137,49 +172,6 @@ public class DLoadContext implements LoadContext { return beanLoad; } - /** - * Remove the +query and +lazy secondary queries and - * register them with their appropriate LoadBeanContext - * or LoadManyContext. - *

- * The parts of the secondary queries are removed and used - * by LoadBeanContext/LoadManyContext to build the appropriate - * queries. - *

- */ - public void registerSecondaryQueries(SpiQuery query) { - - secQuery = query.removeQueryJoins(); - if (secQuery != null) { - for (int i = 0; i < secQuery.size(); i++) { - OrmQueryProperties props = secQuery.get(i); - registerSecondaryQuery(props); - } - } - - List lazyQueries = query.removeLazyJoins(); - if (lazyQueries != null) { - for (int i = 0; i < lazyQueries.size(); i++) { - OrmQueryProperties lazyProps = lazyQueries.get(i); - registerSecondaryQuery(lazyProps); - } - } - } - - /** - * Setup the load context at this path with OrmQueryProperties which is - * used to build the appropriate query for +query or +lazy loading. - */ - private void registerSecondaryQuery(OrmQueryProperties props) { - - String propName = props.getPath(); - ElPropertyValue elGetValue = rootDescriptor.getElGetValue(propName); - - boolean many = elGetValue.getBeanProperty().containsMany(); - registerSecondaryNode(many, props); - } - - public ObjectGraphNode getObjectGraphNode(String path) { ObjectGraphNode node = nodePathMap.get(path); @@ -247,7 +239,7 @@ public class DLoadContext implements LoadContext { getManyContext(path).register(bc); } - private DLoadBeanContext getBeanContext(String path) { + protected DLoadBeanContext getBeanContext(String path) { if (path == null) { return rootBeanContext; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java index 2919e12d3..ba9acce78 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java @@ -25,7 +25,6 @@ import com.avaje.ebeaninternal.server.querydefn.OrmQueryLimitRequest; import javax.persistence.PersistenceException; import java.util.Iterator; import java.util.List; -import java.util.Set; /** * Generates the SQL SELECT statements taking into account the physical @@ -354,8 +353,7 @@ public class CQueryBuilder { // transfer PathProperties into OrmQueryDetail for (String path : pathProps.getPaths()) { - Set props = pathProps.get(path); - detail.getChunk(path, true).setDefaultProperties(null, props); + detail.fetch(path, pathProps.get(path)); } // check if @Id property included in RawSql 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 e3570defb..c42ec239f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java @@ -1,16 +1,6 @@ package com.avaje.ebeaninternal.server.query; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - import com.avaje.ebean.RawSql; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.avaje.ebeaninternal.api.BindParams; import com.avaje.ebeaninternal.api.BindParams.OrderedList; import com.avaje.ebeaninternal.api.SpiExpressionList; @@ -23,7 +13,15 @@ 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; + +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * Compile Query Predicates. @@ -465,7 +463,7 @@ public class CQueryPredicates { /** * Return the bind values for the where expression. */ - public ArrayList getWhereExprBindValues() { + public List getWhereExprBindValues() { return where.getBindValues(); } 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/query/SqlTreeBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java index a4de477ac..285f412a1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java @@ -140,7 +140,7 @@ public class SqlTreeBuilder { boolean includeJoins = (alias == null) ? false : alias.isIncludeJoins(); return new SqlTree(summary.toString(), rootNode, selectSql, fromSql, inheritanceWhereSql, encryptedProps, - manyProperty, queryDetail.getIncludes(), includeJoins); + manyProperty, queryDetail.getFetchPaths(), includeJoins); } private String buildSelectClause() { @@ -183,7 +183,7 @@ public class SqlTreeBuilder { rootNode = buildSelectChain(null, null, desc, null); if (!rawSql) { - alias.addJoin(queryDetail.getIncludes(), desc); + alias.addJoin(queryDetail.getFetchPaths(), desc); alias.addJoin(predicates.getPredicateIncludes(), desc); alias.addManyWhereJoins(manyWhereJoins.getPropertyNames()); @@ -471,7 +471,7 @@ public class SqlTreeBuilder { return false; } - if (queryDetail.includes(propName)) { + if (queryDetail.includesPath(propName)) { if (manyProperty != null) { // only one many associated allowed to be included in fetch @@ -498,7 +498,7 @@ public class SqlTreeBuilder { */ private boolean isIncludeBean(String prefix) { - if (queryDetail.includes(prefix)) { + if (queryDetail.includesPath(prefix)) { // explicitly included summary.append(", ").append(prefix); String[] splitNames = SplitName.split(prefix); 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 86f94831d..258c404c4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -13,13 +13,13 @@ 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; import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiQuerySecondary; import com.avaje.ebeaninternal.server.autotune.ProfilingListener; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; @@ -28,7 +28,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 +234,7 @@ public class DefaultOrmQuery implements SpiQuery { /** * Hash of final query after AutoTune tuning. */ - private HashQueryPlan queryPlanHash; + private CQueryPlanKey queryPlanKey; private transient PersistenceContext persistenceContext; @@ -345,20 +345,6 @@ public class DefaultOrmQuery implements SpiQuery { this.beanDescriptor = beanDescriptor; } - /** - * Return true if select all properties was used to ensure the property invoking a lazy load was - * included in the query. - */ - public boolean selectAllForLazyLoadProperty() { - if (lazyLoadProperty != null) { - if (!detail.containsProperty(lazyLoadProperty)) { - detail.select("*"); - return true; - } - } - return false; - } - public RawSql getRawSql() { return rawSql; } @@ -390,22 +376,38 @@ public class DefaultOrmQuery implements SpiQuery { return expressionFactory; } - /** - * Return true if the where expressions contains a many property. - */ - public boolean initManyWhereJoins() { + private void createExtraJoinsToSupportManyWhereClause() { manyWhereJoins = new ManyWhereJoins(); if (whereExpressions != null) { whereExpressions.containsMany(beanDescriptor, manyWhereJoins); } - return !manyWhereJoins.isEmpty(); + if (!manyWhereJoins.isEmpty()) { + setSqlDistinct(true); + } } + /** + * Return the extra joins required to support the where clause for 'Many' properties. + */ public ManyWhereJoins getManyWhereJoins() { return manyWhereJoins; } - public List removeQueryJoins() { + /** + * Return true if select all properties was used to ensure the property invoking a lazy load was + * included in the query. + */ + public boolean selectAllForLazyLoadProperty() { + if (lazyLoadProperty != null) { + if (!detail.containsProperty(lazyLoadProperty)) { + detail.select("*"); + return true; + } + } + return false; + } + + protected List removeQueryJoins() { List queryJoins = detail.removeSecondaryQueries(); if (queryJoins != null) { if (orderBy != null) { @@ -433,7 +435,7 @@ public class DefaultOrmQuery implements SpiQuery { return queryJoins; } - public List removeLazyJoins() { + protected List removeLazyJoins() { return detail.removeSecondaryLazyQueries(); } @@ -441,11 +443,63 @@ public class DefaultOrmQuery implements SpiQuery { this.lazyLoadManyPath = lazyLoadManyPath; } + @Override + public SpiQuerySecondary convertJoins() { + + createExtraJoinsToSupportManyWhereClause(); + markQueryJoins(); + + return new OrmQuerySecondary(removeQueryJoins(), removeLazyJoins()); + } + /** - * Convert any many joins fetch joins to query joins. + * Limit the number of fetch joins to Many properties, mark as query joins as needed. */ - public void convertManyFetchJoinsToQueryJoins(boolean allowOne, int queryBatch) { - detail.convertManyFetchJoinsToQueryJoins(beanDescriptor, lazyLoadManyPath, allowOne, queryBatch); + private void markQueryJoins() { + detail.markQueryJoins(beanDescriptor, lazyLoadManyPath, isAllowOneManyFetch()); + } + + private boolean isAllowOneManyFetch() { + + if (Mode.LAZYLOAD_MANY.equals(getMode())) { + return false; + } else if (hasMaxRowsOrFirstRow() && !isRawSql() && !isSqlSelect()) { + return false; + } + return true; + } + + protected void setOrmQueryDetail(OrmQueryDetail detail) { + this.detail = detail; + } + + public void setDefaultSelectClause() { + detail.setDefaultSelectClause(beanDescriptor); + } + + public void setDetail(OrmQueryDetail detail) { + this.detail = detail; + } + + public boolean tuneFetchProperties(OrmQueryDetail tunedDetail) { + return detail.tuneFetchProperties(tunedDetail); + } + + public OrmQueryDetail getDetail() { + return detail; + } + + public ExpressionList filterMany(String prop) { + + OrmQueryProperties chunk = detail.getChunk(prop, true); + return chunk.filterMany(this); + } + + public void setFilterMany(String prop, ExpressionList filterMany) { + if (filterMany != null) { + OrmQueryProperties chunk = detail.getChunk(prop, true); + chunk.setFilterMany((SpiExpressionList) filterMany); + } } /** @@ -466,7 +520,6 @@ public class DefaultOrmQuery implements SpiQuery { public void setSelectId() { // clear select and fetch joins.. detail.clear(); - select(beanDescriptor.getIdBinder().getIdProperty()); } @@ -493,7 +546,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,79 +812,38 @@ public class DefaultOrmQuery implements SpiQuery { /** * Calculate the query hash for either AutoTune query tuning or Query Plan caching. */ - private HashQueryPlan calculateHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { + CQueryPlanKey createQueryPlanKey() { - // exclude bind values and things unrelated to the sql being generated + queryPlanKey = new OrmQueryPlanKey(includeTableJoin, type, detail, maxRows, firstRow, + disableLazyLoading, rawWhereClause, orderBy, query, additionalWhere, additionalHaving, + distinct, sqlDistinct, mapKey, id, bindParams, whereExpressions, havingExpressions, + temporalMode, forUpdate, rootTableAlias, rawSql); - if (builder == null) { - 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(request, builder); - } - if (bindParams != null) { - bindParams.buildQueryPlanHash(builder); - } - - if (request == null) { - // for AutoTune... - builder.add(true); - if (whereExpressions != null) { - whereExpressions.queryAutoTuneHash(builder); - } - if (havingExpressions != null) { - havingExpressions.queryAutoTuneHash(builder); - } - - } else { - // for query plan... - builder.add(false); - if (whereExpressions != null) { - whereExpressions.queryPlanHash(request, builder); - } - if (havingExpressions != null) { - havingExpressions.queryPlanHash(request, builder); - } - } - - return builder.build(); + return queryPlanKey; } /** - * Calculate a hash used by AutoTune to identify when a query has changed (and hence potentially - * needs a new tuned query plan to be developed). + * Prepare the query which prepares any expressions (sub-query expressions etc) and calculates the query plan key. */ - public HashQueryPlan queryAutoTuneHash(HashQueryPlanBuilder builder) { + public CQueryPlanKey prepare(BeanQueryRequest request) { - return calculateHash(null, builder); + prepareExpressions(request); + + queryPlanKey = createQueryPlanKey(); + 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 expressions (compile sub-queries etc). */ - public HashQueryPlan queryPlanHash(BeanQueryRequest request) { + private void prepareExpressions(BeanQueryRequest request) { - queryPlanHash = calculateHash(request, null); - return queryPlanHash; + if (whereExpressions != null) { + whereExpressions.prepareExpression(request); + } + if (havingExpressions != null) { + havingExpressions.prepareExpression(request); + } } /** @@ -863,7 +875,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); } /** @@ -974,18 +986,11 @@ public class DefaultOrmQuery implements SpiQuery { parser.assign(this); } - protected void setOrmQueryDetail(OrmQueryDetail detail) { - this.detail = detail; - } protected void setRawWhereClause(String rawWhereClause) { this.rawWhereClause = rawWhereClause; } - public void setDefaultSelectClause() { - detail.setDefaultSelectClause(beanDescriptor); - } - public DefaultOrmQuery select(String columns) { detail.select(columns); return this; @@ -1004,7 +1009,7 @@ public class DefaultOrmQuery implements SpiQuery { } public DefaultOrmQuery fetch(String property, String columns, FetchConfig config) { - detail.addFetch(property, columns, config); + detail.fetch(property, columns, config); return this; } @@ -1207,18 +1212,6 @@ public class DefaultOrmQuery implements SpiQuery { return beanType; } - public void setDetail(OrmQueryDetail detail) { - this.detail = detail; - } - - public boolean tuneFetchProperties(OrmQueryDetail tunedDetail) { - return detail.tuneFetchProperties(tunedDetail); - } - - public OrmQueryDetail getDetail() { - return detail; - } - public String toString() { return "Query [" + whereExpressions + "]"; } @@ -1312,19 +1305,6 @@ public class DefaultOrmQuery implements SpiQuery { return whereExpressions; } - public ExpressionList filterMany(String prop) { - - OrmQueryProperties chunk = detail.getChunk(prop, true); - return chunk.filterMany(this); - } - - public void setFilterMany(String prop, ExpressionList filterMany) { - if (filterMany != null) { - OrmQueryProperties chunk = detail.getChunk(prop, true); - chunk.setFilterMany((SpiExpressionList) filterMany); - } - } - public DefaultOrmQuery having(String addToHavingClause) { if (additionalHaving == null) { additionalHaving = addToHavingClause; diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java index e8d0cc46b..47fb077c6 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -1,20 +1,6 @@ package com.avaje.ebeaninternal.server.querydefn; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.persistence.PersistenceException; - import com.avaje.ebean.FetchConfig; -import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; @@ -22,6 +8,17 @@ import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; import com.avaje.ebeaninternal.server.el.ElPropertyValue; import com.avaje.ebeaninternal.server.query.SplitName; +import javax.persistence.PersistenceException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + /** * Represents the internal structure of an Object Relational query. *

@@ -45,9 +42,7 @@ public class OrmQueryDetail implements Serializable { /** * Contains the fetch/lazy/query joins and their properties. */ - private LinkedHashMap fetchPaths = new LinkedHashMap(8); - - private LinkedHashSet includes = new LinkedHashSet(8); + private LinkedHashMap fetchPaths = new LinkedHashMap(); /** * Return a deep copy of the OrmQueryDetail. @@ -58,34 +53,32 @@ public class OrmQueryDetail implements Serializable { for (Map.Entry entry : fetchPaths.entrySet()) { copy.fetchPaths.put(entry.getKey(), entry.getValue().copy()); } - copy.includes = new LinkedHashSet(includes); return copy; } + public int queryPlanHash() { + HashQueryPlanBuilder builder = new HashQueryPlanBuilder(); + queryPlanHash(builder); + return builder.getPlanHash(); + } + /** * Calculate the hash for the query plan. */ - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - if (baseProps == null) { - builder.add(false); - } else { - builder.add(true); - baseProps.queryPlanHash(request, builder); - } - + public void queryPlanHash(HashQueryPlanBuilder builder) { + baseProps.queryPlanHash(builder); if (fetchPaths != null) { for (OrmQueryProperties p : fetchPaths.values()) { - p.queryPlanHash(request, builder); + p.queryPlanHash(builder); } } } /** - * Return true if equal in terms of autoTune (select and fetch). + * Return true if the details are the same for query plan purposes. */ - public boolean isAutoTuneEqual(OrmQueryDetail otherDetail) { - - if (!isSame(baseProps, otherDetail.baseProps)) { + public boolean isSameByPlan(OrmQueryDetail otherDetail) { + if (!isSameByPlan(baseProps, otherDetail.baseProps)) { return false; } if (fetchPaths == null) { @@ -94,10 +87,16 @@ public class OrmQueryDetail implements Serializable { if (fetchPaths.size() != otherDetail.fetchPaths.size()) { return false; } - Set> entries = fetchPaths.entrySet(); - for (Map.Entry entry : entries) { - OrmQueryProperties chunk = otherDetail.getChunk(entry.getKey(), false); - if (!isSame(entry.getValue(), chunk)) { + // check with ordering being important + Iterator> thisIt = fetchPaths.entrySet().iterator(); + Iterator> thatIt = otherDetail.fetchPaths.entrySet().iterator(); + while (thisIt.hasNext() && thatIt.hasNext()) { + Map.Entry thisEntry = thisIt.next(); + Map.Entry thatEntry = thatIt.next(); + if (!thisEntry.getKey().equals(thatEntry.getKey())) { + return false; + } + if (!thisEntry.getValue().isSameByPlan(thatEntry.getValue())) { return false; } } @@ -105,21 +104,54 @@ public class OrmQueryDetail implements Serializable { return true; } - private boolean isSame(OrmQueryProperties p1, OrmQueryProperties p2) { - if (p1 == null) { - return p2 == null; + /** + * Return true if equal in terms of autoTune (select and fetch without property ordering). + */ + public boolean isAutoTuneEqual(OrmQueryDetail otherDetail) { + + if (!isSameByAutoTune(baseProps, otherDetail.baseProps)) { + return false; } - return p1.isSame(p2); + if (fetchPaths == null) { + return otherDetail.fetchPaths == null; + } + if (fetchPaths.size() != otherDetail.fetchPaths.size()) { + return false; + } + // check without regard to ordering + for (Map.Entry entry : fetchPaths.entrySet()) { + OrmQueryProperties chunk = otherDetail.getChunk(entry.getKey(), false); + if (!isSameByAutoTune(entry.getValue(), chunk)) { + return false; + } + } + + return true; + } + + private boolean isSameByAutoTune(OrmQueryProperties p1, OrmQueryProperties p2) { + return p1 == null ? p2 == null : p1.isSameByAutoTune(p2); + } + + private boolean isSameByPlan(OrmQueryProperties p1, OrmQueryProperties p2) { + return p1 == null ? p2 == null : p1.isSameByPlan(p2); } public String toString() { + return asString(); + } + + /** + * Return the detail in string form. + */ + public String asString() { StringBuilder sb = new StringBuilder(); - if (baseProps != null) { - sb.append("select ").append(baseProps); + if (!baseProps.isEmpty()) { + baseProps.append("select ", sb); } if (fetchPaths != null) { for (OrmQueryProperties join : fetchPaths.values()) { - sb.append(" fetch ").append(join); + join.append(" fetch ", sb); } } return sb.toString(); @@ -133,11 +165,11 @@ public class OrmQueryDetail implements Serializable { * set the properties to include on the base / root entity. */ public void select(String columns) { - baseProps = new OrmQueryProperties(null, columns); + baseProps = new OrmQueryProperties(null, columns, null); } public boolean containsProperty(String property) { - return baseProps == null || baseProps.isIncluded(property); + return baseProps.isIncluded(property); } /** @@ -174,17 +206,12 @@ public class OrmQueryDetail implements Serializable { Collections.sort(matchingPaths); // the list of secondary queries - ArrayList props = new ArrayList(2); + ArrayList props = new ArrayList(); for (int i = 0; i < matchingPaths.size(); i++) { String path = matchingPaths.get(i); - includes.remove(path); OrmQueryProperties secQuery = fetchPaths.remove(path); - if (secQuery == null) { - // the path has already been removed by another - // secondary query - - } else { + if (secQuery != null) { props.add(secQuery); // remove any child properties for this path @@ -195,7 +222,6 @@ public class OrmQueryDetail implements Serializable { // remove join to secondary query from the main query // and add to this secondary query pass2It.remove(); - includes.remove(pass2Prop.getPath()); secQuery.add(pass2Prop); } } @@ -224,29 +250,24 @@ public class OrmQueryDetail implements Serializable { OrmQueryProperties tunedRoot = tunedDetail.getChunk(null, false); if (tunedRoot != null) { tuned = true; - baseProps.setTunedProperties(tunedRoot); - + baseProps = tunedRoot; for (OrmQueryProperties tunedChunk : tunedDetail.fetchPaths.values()) { - OrmQueryProperties chunk = getChunk(tunedChunk.getPath(), false); - if (chunk != null) { - // set the properties to select - chunk.setTunedProperties(tunedChunk); - } else { - // add a missing join - putFetchPath(tunedChunk.copy()); - } + fetch(tunedChunk.copy()); } } return tuned; } /** - * Matches a join() method of the query. + * Add or replace the fetch detail. */ - public void putFetchPath(OrmQueryProperties chunk) { + protected void fetch(OrmQueryProperties chunk) { String path = chunk.getPath(); - fetchPaths.put(path, chunk); - includes.add(path); + if (path == null) { + baseProps = chunk; + } else { + fetchPaths.put(path, chunk); + } } /** @@ -256,7 +277,6 @@ public class OrmQueryDetail implements Serializable { *

*/ public void clear() { - includes.clear(); fetchPaths.clear(); } @@ -266,26 +286,33 @@ public class OrmQueryDetail implements Serializable { * @param path the property to join * @param partialProps the properties on the join property to include */ - public void addFetch(String path, String partialProps, FetchConfig fetchConfig) { + public void fetch(String path, String partialProps, FetchConfig fetchConfig) { - OrmQueryProperties chunk = getChunk(path, true); - chunk.setProperties(partialProps); - chunk.setFetchConfig(fetchConfig); + fetch(new OrmQueryProperties(path, partialProps, fetchConfig)); } + /** + * Add for raw sql etc when the properties are already parsed into a set. + */ + public void fetch(String path, LinkedHashSet properties) { + fetch(new OrmQueryProperties(path, properties)); + } + + /** + * Sort the fetch paths into depth order adding any missing parent paths if necessary. + */ public void sortFetchPaths(BeanDescriptor d) { - LinkedHashMap sorted = new LinkedHashMap(fetchPaths.size()); - - for (OrmQueryProperties p : fetchPaths.values()) { - sortFetchPaths(d, p, sorted); + if (!fetchPaths.isEmpty()) { + LinkedHashMap sorted = new LinkedHashMap(); + for (OrmQueryProperties p : fetchPaths.values()) { + sortFetchPaths(d, p, sorted); + } + fetchPaths = sorted; } - - fetchPaths = sorted; } - private void sortFetchPaths(BeanDescriptor d, OrmQueryProperties p, - LinkedHashMap sorted) { + private void sortFetchPaths(BeanDescriptor d, OrmQueryProperties p, LinkedHashMap sorted) { String path = p.getPath(); if (!sorted.containsKey(path)) { @@ -298,8 +325,7 @@ public class OrmQueryDetail implements Serializable { if (parentProp == null) { ElPropertyValue el = d.getElGetValue(parentPath); if (el == null) { - String msg = "Path [" + parentPath + "] not valid from " + d.getFullName(); - throw new PersistenceException(msg); + throw new PersistenceException("Path [" + parentPath + "] not valid from " + d.getFullName()); } // add a missing parent path just fetching the Id property BeanPropertyAssoc assocOne = (BeanPropertyAssoc) el.getBeanProperty(); @@ -313,12 +339,9 @@ public class OrmQueryDetail implements Serializable { } /** - * Convert 'fetch joins' to 'many' properties over to 'query joins'. + * Mark 'fetch joins' to 'many' properties over to 'query joins' where needed. */ - public void convertManyFetchJoinsToQueryJoins(BeanDescriptor beanDescriptor, String lazyLoadManyPath, - boolean allowOne, int queryBatch) { - - ArrayList manyChunks = new ArrayList(3); + public void markQueryJoins(BeanDescriptor beanDescriptor, String lazyLoadManyPath, boolean allowOne) { // the name of the many fetch property if there is one String manyFetchProperty = null; @@ -334,8 +357,7 @@ public class OrmQueryDetail implements Serializable { // this is a join to a *ToMany OrmQueryProperties chunk = fetchPaths.get(fetchPath); - if (chunk.isFetchJoin() && !isLazyLoadManyRoot(lazyLoadManyPath, chunk) - && !hasParentSecJoin(lazyLoadManyPath, chunk)) { + if (isQueryJoinCandidate(lazyLoadManyPath, chunk)) { // this is a 'fetch join' (included in main query) if (fetchJoinFirstMany) { // letting the first one remain a 'fetch join' @@ -343,16 +365,20 @@ public class OrmQueryDetail implements Serializable { manyFetchProperty = fetchPath; } else { // convert this one over to a 'query join' - manyChunks.add(chunk); + chunk.markForQueryJoin(); } } } } + } - for (int i = 0; i < manyChunks.size(); i++) { - // convert 'fetch joins' over to 'query joins' - manyChunks.get(i).setQueryFetch(queryBatch, true); - } + /** + * Return true if this path is a candidate for converting to a query join. + */ + private boolean isQueryJoinCandidate(String lazyLoadManyPath, OrmQueryProperties chunk) { + return chunk.isFetchJoin() + && !isLazyLoadManyRoot(lazyLoadManyPath, chunk) + && !hasParentSecJoin(lazyLoadManyPath, chunk); } /** @@ -397,32 +423,28 @@ public class OrmQueryDetail implements Serializable { public void setDefaultSelectClause(BeanDescriptor desc) { if (desc.hasDefaultSelectClause() && !hasSelectClause()) { - if (baseProps == null) { - baseProps = new OrmQueryProperties(); - } - baseProps.setDefaultProperties(desc.getDefaultSelectClause(), desc.getDefaultSelectClauseSet()); + baseProps = new OrmQueryProperties(null, desc.getDefaultSelectClause()); } for (OrmQueryProperties joinProps : fetchPaths.values()) { if (!joinProps.hasSelectClause()) { BeanDescriptor assocDesc = desc.getBeanDescriptor(joinProps.getPath()); if (assocDesc.hasDefaultSelectClause()) { - // use the default select clause - joinProps.setDefaultProperties(assocDesc.getDefaultSelectClause(), assocDesc.getDefaultSelectClauseSet()); + fetch(joinProps.getPath(), assocDesc.getDefaultSelectClause(), joinProps.getFetchConfig()); } } } } public boolean hasSelectClause() { - return (baseProps != null && baseProps.hasSelectClause()); + return (baseProps.hasSelectClause()); } /** * Return true if the query detail has neither select properties specified or any joins defined. */ public boolean isEmpty() { - return fetchPaths.isEmpty() && (baseProps == null || !baseProps.hasProperties()); + return fetchPaths.isEmpty() && baseProps.allProperties(); } /** @@ -451,7 +473,7 @@ public class OrmQueryDetail implements Serializable { OrmQueryProperties props = fetchPaths.get(path); if (create && props == null) { props = new OrmQueryProperties(path); - putFetchPath(props); + fetch(props); return props; } else { @@ -460,9 +482,9 @@ public class OrmQueryDetail implements Serializable { } /** - * Return true if the property is included. + * Return true if the fetch path is included. */ - public boolean includes(String path) { + public boolean includesPath(String path) { OrmQueryProperties chunk = fetchPaths.get(path); @@ -471,9 +493,9 @@ public class OrmQueryDetail implements Serializable { } /** - * Return the property includes for this detail. + * Return the fetch paths for this detail. */ - public HashSet getIncludes() { - return includes; + public Set getFetchPaths() { + return fetchPaths.keySet(); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java index 186e0ad90..7b2109288 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java @@ -29,6 +29,8 @@ public class OrmQueryDetailParser { public OrmQueryDetail parse() throws PersistenceException { + if (parser.isEmpty()) return detail; + parser.nextWord(); processInitial(); return detail; @@ -62,8 +64,7 @@ public class OrmQueryDetailParser { private void process() { if (isFetch()) { - OrmQueryProperties props = readFindFetch(); - detail.putFetchPath(props); + detail.fetch(readFindFetch()); } else if (parser.isMatch("where")) { readWhere(); 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..616d8b962 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKey.java @@ -0,0 +1,142 @@ +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 SpiExpression where; + private final SpiExpression having; + private final RawSql.Key rawSqlKey; + 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.where = (whereExpressions == null) ? null : whereExpressions.copyForPlanKey(); + this.having = (havingExpressions == null) ? null : havingExpressions.copyForPlanKey(); + this.temporalMode = temporalMode; + this.forUpdate = forUpdate; + this.rootTableAlias = rootTableAlias; + this.rawSqlKey = (rawSql == null) ? null : rawSql.getKey(); + + // 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(rawSqlKey == null ? 0 : rawSqlKey.hashCode()); + builder.add(includeTableJoin != null ? includeTableJoin.queryHash() : 0); + builder.add(rootTableAlias); + + if (detail != null) { + detail.queryPlanHash(builder); + } + if (bindParams != null) { + bindParams.buildQueryPlanHash(builder); + } + if (where != null) { + where.queryPlanHash(builder); + } + if (having != null) { + having.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 (where != null ? !where.isSameByPlan(that.where) : that.where != null) return false; + if (having != null ? !having.isSameByPlan(that.having) : that.having != null) return false; + if (rawSqlKey != null ? !rawSqlKey.equals(that.rawSqlKey) : that.rawSqlKey != null) return false; + +// if (detail != null ? !detail.equals(that.detail) : that.detail != 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 21dba9a65..f6fcf960f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -1,27 +1,25 @@ package com.avaje.ebeaninternal.server.querydefn; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - import com.avaje.ebean.ExpressionFactory; import com.avaje.ebean.FetchConfig; import com.avaje.ebean.OrderBy; import com.avaje.ebean.Query; -import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionFactory; import com.avaje.ebeaninternal.api.SpiExpressionList; 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.expression.FilterExpressionList; +import com.avaje.ebeaninternal.server.expression.Same; import com.avaje.ebeaninternal.server.query.SplitName; -import com.avaje.ebeaninternal.util.FilterExpressionList; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; /** * Represents the Properties of an Object Relational query. @@ -30,36 +28,27 @@ public class OrmQueryProperties implements Serializable { private static final long serialVersionUID = -8785582703966455658L; - private String parentPath; - private String path; + protected static final FetchConfig DEFAULT_FETCH = new FetchConfig(); - private String properties; + private final String parentPath; + private final String path; - private String trimmedProperties; + private final String rawProperties; + private final String trimmedProperties; + + private final LinkedHashSet included; + + private final FetchConfig fetchConfig; /** - * NB: -1 means no +query, 0 means use the default batch size. + * Flag set when this fetch path needs to be a query join. */ - private int queryFetchBatch = -1; - private boolean queryFetchAll; - - /** - * NB: -1 means no +lazy, 0 means use the default batch size. - */ - private int lazyFetchBatch = -1; - - private FetchConfig fetchConfig; + private boolean markForQueryJoin; private boolean cache; private boolean readOnly; - /** - * Note this SHOULD be a LinkedHashSet to preserve order of the properties. This is to make using - * SqlSelect easier with predictable property/column ordering. - */ - private Set included; - /** * Included bean joins. */ @@ -73,8 +62,7 @@ public class OrmQueryProperties implements Serializable { private List secondaryChildren; /** - * OrderBy properties that where on the main query but moved here as they relate to this (query - * join). + * OrderBy properties that where on the main query but moved here as they relate to this (query join). */ @SuppressWarnings("rawtypes") private OrderBy orderBy; @@ -86,23 +74,103 @@ public class OrmQueryProperties implements Serializable { private SpiExpressionList filterMany; /** - * Construct with a given path (null == root path). + * Construct for root so path (and parentPath) are null. + */ + public OrmQueryProperties() { + this((String)null); + } + + /** + * Construct with a given path. */ public OrmQueryProperties(String path) { this.path = path; this.parentPath = SplitName.parent(path); + this.rawProperties = null; + this.trimmedProperties = null; + this.included = null; + this.fetchConfig = DEFAULT_FETCH; } - public OrmQueryProperties() { - this(null); + public OrmQueryProperties(String path, String rawProperties) { + this(path, rawProperties, null); + } + + public OrmQueryProperties(String path, String rawProperties, FetchConfig fetchConfig) { + + OrmQueryPropertiesParser.Response response = OrmQueryPropertiesParser.parse(rawProperties); + + this.path = path; + this.parentPath = SplitName.parent(path); + this.rawProperties = rawProperties; + this.trimmedProperties = response.properties; + this.included = response.included; + this.cache = response.cache; + this.readOnly = response.readOnly; + if (fetchConfig != null) { + this.fetchConfig = fetchConfig; + } else { + this.fetchConfig = response.fetchConfig; + } + } + + public OrmQueryProperties(String path, LinkedHashSet parsedProperties) { + if (parsedProperties == null) { + throw new IllegalArgumentException("parsedProperties is null"); + } + + this.path = path; + this.parentPath = SplitName.parent(path); + // for rawSql parsedProperties can be empty (when only fetching Id property) + this.included = parsedProperties; + this.rawProperties = join(parsedProperties); + this.trimmedProperties = rawProperties; + this.cache = false; + this.readOnly = false; + this.fetchConfig = DEFAULT_FETCH; } /** - * Used by query language parser. + * Join the set of properties into a comma delimited string. */ - public OrmQueryProperties(String path, String properties) { - this(path); - setProperties(properties); + private String join(LinkedHashSet parsedProperties) { + StringBuilder sb = new StringBuilder(50); + boolean first = true; + for (String property : parsedProperties) { + if (first) { + first = false; + } else { + sb.append(","); + } + sb.append(property); + } + return sb.toString(); + } + + /** + * Copy constructor. + */ + private OrmQueryProperties(OrmQueryProperties source) { + + this.parentPath = source.parentPath; + this.path = source.path; + this.rawProperties = source.rawProperties; + this.trimmedProperties = source.trimmedProperties; + this.cache = source.cache; + this.readOnly = source.readOnly; + this.fetchConfig = source.fetchConfig; + this.filterMany = source.filterMany; + this.included = (source.included == null) ? null : new LinkedHashSet(source.included); + if (includedBeanJoin != null) { + this.includedBeanJoin = new HashSet(source.includedBeanJoin); + } + } + + /** + * Creates a copy of the OrmQueryProperties. + */ + public OrmQueryProperties copy() { + return new OrmQueryProperties(this); } /** @@ -116,49 +184,10 @@ public class OrmQueryProperties implements Serializable { orderBy.add(orderProp); } - /** - * Set the Fetch configuration options for this path. - */ - public void setFetchConfig(FetchConfig fetchConfig) { - if (fetchConfig != null) { - this.fetchConfig = fetchConfig; - lazyFetchBatch = fetchConfig.getLazyBatchSize(); - queryFetchBatch = fetchConfig.getQueryBatchSize(); - queryFetchAll = fetchConfig.isQueryAll(); - } - } - public FetchConfig getFetchConfig() { return fetchConfig; } - /** - * Set the comma delimited properties to fetch for this path. - *

- * This can include the +query and +lazy type hints. - *

- */ - public void setProperties(String properties) { - this.properties = properties; - this.trimmedProperties = properties; - parseProperties(); - - if (!isAllProperties()) { - Set parsed = parseIncluded(trimmedProperties); - if (parsed.contains("*")) { - this.included = null; - } else { - this.included = parsed; - } - } else { - this.included = null; - } - } - - private boolean isAllProperties() { - return (trimmedProperties == null) || (trimmedProperties.length() == 0) || "*".equals(trimmedProperties); - } - /** * Return the expressions used to filter on this path. This should be a many path to use this * method. @@ -171,9 +200,7 @@ public class OrmQueryProperties implements Serializable { ExpressionFactory filterEf = queryEf.createExpressionFactory();// exprPath); filterMany = new FilterExpressionList(exprPath, filterEf, rootQuery); // by default we need to make this a 'query join' now - queryFetchAll = true; - queryFetchBatch = 100; - lazyFetchBatch = 100; + markForQueryJoin = true; } return filterMany; } @@ -202,28 +229,6 @@ public class OrmQueryProperties implements Serializable { this.filterMany = filterMany; } - /** - * Set the properties from deployment default FetchTypes. - */ - public void setDefaultProperties(String properties, Set included) { - this.properties = properties; - this.trimmedProperties = properties; - this.included = included; - } - - /** - * Set the properties from a matching AutoTune tuned properties. - */ - public void setTunedProperties(OrmQueryProperties tunedProperties) { - if (tunedProperties.hasProperties()) { - this.properties = tunedProperties.properties; - this.trimmedProperties = tunedProperties.trimmedProperties; - this.included = tunedProperties.included; - this.queryFetchBatch = Math.max(queryFetchBatch, tunedProperties.queryFetchBatch); - this.lazyFetchBatch = Math.max(lazyFetchBatch, tunedProperties.lazyFetchBatch); - } - } - /** * Define the select and joins for this query. */ @@ -258,30 +263,6 @@ public class OrmQueryProperties implements Serializable { } } - /** - * Creates a copy of the OrmQueryProperties. - */ - public OrmQueryProperties copy() { - OrmQueryProperties copy = new OrmQueryProperties(); - copy.parentPath = parentPath; - copy.path = path; - copy.properties = properties; - copy.trimmedProperties = trimmedProperties; - copy.cache = cache; - copy.readOnly = readOnly; - copy.queryFetchAll = queryFetchAll; - copy.queryFetchBatch = queryFetchBatch; - copy.lazyFetchBatch = lazyFetchBatch; - copy.filterMany = filterMany; - if (included != null) { - copy.included = new HashSet(included); - } - if (includedBeanJoin != null) { - copy.includedBeanJoin = new HashSet(includedBeanJoin); - } - return copy; - } - public boolean hasSelectClause() { if ("*".equals(trimmedProperties)) { // explicitly selected all properties @@ -291,17 +272,28 @@ public class OrmQueryProperties implements Serializable { return included != null; } + /** + * Return true if the properties and configuration are empty. + */ + public boolean isEmpty() { + return rawProperties == null || rawProperties.isEmpty(); + } + public String toString() { - String s = ""; + StringBuilder sb = new StringBuilder(40); + append("", sb); + return sb.toString(); + } + + public String append(String prefix, StringBuilder sb) { + sb.append(prefix); if (path != null) { - s += path + " "; + sb.append(path).append(" "); } - if (properties != null) { - s += "(" + properties + ") "; - } else if (included != null) { - s += "(" + included + ") "; + if (!isEmpty()) { + sb.append("(").append(rawProperties).append(") "); } - return s; + return sb.toString(); } public boolean isChild(OrmQueryProperties possibleChild) { @@ -318,48 +310,18 @@ public class OrmQueryProperties implements Serializable { secondaryChildren.add(child); } - public int autoTunePlanHash() { - - int hc = (path != null ? path.hashCode() : 1); - if (properties != null) { - hc = hc * 31 + properties.hashCode(); - } else { - hc = hc * 31 + (included != null ? included.hashCode() : 1); - } - - return hc; - } - /** - * Calculate the query plan hash. + * Return the raw properties. */ - @SuppressWarnings("unchecked") - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - - builder.add(path); - if (properties != null) { - builder.add(properties); - } else { - builder.add(included); - } - builder.add(filterMany != null); - if (filterMany != null) { - filterMany.queryPlanHash(request, builder); - } - builder.add(lazyFetchBatch); - builder.add(queryFetchBatch); - builder.add(queryFetchAll); - } - public String getProperties() { - return properties; + return rawProperties; } /** - * Return true if this has properties. + * Return true if this includes all properties on the path. */ - public boolean hasProperties() { - return properties != null || included != null; + public boolean allProperties() { + return included == null; } /** @@ -383,10 +345,6 @@ public class OrmQueryProperties implements Serializable { includedBeanJoin.add(propertyName); } - public boolean allProperties() { - return included == null; - } - /** * This excludes the bean joined properties. *

@@ -399,7 +357,7 @@ public class OrmQueryProperties implements Serializable { return included; } - LinkedHashSet temp = new LinkedHashSet(secondaryQueryJoins.size() + included.size()); + LinkedHashSet temp = new LinkedHashSet(2 * (secondaryQueryJoins.size() + included.size())); temp.addAll(included); temp.addAll(secondaryQueryJoins); return temp; @@ -413,30 +371,10 @@ public class OrmQueryProperties implements Serializable { } /** - * Return all the properties including the bean joins. This is the set that will be used by - * EntityBeanIntercept to determine if a property needs to be lazy loaded. + * Return the property set. */ - public Set getAllIncludedProperties() { - - if (included == null) { - return null; - } - - if (includedBeanJoin == null && secondaryQueryJoins == null) { - return new LinkedHashSet(included); - } - - LinkedHashSet s = new LinkedHashSet(2 * (included.size() + 5)); - if (included != null) { - s.addAll(included); - } - if (includedBeanJoin != null) { - s.addAll(includedBeanJoin); - } - if (secondaryQueryJoins != null) { - s.addAll(secondaryQueryJoins); - } - return s; + protected Set getIncluded() { + return included; } public boolean isIncluded(String propName) { @@ -448,141 +386,120 @@ public class OrmQueryProperties implements Serializable { return included == null || included.contains(propName); } - public void setQueryFetch(int batch, boolean queryFetchAll) { - this.queryFetchBatch = batch; - this.queryFetchAll = queryFetchAll; + /** + * Mark this path as needing to be a query join. + */ + public void markForQueryJoin() { + markForQueryJoin = true; } + /** + * Return true if this path is a 'query join'. + */ + public boolean isQueryFetch() { + return markForQueryJoin || getQueryFetchBatch() > -1; + } + + /** + * Return true if this path is a 'fetch join'. + */ public boolean isFetchJoin() { return !isQueryFetch() && !isLazyFetch(); } - public boolean isQueryFetch() { - return queryFetchBatch > -1; - } - - public int getQueryFetchBatch() { - return queryFetchBatch; - } - - public boolean isQueryFetchAll() { - return queryFetchAll; - } - + /** + * Return true if this path is a lazy fetch. + */ public boolean isLazyFetch() { - return lazyFetchBatch > -1; + return getLazyFetchBatch() > -1; } + /** + * Return the batch size to use for the query join. + */ + public int getQueryFetchBatch() { + return fetchConfig.getQueryBatchSize(); + } + + /** + * Return true if a query join should eagerly fetch 'all' rather than the 'first'. + */ + public boolean isQueryFetchAll() { + return fetchConfig.isQueryAll(); + } + + /** + * Return the batch size to use for lazy loading. + */ public int getLazyFetchBatch() { - return lazyFetchBatch; + return fetchConfig.getLazyBatchSize(); } + /** + * Return true if this path has the +readonly option. + */ public boolean isReadOnly() { return readOnly; } + /** + * Return true if this path has the +cache option to hit the cache. + */ public boolean isCache() { return cache; } + /** + * Return the parent path. + */ public String getParentPath() { return parentPath; } + /** + * Return the path relative to the root of the graph. + */ public String getPath() { return path; } - private void parseProperties() { - if (trimmedProperties == null) { - return; - } - int pos = trimmedProperties.indexOf("+readonly"); - if (pos > -1) { - trimmedProperties = StringHelper.replaceString(trimmedProperties, "+readonly", ""); - this.readOnly = true; - } - pos = trimmedProperties.indexOf("+cache"); - if (pos > -1) { - trimmedProperties = StringHelper.replaceString(trimmedProperties, "+cache", ""); - this.cache = true; - } - pos = trimmedProperties.indexOf("+query"); - if (pos > -1) { - queryFetchBatch = parseBatchHint(pos, "+query"); - } - pos = trimmedProperties.indexOf("+lazy"); - if (pos > -1) { - lazyFetchBatch = parseBatchHint(pos, "+lazy"); - } - - trimmedProperties = trimmedProperties.trim(); - while (trimmedProperties.startsWith(",")) { - trimmedProperties = trimmedProperties.substring(1).trim(); - } - } - - private int parseBatchHint(int pos, String option) { - - int startPos = pos + option.length(); - - int endPos = findEndPos(startPos, trimmedProperties); - if (endPos == -1) { - trimmedProperties = StringHelper.replaceString(trimmedProperties, option, ""); - return 0; - - } else { - - String batchParam = trimmedProperties.substring(startPos + 1, endPos); - - if (endPos + 1 >= trimmedProperties.length()) { - trimmedProperties = trimmedProperties.substring(0, pos); - } else { - trimmedProperties = trimmedProperties.substring(0, pos) + trimmedProperties.substring(endPos + 1); - } - return Integer.parseInt(batchParam); - } - } - - private int findEndPos(int pos, String props) { - - if (pos < props.length()) { - if (props.charAt(pos) == '(') { - int endPara = props.indexOf(')', pos + 1); - if (endPara == -1) { - String m = "Error could not find ')' in " + props + " after position " + pos; - throw new RuntimeException(m); - } - return endPara; - } - } - return -1; - } - /** - * Parse the include separating by comma or semicolon. + * Return true if the properties are the same for autoTune purposes. */ - private static Set parseIncluded(String rawList) { - - String[] res = rawList.split(","); - - LinkedHashSet set = new LinkedHashSet(res.length + 3); - - String temp; - for (int i = 0; i < res.length; i++) { - temp = res[i].trim(); - if (temp.length() > 0) { - set.add(temp); - } - } - - return Collections.unmodifiableSet(set); - } - - public boolean isSame(OrmQueryProperties p2) { + public boolean isSameByAutoTune(OrmQueryProperties p2) { if (included == null) { return p2.included == null; } return included.equals(p2.included); } + + /** + * Properties are the same for query plan purposes. + */ + public boolean isSameByPlan(OrmQueryProperties p2) { + + if (!Same.sameByValue(secondaryQueryJoins, p2.secondaryQueryJoins)) return false; + if (!Same.sameByValue(included, p2.included)) return false; + if (!Same.sameByNull(filterMany, p2.filterMany)) return false; + if (filterMany != null && !filterMany.isSameByPlan(p2.filterMany)) return false; + + return fetchConfig.equals(p2.fetchConfig); + } + + /** + * Calculate the query plan hash. + */ + public void queryPlanHash(HashQueryPlanBuilder builder) { + + builder.add(path); + builder.addOrdered(included); + builder.add(secondaryQueryJoins); + + builder.add(filterMany != null); + if (filterMany != null) { + filterMany.queryPlanHash(builder); + } + builder.add(fetchConfig.hashCode()); + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesParser.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesParser.java new file mode 100644 index 000000000..3027dc20b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesParser.java @@ -0,0 +1,185 @@ +package com.avaje.ebeaninternal.server.querydefn; + +import com.avaje.ebean.FetchConfig; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; + +import java.util.LinkedHashSet; + +/** + * Parses the path properties string. + */ +public class OrmQueryPropertiesParser { + + private static Response EMPTY = new Response(); + + /** + * Immutable response of the parsed properties and options. + */ + public static class Response { + + final boolean readOnly; + final boolean cache; + final FetchConfig fetchConfig; + final String properties; + final LinkedHashSet included; + + public Response(boolean readOnly, boolean cache, int queryFetchBatch, int lazyFetchBatch, String properties, LinkedHashSet included) { + this.readOnly = readOnly; + this.cache = cache; + this.properties = properties; + this.included = included; + if (lazyFetchBatch > -1 || queryFetchBatch > -1) { + this.fetchConfig = new FetchConfig().lazy(lazyFetchBatch).query(queryFetchBatch); + } else { + this.fetchConfig = OrmQueryProperties.DEFAULT_FETCH; + } + } + + public Response() { + this.readOnly = false; + this.cache = false; + this.fetchConfig = OrmQueryProperties.DEFAULT_FETCH; + this.properties = ""; + this.included = null; + } + } + + /** + * Parses the path properties string returning the parsed properties and options. + * In general it is comma delimited with some special strings like +lazy(20). + */ + public static Response parse(String rawProperties) { + return new OrmQueryPropertiesParser(rawProperties).parse(); + } + + private String inputProperties; + + private String outputProperties = ""; + private boolean allProperties; + private boolean readOnly; + private boolean cache; + private int queryFetchBatch = -1; + private int lazyFetchBatch = -1; + + private OrmQueryPropertiesParser(String inputProperties) { + this.inputProperties = inputProperties; + } + + /** + * Parse the raw string properties input. + */ + private Response parse() { + + if (inputProperties == null || inputProperties.isEmpty()) { + return EMPTY; + } + int pos = inputProperties.indexOf("+readonly"); + if (pos > -1) { + inputProperties = StringHelper.replaceString(inputProperties, "+readonly", ""); + readOnly = true; + } + pos = inputProperties.indexOf("+cache"); + if (pos > -1) { + inputProperties = StringHelper.replaceString(inputProperties, "+cache", ""); + cache = true; + } + pos = inputProperties.indexOf("+query"); + if (pos > -1) { + queryFetchBatch = parseBatchHint(pos, "+query"); + } + pos = inputProperties.indexOf("+lazy"); + if (pos > -1) { + lazyFetchBatch = parseBatchHint(pos, "+lazy"); + } + + LinkedHashSet included = parseIncluded(); + String properties = (allProperties) ? "*" : outputProperties; + return new Response(readOnly, cache, queryFetchBatch, lazyFetchBatch, properties, included); + } + + /** + * Parse the include separating by comma or semicolon. + */ + private LinkedHashSet parseIncluded() { + + inputProperties = inputProperties.trim(); + if (inputProperties.isEmpty()) { + // default properties + return null; + } + if (inputProperties.equals("*")) { + // explicit all properties + allProperties = true; + return null; + } + + String[] res = inputProperties.split(","); + + StringBuilder sb = new StringBuilder(70); + LinkedHashSet propertySet = new LinkedHashSet(res.length * 2); + + int count = 0; + String temp; + for (int i = 0; i < res.length; i++) { + temp = res[i].trim(); + if (temp.length() > 0) { + if (count > 0) { + sb.append(","); + } + sb.append(temp); + propertySet.add(temp); + count++; + } + } + + if (propertySet.isEmpty()) { + // default properties + return null; + } + + if (propertySet.contains("*")) { + // explicit all properties + allProperties = true; + return null; + } + + // partial properties + outputProperties = sb.toString(); + return propertySet; + } + + private int parseBatchHint( int pos, String option) { + + int startPos = pos + option.length(); + int endPos = findEndPos(startPos, inputProperties); + if (endPos == -1) { + inputProperties = StringHelper.replaceString(inputProperties, option, ""); + return 0; + + } else { + + String batchParam = inputProperties.substring(startPos + 1, endPos); + + if (endPos + 1 >= inputProperties.length()) { + inputProperties = inputProperties.substring(0, pos); + } else { + inputProperties = inputProperties.substring(0, pos) + inputProperties.substring(endPos + 1); + } + return Integer.parseInt(batchParam); + } + } + + private int findEndPos(int pos, String props) { + + if (pos < props.length()) { + if (props.charAt(pos) == '(') { + int endPara = props.indexOf(')', pos + 1); + if (endPara == -1) { + throw new RuntimeException("Error could not find ')' in " + props + " after position " + pos); + } + return endPara; + } + } + return -1; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQuerySecondary.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQuerySecondary.java new file mode 100644 index 000000000..85abbb7bc --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQuerySecondary.java @@ -0,0 +1,39 @@ +package com.avaje.ebeaninternal.server.querydefn; + +import com.avaje.ebeaninternal.api.SpiQuerySecondary; + +import java.util.List; + +/** + * The secondary query paths for 'query joins' and 'lazy loading'. + */ +public class OrmQuerySecondary implements SpiQuerySecondary { + + private final List queryJoins; + + private final List lazyJoins; + + /** + * Construct with the 'query join' and 'lazy join' path properties. + */ + public OrmQuerySecondary(List queryJoins, List lazyJoins) { + this.queryJoins = queryJoins; + this.lazyJoins = lazyJoins; + } + + /** + * Return a list of path/properties that are query join loaded. + */ + @Override + public List getQueryJoins() { + return queryJoins; + } + + /** + * Return the list of path/properties that are lazy loaded. + */ + @Override + public List getLazyJoins() { + return lazyJoins; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/SimpleTextParser.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/SimpleTextParser.java index d65a24f97..345503966 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/SimpleTextParser.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/SimpleTextParser.java @@ -16,6 +16,10 @@ public class SimpleTextParser { this.eof = oql.length(); } + public boolean isEmpty() { + return oql.isEmpty(); + } + public int getPos() { return pos; } 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/ebean/ColumnMappingTest.java b/src/test/java/com/avaje/ebean/ColumnMappingTest.java new file mode 100644 index 000000000..6c77bd2da --- /dev/null +++ b/src/test/java/com/avaje/ebean/ColumnMappingTest.java @@ -0,0 +1,65 @@ +package com.avaje.ebean; + +import org.junit.Test; + +import java.util.Arrays; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class ColumnMappingTest { + + RawSql.ColumnMapping.Column col(int indexPos, String dbColumn, String dbAlias) { + return new RawSql.ColumnMapping.Column(indexPos, dbColumn, dbAlias); + } + + RawSql.ColumnMapping mapping(RawSql.ColumnMapping.Column... cols) { + return new RawSql.ColumnMapping(Arrays.asList(cols)); + } + + @Test + public void equals_same() { + + RawSql.ColumnMapping mapping1 = mapping(col(1, "id", null), col(2, "name", null)); + RawSql.ColumnMapping mapping2 = mapping(col(1, "id", null), col(2, "name", null)); + + assertSame(mapping1, mapping2); + } + + @Test + public void equals_diffPropertyName() { + + RawSql.ColumnMapping mapping1 = mapping(col(1, "id", null), col(2, "name", null)); + RawSql.ColumnMapping mapping2 = mapping(col(1, "id", null), col(2, "diff", null)); + + assertDifferent(mapping1, mapping2); + } + + @Test + public void equals_moreColumns() { + + RawSql.ColumnMapping mapping1 = mapping(col(1, "id", null), col(2, "name", null)); + RawSql.ColumnMapping mapping2 = mapping(col(1, "id", null), col(2, "name", null), col(2, "diff", null)); + + assertDifferent(mapping1, mapping2); + } + + + @Test + public void equals_lessColumns() { + + RawSql.ColumnMapping mapping1 = mapping(col(1, "id", null), col(2, "name", null)); + RawSql.ColumnMapping mapping2 = mapping(col(1, "id", null)); + + assertDifferent(mapping1, mapping2); + } + + private void assertSame(Object key, Object key1) { + assertThat(key).isEqualTo(key1); + assertThat(key.hashCode()).isEqualTo(key1.hashCode()); + } + + private void assertDifferent(Object key, Object key1) { + assertThat(key).isNotEqualTo(key1); + assertThat(key.hashCode()).isNotEqualTo(key1.hashCode()); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/ColumnTest.java b/src/test/java/com/avaje/ebean/ColumnTest.java new file mode 100644 index 000000000..d8c9f7d19 --- /dev/null +++ b/src/test/java/com/avaje/ebean/ColumnTest.java @@ -0,0 +1,58 @@ +package com.avaje.ebean; + + +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class ColumnTest { + + RawSql.ColumnMapping.Column col(int indexPos, String dbColumn, String dbAlias) { + return new RawSql.ColumnMapping.Column(indexPos, dbColumn, dbAlias); + } + + @Test + public void equals_when_same() { + assertSame(col(1, "name", null),col(1, "name", null)); + } + + @Test + public void equals_when_same_withAlias() { + assertSame(col(1, "t0.name", "t0"),col(1, "t0.name", "t0")); + } + + @Test + public void equals_when_diffIndex() { + assertDifferent(col(1, "name", null),col(2, "name", null)); + } + + @Test + public void equals_when_diffProperty() { + assertDifferent(col(1, "name", null),col(1, "diffName", null)); + } + + @Test + public void equals_when_diffAlias() { + assertDifferent(col(1, "t1.name", "t1"),col(1, "t1.name", "t0")); + } + + @Test + public void equals_when_diffAliasNullLast() { + assertDifferent(col(1, "t1.name", "t1"),col(1, "t1.name", null)); + } + @Test + public void equals_when_diffAliasNullFirst() { + assertDifferent(col(1, "t1.name", null),col(1, "t1.name", "t1")); + } + + + private void assertSame(Object key, Object key1) { + assertThat(key).isEqualTo(key1); + assertThat(key.hashCode()).isEqualTo(key1.hashCode()); + } + + private void assertDifferent(Object key, Object key1) { + assertThat(key).isNotEqualTo(key1); + assertThat(key.hashCode()).isNotEqualTo(key1.hashCode()); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/FetchConfigTest.java b/src/test/java/com/avaje/ebean/FetchConfigTest.java new file mode 100644 index 000000000..071f0a43f --- /dev/null +++ b/src/test/java/com/avaje/ebean/FetchConfigTest.java @@ -0,0 +1,168 @@ +package com.avaje.ebean; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class FetchConfigTest { + + @Test + public void testLazy() throws Exception { + + FetchConfig config = new FetchConfig().lazy(); + + assertThat(config.getLazyBatchSize()).isEqualTo(0); + assertThat(config.getQueryBatchSize()).isEqualTo(-1); + assertThat(config.isQueryAll()).isEqualTo(false); + } + + @Test + public void testLazy_withParameter() throws Exception { + + FetchConfig config = new FetchConfig().lazy(50); + + assertThat(config.getLazyBatchSize()).isEqualTo(50); + assertThat(config.getQueryBatchSize()).isEqualTo(-1); + assertThat(config.isQueryAll()).isEqualTo(false); + } + + @Test + public void testQuery() throws Exception { + + FetchConfig config = new FetchConfig().query(); + + assertThat(config.getLazyBatchSize()).isEqualTo(-1); + assertThat(config.getQueryBatchSize()).isEqualTo(0); + assertThat(config.isQueryAll()).isEqualTo(true); + } + + @Test + public void testQuery_withParameter() throws Exception { + + FetchConfig config = new FetchConfig().query(50); + + assertThat(config.getLazyBatchSize()).isEqualTo(-1); + assertThat(config.getQueryBatchSize()).isEqualTo(50); + assertThat(config.isQueryAll()).isEqualTo(true); + } + + @Test + public void testQueryFirst() throws Exception { + + FetchConfig config = new FetchConfig().queryFirst(50); + + assertThat(config.getLazyBatchSize()).isEqualTo(-1); + assertThat(config.getQueryBatchSize()).isEqualTo(50); + assertThat(config.isQueryAll()).isEqualTo(false); + } + + @Test + public void testQueryAndLazy_withParameters() throws Exception { + + FetchConfig config = new FetchConfig().query(50).lazy(10); + + assertThat(config.getLazyBatchSize()).isEqualTo(10); + assertThat(config.getQueryBatchSize()).isEqualTo(50); + assertThat(config.isQueryAll()).isEqualTo(false); + } + + @Test + public void testQueryAndLazy() throws Exception { + + FetchConfig config = new FetchConfig().query(50).lazy(); + + assertThat(config.getLazyBatchSize()).isEqualTo(0); + assertThat(config.getQueryBatchSize()).isEqualTo(50); + assertThat(config.isQueryAll()).isEqualTo(false); + } + + + @Test + public void testEquals_when_noOptions() throws Exception { + + assertSame(new FetchConfig(), new FetchConfig()); + } + + @Test + public void testEquals_when_query_50_lazy_40() throws Exception { + + assertSame(new FetchConfig().query(50).lazy(40), new FetchConfig().query(50).lazy(40)); + } + + @Test + public void testEquals_when_query_50_lazy() throws Exception { + + assertSame(new FetchConfig().query(50).lazy(), new FetchConfig().query(50).lazy()); + } + + @Test + public void testEquals_when_query_50() throws Exception { + + assertSame(new FetchConfig().query(50), new FetchConfig().query(50)); + } + + @Test + public void testEquals_when_queryFirst_50_lazy_40() throws Exception { + + assertSame(new FetchConfig().queryFirst(50).lazy(40), new FetchConfig().queryFirst(50).lazy(40)); + } + + @Test + public void testEquals_when_queryFirst_50_lazy() throws Exception { + + assertSame(new FetchConfig().queryFirst(50).lazy(), new FetchConfig().queryFirst(50).lazy()); + } + + @Test + public void testEquals_when_queryFirst_50() throws Exception { + + assertSame(new FetchConfig().queryFirst(50), new FetchConfig().queryFirst(50)); + } + + @Test + public void testNotEquals_when_query_50() throws Exception { + + assertDifferent(new FetchConfig().query(50), new FetchConfig().query(40)); + } + + @Test + public void testNotEquals_when_query_50_lazy() throws Exception { + + assertDifferent(new FetchConfig().query(50), new FetchConfig().query(50).lazy()); + } + + @Test + public void testNotEquals_when_query_50_lazy_40() throws Exception { + + assertDifferent(new FetchConfig().query(50), new FetchConfig().query(50).lazy(40)); + } + + @Test + public void testNotEquals_when_queryFirst_50() throws Exception { + + assertDifferent(new FetchConfig().queryFirst(50), new FetchConfig().queryFirst(40)); + } + + @Test + public void testNotEquals_when_queryFirst_50_lazy() throws Exception { + + assertDifferent(new FetchConfig().queryFirst(50), new FetchConfig().queryFirst(50).lazy()); + } + + @Test + public void testNotEquals_when_queryFirst_50_lazy_40() throws Exception { + + assertDifferent(new FetchConfig().queryFirst(50), new FetchConfig().queryFirst(50).lazy(40)); + } + + void assertDifferent(FetchConfig v1, FetchConfig v2) { + assertThat(v1).isNotEqualTo(v2); + assertThat(v1.hashCode()).isNotEqualTo(v2.hashCode()); + } + + + void assertSame(FetchConfig v1, FetchConfig v2) { + assertThat(v1).isEqualTo(v2); + assertThat(v1.hashCode()).isEqualTo(v2.hashCode()); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/RawSqlKeyTest.java b/src/test/java/com/avaje/ebean/RawSqlKeyTest.java new file mode 100644 index 000000000..c618a2d27 --- /dev/null +++ b/src/test/java/com/avaje/ebean/RawSqlKeyTest.java @@ -0,0 +1,64 @@ +package com.avaje.ebean; + + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RawSqlKeyTest { + + @Test + public void equals_when_sameParsedSql() { + + RawSql.Key key = RawSqlBuilder.parse("select id from customer").create().getKey(); + RawSql.Key key1 = RawSqlBuilder.parse("select id from customer").create().getKey(); + + assertSame(key, key1); + } + + @Test + public void equals_when_diffParsedSql() { + + RawSql.Key key = RawSqlBuilder.parse("select id from customer").create().getKey(); + RawSql.Key key1 = RawSqlBuilder.parse("select name from customer").create().getKey(); + + assertDifferent(key, key1); + } + + @Test + public void equals_when_sameColumnMapping() { + + RawSql.Key key = RawSqlBuilder.parse("select id from customer").columnMapping("id","b").create().getKey(); + RawSql.Key key1 = RawSqlBuilder.parse("select id from customer").columnMapping("id","b").create().getKey(); + + assertSame(key, key1); + } + + @Test + public void equals_when_diffColumnMapping() { + + RawSql.Key key = RawSqlBuilder.parse("select a from customer").columnMapping("a","b").create().getKey(); + RawSql.Key key1 = RawSqlBuilder.parse("select a from customer").columnMapping("a","c").create().getKey(); + + assertDifferent(key, key1); + } + + @Test + public void equals_when_parseToUnpased() { + + RawSql.Key key = RawSqlBuilder.parse("select a from customer").columnMapping("a","b").create().getKey(); + RawSql.Key key1 = RawSqlBuilder.unparsed("select a from customer").columnMapping("a","c").create().getKey(); + + assertDifferent(key, key1); + } + + private void assertSame(RawSql.Key key, RawSql.Key key1) { + assertThat(key).isEqualTo(key1); + assertThat(key.hashCode()).isEqualTo(key1.hashCode()); + } + + private void assertDifferent(RawSql.Key key, RawSql.Key key1) { + assertThat(key).isNotEqualTo(key1); + assertThat(key.hashCode()).isNotEqualTo(key1.hashCode()); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java index b8917b790..6ed966cff 100644 --- a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java +++ b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java @@ -147,21 +147,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer { } - @Override - public SpiOrmQueryRequest createQueryRequest(BeanDescriptor desc, SpiQuery q, Transaction t) { - return null; - } - @Override public CQuery compileQuery(Query query, Transaction t) { return null; } - @Override - public CQueryEngine getQueryEngine() { - return null; - } - @Override public int delete(Query query, Transaction t) { return 0; diff --git a/src/test/java/com/avaje/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java b/src/test/java/com/avaje/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java new file mode 100644 index 000000000..d61015f1e --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java @@ -0,0 +1,288 @@ +package com.avaje.ebeaninternal.server.core; + + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.FetchConfig; +import com.avaje.ebean.Query; +import com.avaje.ebeaninternal.server.querydefn.DefaultOrmQuery; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; +import com.avaje.tests.model.basic.Order; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DefaultServer_createOrmQueryRequestTest extends BaseTestCase { + + Query query() { + return server().find(Order.class); + } + + OrmQueryRequest queryRequest(Query query) { + return OrmQueryRequestTestHelper.queryRequest(query); + } + + OrmQueryDetail detail(Query query) { + return queryRequest(query).getQuery().getDetail(); + } + + @Test + public void when_empty_then_same() { + + assertSame(detail(query()), detail(query())); + } + + @Test + public void select_all_then_same() { + + assertSame(detail(query().select("*")), detail(query().select("*"))); + } + + @Test + public void when_propertiesInOrder_then_same() { + + assertSame(detail(query().select("id,name")), detail(query().select("id,name"))); + } + + @Test + public void when_propertiesInDifferentOrder_then_different() { + + assertDifferent(detail(query().select("id,name")), detail(query().select("name, id"))); + + // but same from autotune perspective + assertThat(detail(query().select("id,name")).isAutoTuneEqual(detail(query().select("name, id")))).isTrue(); + } + + @Test + public void when_additional_fetch_then_different() { + + assertDifferent(detail(query().select("id,name")), detail(query().select("id,name").fetch("details"))); + } + + @Test + public void when_same_fetch_then_same() { + + assertSame(detail(query().select("id,name").fetch("details")), detail(query().select("id,name").fetch("details"))); + } + + @Test + public void when_fetch_order_different_then_different() { + + assertDifferent(detail(query().select("id,name").fetch("details").fetch("customer")), + detail(query().select("id,name").fetch("customer").fetch("details"))); + } + + @Test + public void when_extra_queryFetchToMany_then_same() { + + assertDifferent(detail(query().select("id,name").fetch("customer")), + detail(query().select("id,name").fetch("customer").fetch("details", new FetchConfig().query()))); + } + + @Test + public void when_extra_queryToOne_fetch_then_different() { + + // with the fetch of customer the foreign key must be added to the root query + assertDifferent(detail(query().select("id,name")), + detail(query().select("id,name").fetch("customer", new FetchConfig().query()))); + } + + @Test + public void when_additional_fetch_V2_then_different() { + + assertDifferent(detail(query().select("id,name")), detail(query().select("id,name").fetch("customer", "id"))); + } + + + @Test + public void when_fetchConfig_then_differentPlan() throws Exception { + + DefaultOrmQuery query1 = (DefaultOrmQuery) Ebean.find(Order.class) + .select("status, shipDate") + .fetch("details", "orderQty, unitPrice", new FetchConfig().query()) + .fetch("details.product", "sku, name"); + + + DefaultOrmQuery query2 = (DefaultOrmQuery) Ebean.find(Order.class) + .select("status, shipDate") + .fetch("details", "orderQty, unitPrice") + .fetch("details.product", "sku, name"); + + assertDifferent(detail(query1), detail(query2)); + } + + @Test + public void testJoinOrder_when_fetchJoins_expect_detailJoinsPreserveOrder() { + + Query query = Ebean.find(Order.class) + .select("status, orderDate") + .fetch("customer", "name") + .fetch("details"); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("customer", "details"); + } + + @Test + public void testJoinOrder_when_fetchJoinsAndWhere_expect_fetchJoinsOnlyInFetchPaths() { + + Query query = Ebean.find(Order.class) + .select("status, orderDate") + .fetch("details") + .where().eq("customer.name", "rob").query(); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("details"); + } + + @Test + public void testJoinOrder_when_queryFetch_expect_getFetchPaths_doesNotIncludeQueryJoin() { + + Query query = Ebean.find(Order.class) + .select("status, orderDate") + .fetch("customer", "name") + .fetch("details", new FetchConfig().query()); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("customer"); + } + + @Test + public void testJoinOrder_when_lazyFetch_expect_getFetchPaths_doesNotIncludeQueryJoin() { + + Query query = Ebean.find(Order.class) + .select("status, orderDate") + .fetch("customer", "name") + .fetch("details", new FetchConfig().lazy()); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("customer"); + } + + @Test + public void testJoinOrder_when_lazyFetchAndHasChildren_expect_getFetchPaths_doesNotIncludeJoinOrChild() { + + Query query = Ebean.find(Order.class) + .select("status, orderDate") + .fetch("customer", "name") + .fetch("details", new FetchConfig().lazy()) + .fetch("details.product"); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("customer"); + } + + @Test + public void testJoinOrder_when_fetchMany_expect_getFetchPaths_containsAllInOrder() { + + Query query = Ebean.find(Order.class) + .select("status, orderDate") + .fetch("details") + .fetch("details.product") + .fetch("customer", "name"); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("details", "details.product", "customer"); + } + + @Test + public void testJoinOrder_when_queryJoin_expect_getFetchPaths_excludesQueryJoinAndChildren() { + + Query query = Ebean.find(Order.class) + .select("status, orderDate") + .fetch("details", new FetchConfig().query()) + .fetch("details.product") + .fetch("customer", "name"); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("customer"); + } + + @Test + public void test_removeJoinToMany_when_multipleManyPaths() { + + Query query = Ebean.find(Order.class) + .fetch("details") + .fetch("details.product") + .fetch("customer") + .fetch("customer.contacts"); // second many path + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("details", "details.product", "customer"); + } + + @Test + public void test_removeAllJoinToMany_when_firstRow() { + + Query query = Ebean.find(Order.class) + .setFirstRow(1) + .fetch("details") // many path + .fetch("details.product") + .fetch("customer") + .fetch("customer.contacts"); // many path + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("customer"); + } + + @Test + public void test_removeAllJoinToMany_when_maxRows() { + + Query query = Ebean.find(Order.class) + .setMaxRows(1) + .fetch("details") // many path + .fetch("details.product") + .fetch("customer") + .fetch("customer.contacts"); // many path + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("customer"); + } + + @Test + public void test_removeJoinToMany_when_filterMany() { + + Query query = Ebean.find(Order.class) + .fetch("details") + .fetch("details.product") + .fetch("customer") + .fetch("customer.contacts") + .filterMany("details").eq("orderQuantity", 10) + .query(); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("customer", "customer.contacts"); + } + + private void assertSame(OrmQueryDetail detail1, OrmQueryDetail detail2) { + assertThat(detail1.isSameByPlan(detail2)).isTrue(); + assertThat(detail1.queryPlanHash()).isEqualTo(detail2.queryPlanHash()); + } + + private void assertDifferent(OrmQueryDetail detail1, OrmQueryDetail detail2) { + assertThat(detail1.isSameByPlan(detail2)).isFalse(); + assertThat(detail1.queryPlanHash()).isNotEqualTo(detail2.queryPlanHash()); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/core/OrmQueryRequestTestHelper.java b/src/test/java/com/avaje/ebeaninternal/server/core/OrmQueryRequestTestHelper.java new file mode 100644 index 000000000..453084c6c --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/core/OrmQueryRequestTestHelper.java @@ -0,0 +1,18 @@ +package com.avaje.ebeaninternal.server.core; + +import com.avaje.ebean.Ebean; +import com.avaje.ebean.Query; +import com.avaje.ebeaninternal.api.SpiQuery; + +public class OrmQueryRequestTestHelper { + + static DefaultServer defaultServer = (DefaultServer) Ebean.getDefaultServer(); + + /** + * Create and return a OrmQueryRequest for the given query. + */ + public static OrmQueryRequest queryRequest(Query query) { + return (OrmQueryRequest) defaultServer.createQueryRequest(SpiQuery.Type.LIST, query, null); + } + +} 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 3e6ad2696..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,40 +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); + } + + 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); - BeanQueryRequest request = create(query, desc); HashQueryPlanBuilder builder = new HashQueryPlanBuilder(); - expr.queryPlanHash(request, builder); + 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(); @@ -70,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 7c1b6e8ae..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 { @@ -20,10 +24,10 @@ public class InExpressionTest { InExpression ex2 = new InExpression("bar", values, false); HashQueryPlanBuilder b1 = new HashQueryPlanBuilder(); - ex1.queryPlanHash(null, b1); + ex1.queryPlanHash(b1); HashQueryPlanBuilder b2 = new HashQueryPlanBuilder(); - ex2.queryPlanHash(null, b2); + ex2.queryPlanHash(b2); assertNotEquals(b1.build(), b2.build()); } @@ -38,10 +42,10 @@ public class InExpressionTest { InExpression ex2 = new InExpression("foo", values2, false); HashQueryPlanBuilder b1 = new HashQueryPlanBuilder(); - ex1.queryPlanHash(null, b1); + ex1.queryPlanHash(b1); HashQueryPlanBuilder b2 = new HashQueryPlanBuilder(); - ex2.queryPlanHash(null, b2); + ex2.queryPlanHash(b2); assertNotEquals(b1.build(), b2.build()); } @@ -55,10 +59,10 @@ public class InExpressionTest { InExpression ex2 = new InExpression("foo", values, false); HashQueryPlanBuilder b1 = new HashQueryPlanBuilder(); - ex1.queryPlanHash(null, b1); + ex1.queryPlanHash(b1); HashQueryPlanBuilder b2 = new HashQueryPlanBuilder(); - ex2.queryPlanHash(null, b2); + ex2.queryPlanHash(b2); assertNotEquals(b1.build(), b2.build()); } @@ -72,10 +76,10 @@ public class InExpressionTest { InExpression ex2 = new InExpression("foo", values, true); HashQueryPlanBuilder b1 = new HashQueryPlanBuilder(); - ex1.queryPlanHash(null, b1); + ex1.queryPlanHash(b1); HashQueryPlanBuilder b2 = new HashQueryPlanBuilder(); - ex2.queryPlanHash(null, b2); + ex2.queryPlanHash(b2); assertEquals(b1.build(), b2.build()); } @@ -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/loadcontext/DLoadContextTest.java b/src/test/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContextTest.java new file mode 100644 index 000000000..13dfa8a2d --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContextTest.java @@ -0,0 +1,75 @@ +package com.avaje.ebeaninternal.server.loadcontext; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.FetchConfig; +import com.avaje.ebean.Query; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.core.OrmQueryRequestTestHelper; +import com.avaje.tests.model.basic.Order; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DLoadContextTest extends BaseTestCase { + + OrmQueryRequest queryRequest(Query query) { + return OrmQueryRequestTestHelper.queryRequest(query); + } + + Query query() { + return server().find(Order.class); + } + + @Test + public void construct_when_defaults_expect_defaultLazyBatchSizeServerDefaults() { + + OrmQueryRequest queryRequest = queryRequest(query()); + queryRequest.initTransIfRequired(); + + DLoadContext graphContext = (DLoadContext)queryRequest.getGraphContext(); + DLoadBeanContext customer = graphContext.getBeanContext("customer"); + + assertThat(customer.firstBatchSize).isEqualTo(10); + assertThat(customer.secondaryBatchSize).isEqualTo(10); + } + + @Test + public void construct_when_fetchQuery_expect_100_batchSize() { + + OrmQueryRequest queryRequest = queryRequest(query().fetch("customer",new FetchConfig().query())); + queryRequest.initTransIfRequired(); + + DLoadContext graphContext = (DLoadContext)queryRequest.getGraphContext(); + DLoadBeanContext customer = graphContext.getBeanContext("customer"); + + assertThat(customer.firstBatchSize).isEqualTo(100); + assertThat(customer.secondaryBatchSize).isEqualTo(100); + } + + @Test + public void construct_when_fetchQuery50_expect_50_batchSize() { + + OrmQueryRequest queryRequest = queryRequest(query().fetch("customer",new FetchConfig().query(50))); + queryRequest.initTransIfRequired(); + + DLoadContext graphContext = (DLoadContext)queryRequest.getGraphContext(); + DLoadBeanContext customer = graphContext.getBeanContext("customer"); + + assertThat(customer.firstBatchSize).isEqualTo(50); + assertThat(customer.secondaryBatchSize).isEqualTo(50); + } + + @Test + public void construct_when_fetchQueryFirst20Lazy5_expect_20_5_batchSize() { + + OrmQueryRequest queryRequest = queryRequest(query().fetch("customer",new FetchConfig().queryFirst(20).lazy(5))); + queryRequest.initTransIfRequired(); + + DLoadContext graphContext = (DLoadContext)queryRequest.getGraphContext(); + DLoadBeanContext customer = graphContext.getBeanContext("customer"); + + assertThat(customer.firstBatchSize).isEqualTo(20); + assertThat(customer.secondaryBatchSize).isEqualTo(5); + } + +} \ 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 6539e9b3c..d3016e706 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java @@ -3,9 +3,6 @@ package com.avaje.ebeaninternal.server.querydefn; import com.avaje.ebean.Ebean; import com.avaje.ebean.FetchConfig; -import com.avaje.ebeaninternal.api.HashQueryPlan; -import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; -import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.tests.model.basic.Order; import org.junit.Test; @@ -14,51 +11,47 @@ import static org.assertj.core.api.Assertions.assertThat; public class DefaultOrmQueryTest { @Test - public void testQueryAutofetchHash() throws Exception { + public void when_addWhere_then_planChanges() { - SpiQuery query1 = (SpiQuery)Ebean.find(Order.class) - .select("status, shipDate") - .fetch("details", "orderQty, unitPrice", new FetchConfig().query()) - .fetch("details.product", "sku, name"); - - HashQueryPlanBuilder b1 = new HashQueryPlanBuilder(); - query1.queryAutoTuneHash(b1); - HashQueryPlan hash1 = b1.build(); - - SpiQuery query2 = (SpiQuery)Ebean.find(Order.class) - .select("status, shipDate") - .fetch("details", "orderQty, unitPrice") - .fetch("details.product", "sku, name"); - - HashQueryPlanBuilder b2 = new HashQueryPlanBuilder(); - query2.queryAutoTuneHash(b2); - HashQueryPlan hash2 = b2.build(); - - assertThat(hash1).isNotEqualTo(hash2); + 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.createQueryPlanKey()).isNotEqualTo(q2.createQueryPlanKey()); + assertThat(q1.queryBindHash()).isNotEqualTo(q2.queryBindHash()); } @Test - public void test_AutofetchHash_diffFirstRows() throws Exception { + public void when_sameWhereWithDiffBindValues_then_planSame_bindDiff() { - SpiQuery query1 = (SpiQuery)Ebean.find(Order.class) + 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.createQueryPlanKey()).isEqualTo(q2.createQueryPlanKey()); + assertThat(q1.queryBindHash()).isNotEqualTo(q2.queryBindHash()); + } + + @Test + public void when_sameWhereAndBindValues_then_planSameAndBind() { + + 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.createQueryPlanKey()).isEqualTo(q2.createQueryPlanKey()); + assertThat(q1.queryBindHash()).isEqualTo(q2.queryBindHash()); + } + + @Test + public void when_diffFirstMaxRows_then_differentPlan() throws Exception { + + DefaultOrmQuery query1 = (DefaultOrmQuery)Ebean.find(Order.class) .setFirstRow(0) .setMaxRows(31); - HashQueryPlanBuilder b1 = new HashQueryPlanBuilder(); - query1.queryAutoTuneHash(b1); - HashQueryPlan hash1 = b1.build(); - - SpiQuery query2 = (SpiQuery)Ebean.find(Order.class) + DefaultOrmQuery query2 = (DefaultOrmQuery)Ebean.find(Order.class) .setFirstRow(1) .setMaxRows(0); - HashQueryPlanBuilder b2 = new HashQueryPlanBuilder(); - query2.queryAutoTuneHash(b2); - HashQueryPlan hash2 = b2.build(); - - assertThat(hash1).isNotEqualTo(hash2); - + assertThat(query1.createQueryPlanKey()).isNotEqualTo(query2.createQueryPlanKey()); } } \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParserTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParserTest.java index 0e877728c..3be9f7943 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParserTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParserTest.java @@ -5,114 +5,111 @@ import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; public class OrmQueryDetailParserTest extends BaseTestCase { + OrmQueryDetail parse(String query) { + return new OrmQueryDetailParser(query).parse(); + } + + @Test(expected = NullPointerException.class) + public void parse_when_nullString() throws Exception { + parse(null); + } + + @Test + public void parse_when_emptyString() throws Exception { + assertTrue(parse("").isEmpty()); + } + @Test public void testParseBasic() throws Exception { + OrmQueryDetail detail = parse("select (id,name)"); - OrmQueryDetail other = new OrmQueryDetail(); - other.select("id,name"); - - OrmQueryProperties root = other.getChunk(null, false); + OrmQueryProperties root = detail.getChunk(null, false); assertNull(root.getPath()); - assertThat(root.getAllIncludedProperties()).contains("id", "name"); - - OrmQueryDetailParser p = new OrmQueryDetailParser("select (id,name)"); - OrmQueryDetail detail = p.parse(); - - root = detail.getChunk(null, false); - assertNull(root.getPath()); - assertThat(root.getAllIncludedProperties()).contains("id", "name"); + assertThat(root.getIncluded()).containsExactly("id", "name"); } @Test public void testParseEmptySelect() throws Exception { - OrmQueryDetailParser p = new OrmQueryDetailParser("select fetch customer (email)"); - OrmQueryDetail detail = p.parse(); + OrmQueryDetail detail = parse("select fetch customer (email)"); OrmQueryProperties root = detail.getChunk(null, false); assertNull(root.getPath()); - assertThat(root.getAllIncludedProperties()).isNull(); + assertThat(root.getIncluded()).isNull(); OrmQueryProperties chunk = detail.getChunk("customer", false); assertThat(chunk.getPath()).isEqualTo("customer"); - assertThat(chunk.getAllIncludedProperties()).contains("email"); + assertThat(chunk.getIncluded()).contains("email"); } @Test public void testParseSelectFetch() throws Exception { - OrmQueryDetailParser p = new OrmQueryDetailParser("select (id,name) fetch customer (email)"); - OrmQueryDetail detail = p.parse(); + OrmQueryDetail detail = parse("select (id,name) fetch customer (email)"); OrmQueryProperties root = detail.getChunk(null, false); assertNull(root.getPath()); - assertThat(root.getAllIncludedProperties()).contains("id", "name"); + assertThat(root.getIncluded()).contains("id", "name"); OrmQueryProperties chunk = detail.getChunk("customer", false); assertThat(chunk.getPath()).isEqualTo("customer"); - assertThat(chunk.getAllIncludedProperties()).contains("email"); + assertThat(chunk.getIncluded()).contains("email"); } @Test public void testParseSelectFetchMore() throws Exception { - OrmQueryDetailParser p = new OrmQueryDetailParser("select (id,name) fetch customer (email) fetch details.product (sku,description)"); - OrmQueryDetail detail = p.parse(); + OrmQueryDetail detail = parse("select (id,name) fetch customer (email) fetch details.product (sku,description)"); OrmQueryProperties root = detail.getChunk(null, false); assertNull(root.getPath()); - assertThat(root.getAllIncludedProperties()).contains("id", "name"); + assertThat(root.getIncluded()).contains("id", "name"); OrmQueryProperties chunk = detail.getChunk("customer", false); assertThat(chunk.getPath()).isEqualTo("customer"); - assertThat(chunk.getAllIncludedProperties()).contains("email"); + assertThat(chunk.getIncluded()).contains("email"); chunk = detail.getChunk("details.product", false); assertThat(chunk.getPath()).isEqualTo("details.product"); - assertThat(chunk.getAllIncludedProperties()).contains("sku","description"); + assertThat(chunk.getIncluded()).contains("sku","description"); } @Test public void testParseWithPlusQuery() throws Exception { - OrmQueryDetailParser p = new OrmQueryDetailParser("select (id,name) fetch customer (+query,id,name,email)"); - OrmQueryDetail detail = p.parse(); + OrmQueryDetail detail = parse("select (id,name) fetch customer (+query,id,name,email)"); OrmQueryProperties root = detail.getChunk(null, false); assertNull(root.getPath()); - assertThat(root.getAllIncludedProperties()).contains("id", "name"); + assertThat(root.getIncluded()).contains("id", "name"); OrmQueryProperties chunk = detail.getChunk("customer", false); assertThat(chunk.getPath()).isEqualTo("customer"); - assertThat(chunk.getAllIncludedProperties()).contains("id", "name", "email"); + assertThat(chunk.getIncluded()).contains("id", "name", "email"); assertThat(chunk.isQueryFetch()).isTrue(); - } @Test public void testTuneApply() { - OrmQueryDetailParser p = new OrmQueryDetailParser("select (status) fetch customer (email)"); - OrmQueryDetail detail = p.parse(); - - OrmQueryDetailParser p2 = new OrmQueryDetailParser("select (id,name) fetch customer (+query,id,name,email)"); - OrmQueryDetail tune = p2.parse(); - + OrmQueryDetail detail = parse("select (status) fetch customer (email)"); + OrmQueryDetail tune = parse("select (id,name) fetch customer (+query,id,name,email)"); detail.tuneFetchProperties(tune); OrmQueryProperties root = detail.getChunk(null, false); assertNull(root.getPath()); - assertThat(root.getAllIncludedProperties()).contains("id", "name"); + assertThat(root.getIncluded()).contains("id", "name"); OrmQueryProperties chunk = detail.getChunk("customer", false); assertThat(chunk.getPath()).isEqualTo("customer"); - assertThat(chunk.getAllIncludedProperties()).contains("id", "name", "email"); + assertThat(chunk.getIncluded()).contains("id", "name", "email"); assertThat(chunk.isQueryFetch()).isTrue(); } diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java index 3fa127b73..0ca744e09 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java @@ -1,21 +1,230 @@ package com.avaje.ebeaninternal.server.querydefn; +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.tests.model.basic.Order; import org.junit.Test; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; -public class OrmQueryDetailTest { +public class OrmQueryDetailTest extends BaseTestCase { + OrmQueryDetail parse(String query) { + return new OrmQueryDetailParser(query).parse(); + } @Test - public void test_isAutoTuneEqual() { + public void isEmpty_when_empty() { - OrmQueryDetailParser parser1 = new OrmQueryDetailParser("select (id,name) fetch customer (name) fetch details (code)"); - OrmQueryDetail detail1 = parser1.parse(); + OrmQueryDetail detail = new OrmQueryDetail(); + assertThat(detail.isEmpty()).isTrue(); + } - OrmQueryDetailParser parser2 = new OrmQueryDetailParser("select (id,name) fetch details (code) fetch customer (name)"); - OrmQueryDetail detail2 = parser2.parse(); + @Test + public void isEmpty_when_hasFetch_expect_false() { + + OrmQueryDetail detail = new OrmQueryDetail(); + detail.fetch("customer", null, null); + assertThat(detail.isEmpty()).isFalse(); + } + + @Test + public void isEmpty_when_hasSelect_expect_false() { + + OrmQueryDetail detail = new OrmQueryDetail(); + detail.select("name"); + assertThat(detail.isEmpty()).isFalse(); + } + + @Test + public void isAutoTuneEqual_when_fetchOrderIsDifferent_then_stillEqual() { + + OrmQueryDetail detail1 = parse("select (id,name) fetch customer (name) fetch details (code)"); + OrmQueryDetail detail2 = parse("select (id,name) fetch details (code) fetch customer (name)"); assertTrue(detail1.isAutoTuneEqual(detail2)); } + + @Test + public void isAutoTuneEqual_when_different_select() { + + OrmQueryDetail detail1 = parse("select (id,name) fetch customer (name) fetch details (code)"); + OrmQueryDetail detail2 = parse("select (id) fetch details (code) fetch customer (name)"); + + assertFalse(detail1.isAutoTuneEqual(detail2)); + } + + @Test + public void isAutoTuneEqual_when_different_selectInFetch() { + + OrmQueryDetail detail1 = parse("select (id,name) fetch customer (name) fetch details (code)"); + OrmQueryDetail detail2 = parse("select (id,name) fetch customer (id,name) fetch details (code)"); + + assertFalse(detail1.isAutoTuneEqual(detail2)); + } + + @Test + public void isAutoTuneEqual_when_different_additionalFetch() { + + OrmQueryDetail detail1 = parse("select (id,name) fetch customer (name) fetch details (code)"); + OrmQueryDetail detail2 = parse("select (id,name) fetch customer (name) fetch details (code) fetch customer.contacts"); + + assertFalse(detail1.isAutoTuneEqual(detail2)); + } + + @Test + public void select_whenMultiple() throws Exception { + + OrmQueryDetail other = new OrmQueryDetail(); + other.select("id,name"); + + OrmQueryProperties root = other.getChunk(null, false); + assertNull(root.getPath()); + assertThat(root.getIncluded()).containsExactly("id", "name"); + } + + @Test + public void select_whenOne() throws Exception { + + OrmQueryDetail other = new OrmQueryDetail(); + other.select("name"); + + OrmQueryProperties root = other.getChunk(null, false); + assertNull(root.getPath()); + assertThat(root.getIncluded()).containsExactly("name"); + } + + @Test + public void getFetchPaths_when_noFetches_then_expect_empty() { + + assertThat(new OrmQueryDetail().getFetchPaths()).isEmpty(); + + OrmQueryDetail detail = new OrmQueryDetail(); + detail.select("foo"); + + assertThat(detail.getFetchPaths()).isEmpty(); + } + + @Test + public void getFetchPaths_when_oneFetch() { + + OrmQueryDetail detail = new OrmQueryDetail(); + detail.select("foo"); + detail.fetch("customer", null, null); + + assertThat(detail.getFetchPaths()).containsExactly("customer"); + } + + @Test + public void getFetchPaths_when_multipleFetch_expect_preserveOrder() { + + OrmQueryDetail detail = new OrmQueryDetail(); + detail.select("foo"); + detail.fetch("customer", null, null); + detail.fetch("details", null, null); + + assertThat(detail.getFetchPaths()).containsExactly("customer", "details"); + } + + @Test + public void getFetchPaths_when_multipleFetch_expect_preserveOrder_v2() { + + OrmQueryDetail detail = new OrmQueryDetail(); + detail.select("foo"); + detail.fetch("details", null, null); + detail.fetch("customer", null, null); + detail.fetch("details.product", null, null); + + assertThat(detail.getFetchPaths()).containsExactly("details", "customer", "details.product"); + } + + @Test + public void markQueryJoins_when_allowOne_expect_stillFetchJoin() { + + OrmQueryDetail detail = new OrmQueryDetail(); + detail.fetch("details", null, null); + + detail.markQueryJoins(orderDesc(), null, true); + + assertThat(detail.getChunk("details", false).isQueryFetch()).isFalse(); + } + + @Test + public void markQueryJoins_when_allowNone_expect_queryJoin() { + + OrmQueryDetail detail = new OrmQueryDetail(); + detail.fetch("details", null, null); + + detail.markQueryJoins(orderDesc(), null, false); + + assertThat(detail.getChunk("details", false).isQueryFetch()).isTrue(); + } + + @Test + public void markQueryJoins_when_allowOneButSecond_expect_queryJoin() { + + OrmQueryDetail detail = new OrmQueryDetail(); + detail.fetch("details", null, null); + detail.fetch("customer.contacts", null, null); + + detail.markQueryJoins(orderDesc(), null, true); + + assertThat(detail.getChunk("details", false).isQueryFetch()).isFalse(); + assertThat(detail.getChunk("customer.contacts", false).isQueryFetch()).isTrue(); + } + + @Test + public void markQueryJoins_when_allowNone_expect_bothQueryJoin() { + + OrmQueryDetail detail = new OrmQueryDetail(); + detail.fetch("details", null, null); + detail.fetch("customer.contacts", null, null); + + detail.markQueryJoins(orderDesc(), null, false); + + assertThat(detail.getChunk("details", false).isQueryFetch()).isTrue(); + assertThat(detail.getChunk("customer.contacts", false).isQueryFetch()).isTrue(); + } + + @Test + public void sortFetchPaths_when_missingParent_expect_addsMissing() { + + OrmQueryDetail detail = new OrmQueryDetail(); + detail.fetch("customer.contacts", null, null); + + detail.sortFetchPaths(orderDesc()); + + assertThat(detail.getFetchPaths()).containsExactly("customer","customer.contacts"); + assertThat(detail.getChunk("customer",false).getIncluded()).containsExactly("id"); + } + + @Test + public void sortFetchPaths_when_outOfOrder_expect_correctOrder() { + + OrmQueryDetail detail = new OrmQueryDetail(); + detail.fetch("customer.contacts", "email", null); + detail.fetch("customer", "name", null); + + detail.sortFetchPaths(orderDesc()); + + assertThat(detail.getFetchPaths()).containsExactly("customer","customer.contacts"); + assertThat(detail.getChunk("customer",false).getIncluded()).containsExactly("name"); + } + + @Test + public void sortFetchPaths_when_empty_expect_stillEmpty() { + + OrmQueryDetail detail = new OrmQueryDetail(); + detail.sortFetchPaths(orderDesc()); + + assertThat(detail.getFetchPaths()).isEmpty(); + } + + BeanDescriptor orderDesc() { + return getBeanDescriptor(Order.class); + } + } \ 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..ae1af12d8 --- /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); + assertDifferent(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); + assertDifferent(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 diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesParserTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesParserTest.java new file mode 100644 index 000000000..fbc800db3 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesParserTest.java @@ -0,0 +1,131 @@ +package com.avaje.ebeaninternal.server.querydefn; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OrmQueryPropertiesParserTest { + + @Test + public void when_null() { + + OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse(null); + assertAllDefaults(res); + assertThat(res.properties).isEqualTo(""); + } + + @Test + public void when_empty() throws Exception { + + OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse(""); + assertAllDefaults(res); + assertThat(res.properties).isEqualTo(""); + } + + @Test + public void when_hasStar() throws Exception { + + OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("*"); + assertAllDefaults(res); + assertThat(res.properties).isEqualTo("*"); + } + + @Test + public void when_hasCache() throws Exception { + + OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("+cache"); + assertThat(res.cache).isTrue(); + assertThat(res.included).isNull(); + } + + @Test + public void when_hasCache_first() throws Exception { + + OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("+cache,id"); + assertThat(res.cache).isTrue(); + assertThat(res.included).containsExactly("id"); + } + + @Test + public void when_hasCache_last() throws Exception { + + OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("name,+cache"); + assertThat(res.cache).isTrue(); + assertThat(res.included).containsExactly("name"); + } + + @Test + public void when_hasCache_middle() throws Exception { + + OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("name,+cache, id"); + assertThat(res.cache).isTrue(); + assertThat(res.included).containsExactly("name", "id"); + } + + @Test + public void when_hasReadOnly() throws Exception { + + OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("+readonly"); + assertThat(res.readOnly).isTrue(); + assertThat(res.included).isNull(); + } + + @Test + public void when_hasLazy() throws Exception { + + OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("+lazy"); + assertThat(res.fetchConfig.getLazyBatchSize()).isEqualTo(0); + assertThat(res.included).isNull(); + } + + @Test + public void when_hasLazyValue() throws Exception { + + OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("+lazy(20)"); + assertThat(res.fetchConfig.getLazyBatchSize()).isEqualTo(20); + assertThat(res.included).isNull(); + } + + @Test + public void when_hasLazyValue_last() throws Exception { + + OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("name,+lazy(20)"); + assertThat(res.fetchConfig.getLazyBatchSize()).isEqualTo(20); + assertThat(res.included).containsExactly("name"); + } + + @Test + public void when_hasLazyValue_first() throws Exception { + + OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("+lazy(20),id,name"); + assertThat(res.fetchConfig.getLazyBatchSize()).isEqualTo(20); + assertThat(res.included).containsExactly("id", "name"); + } + + @Test + public void when_allProperties() throws Exception { + + OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("+query(4),+lazy(5)"); + assertThat(res.fetchConfig.getLazyBatchSize()).isEqualTo(5); + assertThat(res.included).isNull(); + } + + @Test + public void when_everything_set() throws Exception { + + OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("id, name +readonly +lazy(20) +query(30) +cache"); + assertThat(res.fetchConfig.getLazyBatchSize()).isEqualTo(20); + assertThat(res.fetchConfig.getQueryBatchSize()).isEqualTo(30); + assertThat(res.readOnly).isTrue(); + assertThat(res.cache).isTrue(); + assertThat(res.included).containsExactly("id", "name"); + } + + private void assertAllDefaults(OrmQueryPropertiesParser.Response res) { + assertThat(res.cache).isFalse(); + assertThat(res.readOnly).isFalse(); + assertThat(res.fetchConfig.getLazyBatchSize()).isEqualTo(-1); + assertThat(res.fetchConfig.getQueryBatchSize()).isEqualTo(-1); + assertThat(res.included).isNull(); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesTest.java new file mode 100644 index 000000000..8e50cc033 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesTest.java @@ -0,0 +1,89 @@ +package com.avaje.ebeaninternal.server.querydefn; + +import org.junit.Test; + +import java.util.LinkedHashSet; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OrmQueryPropertiesTest { + + String append(String prefix, OrmQueryProperties p1) { + StringBuilder sb = new StringBuilder(); + p1.append(prefix, sb); + return sb.toString(); + } + + @Test(expected = IllegalArgumentException.class) + public void construct_with_propertySet_when_null() { + new OrmQueryProperties(null, (LinkedHashSet)null); + } + + @Test + public void construct_with_propertySet_when_empty() { + + OrmQueryProperties p1 = new OrmQueryProperties(null, new LinkedHashSet()); + assertThat(p1.getProperties()).isEqualTo(""); + assertThat(p1.allProperties()).isFalse(); + } + + @Test + public void construct_with_propertySet_when_one() { + + LinkedHashSet set = new LinkedHashSet(); + set.add("name"); + OrmQueryProperties p1 = new OrmQueryProperties(null, set); + + assertThat(p1.getProperties()).isEqualTo("name"); + assertThat(p1.allProperties()).isFalse(); + } + + @Test + public void construct_with_propertySet_when_some() { + + LinkedHashSet set = new LinkedHashSet(); + set.add("id"); + set.add("name"); + set.add("startDate"); + OrmQueryProperties p1 = new OrmQueryProperties(null, set); + + assertThat(p1.getProperties()).isEqualTo("id,name,startDate"); + assertThat(p1.allProperties()).isFalse(); + } + + @Test + public void append_when_empty() { + + OrmQueryProperties p1 = new OrmQueryProperties(); + assertThat(append("select ",p1)).isEqualTo("select "); + } + + @Test + public void append_when_someProperties() { + + OrmQueryProperties p1 = new OrmQueryProperties(null, "id,name"); + assertThat(append("select ",p1)).isEqualTo("select (id,name) "); + } + + @Test + public void append_when_somePropertiesWithOptions() { + + OrmQueryProperties p1 = new OrmQueryProperties(null, "id,name +cache"); + assertThat(append("select ",p1)).isEqualTo("select (id,name +cache) "); + } + + @Test + public void append_when_path_and_emptyProperties() { + + OrmQueryProperties p1 = new OrmQueryProperties("customer", ""); + assertThat(append("fetch ",p1)).isEqualTo("fetch customer "); + } + + @Test + public void append_when_path_and_somePropertiesWithOptions() { + + OrmQueryProperties p1 = new OrmQueryProperties("customer", "id,name +cache"); + assertThat(append("fetch ",p1)).isEqualTo("fetch customer (id,name +cache) "); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/TestQueryLanguage.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/TestQueryLanguage.java index fb0cc9a26..36f9abb03 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/TestQueryLanguage.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/TestQueryLanguage.java @@ -1,15 +1,14 @@ package com.avaje.ebeaninternal.server.querydefn; -import java.util.Set; - -import org.junit.Assert; -import org.junit.Test; - import com.avaje.ebean.BaseTestCase; import com.avaje.ebean.Ebean; import com.avaje.ebean.EbeanServer; import com.avaje.ebeaninternal.server.expression.DefaultExpressionFactory; import com.avaje.tests.model.basic.Order; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Set; public class TestQueryLanguage extends BaseTestCase { @@ -19,7 +18,7 @@ public class TestQueryLanguage extends BaseTestCase { DefaultOrmQuery q = check("find order join customer (id, name)"); OrmQueryDetail detail = q.getDetail(); OrmQueryProperties chunk = detail.getChunk("customer", false); - Set props = chunk.getAllIncludedProperties(); + Set props = chunk.getIncluded(); Assert.assertTrue(props.contains("id")); Assert.assertTrue(props.contains("name")); @@ -27,7 +26,7 @@ public class TestQueryLanguage extends BaseTestCase { q = check("find order join customer(id, name)"); detail = q.getDetail(); chunk = detail.getChunk("customer", false); - props = chunk.getAllIncludedProperties(); + props = chunk.getIncluded(); Assert.assertTrue(props.contains("id")); Assert.assertTrue(props.contains("name")); @@ -37,7 +36,7 @@ public class TestQueryLanguage extends BaseTestCase { q = check("find order join customer(+cache +readonly, id, name)"); detail = q.getDetail(); chunk = detail.getChunk("customer", false); - props = chunk.getAllIncludedProperties(); + props = chunk.getIncluded(); Assert.assertTrue(props.contains("id")); Assert.assertTrue(props.contains("name")); Assert.assertTrue(chunk.isCache()); @@ -46,7 +45,7 @@ public class TestQueryLanguage extends BaseTestCase { q = check("find order join customer(+cache +readonly,id,name)"); detail = q.getDetail(); chunk = detail.getChunk("customer", false); - props = chunk.getAllIncludedProperties(); + props = chunk.getIncluded(); Assert.assertTrue(props.contains("id")); Assert.assertTrue(props.contains("name")); Assert.assertTrue(chunk.isCache()); @@ -55,14 +54,14 @@ public class TestQueryLanguage extends BaseTestCase { q = check("find order(id,status) join customer(+cache +readonly,id,name)"); detail = q.getDetail(); chunk = detail.getChunk("customer", false); - props = chunk.getAllIncludedProperties(); + props = chunk.getIncluded(); Assert.assertTrue(props.contains("id")); Assert.assertTrue(props.contains("name")); Assert.assertTrue(chunk.isCache()); Assert.assertTrue(chunk.isReadOnly()); chunk = detail.getChunk(null, false); - props = chunk.getAllIncludedProperties(); + props = chunk.getIncluded(); Assert.assertTrue(props.contains("id")); Assert.assertTrue(props.contains("status")); Assert.assertFalse(props.contains("orderDate")); @@ -70,7 +69,7 @@ public class TestQueryLanguage extends BaseTestCase { q = check("find order(id,status) join customer(+cache +readonly,id,name) where id > :minId order by status"); detail = q.getDetail(); chunk = detail.getChunk("customer", false); - props = chunk.getAllIncludedProperties(); + props = chunk.getIncluded(); Assert.assertTrue(props.contains("id")); Assert.assertTrue(props.contains("name")); Assert.assertTrue(chunk.isCache()); diff --git a/src/test/java/com/avaje/tests/autofetch/TunedQueryInfoTest.java b/src/test/java/com/avaje/tests/autofetch/TunedQueryInfoTest.java index 99a0ead5f..2d0cc5fcb 100644 --- a/src/test/java/com/avaje/tests/autofetch/TunedQueryInfoTest.java +++ b/src/test/java/com/avaje/tests/autofetch/TunedQueryInfoTest.java @@ -101,7 +101,7 @@ public class TunedQueryInfoTest extends BaseTestCase { @NotNull private TunedQueryInfo createTunedQueryInfo(OrmQueryDetail tunedDetail) { Origin origin = new Origin(); - origin.setDetail(tunedDetail.toString()); + origin.setDetail(tunedDetail.asString()); return new TunedQueryInfo(origin); } diff --git a/src/test/java/com/avaje/tests/query/TestQueryFindIterate.java b/src/test/java/com/avaje/tests/query/TestQueryFindIterate.java index b4ce33d75..9f60c8c51 100644 --- a/src/test/java/com/avaje/tests/query/TestQueryFindIterate.java +++ b/src/test/java/com/avaje/tests/query/TestQueryFindIterate.java @@ -13,6 +13,7 @@ import com.avaje.tests.model.basic.ResetBasicData; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -150,8 +151,8 @@ public class TestQueryFindIterate extends BaseTestCase { List loggedSql = LoggedSqlCollector.stop(); assertEquals("Got SQL: "+loggedSql, 2, loggedSql.size()); - assertTrue(loggedSql.get(0).contains("select t0.id c0, t0.status c1, t0.order_date c2, t1.id c3, t1.name c4, t2.id c5, t2.order_qty c6, t2.ship_qty")); - assertTrue(loggedSql.get(1).contains("select t0.order_id c0, t0.id c1, t0.ship_time c2, t0.cretime c3, t0.updtime c4, t0.version c5, t0.order_id c6 from or_order_ship")); + assertThat(loggedSql.get(0)).contains("select t0.id c0, t0.status c1, t0.order_date c2, t1.id c3, t1.name c4, t2.id c5, t2.order_qty c6, t2.ship_qty"); + assertThat(loggedSql.get(1)).contains("select t0.order_id c0, t0.id c1, t0.ship_time c2, t0.cretime c3, t0.updtime c4, t0.version c5, t0.order_id c6 from or_order_ship"); } @Test(expected=PersistenceException.class) diff --git a/src/test/java/com/avaje/tests/query/TestQueryParsing.java b/src/test/java/com/avaje/tests/query/TestQueryParsing.java index 3b7c35d68..626dec929 100644 --- a/src/test/java/com/avaje/tests/query/TestQueryParsing.java +++ b/src/test/java/com/avaje/tests/query/TestQueryParsing.java @@ -1,37 +1,40 @@ package com.avaje.tests.query; -import org.junit.Assert; -import org.junit.Test; - import com.avaje.ebean.BaseTestCase; import com.avaje.ebean.Ebean; import com.avaje.ebean.Query; import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; import com.avaje.tests.model.basic.Order; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; public class TestQueryParsing extends BaseTestCase { @Test public void test() { - String oq = "find order join customer join customer.contacts join details (+query(4),+lazy(5))"; + String oq = "find order join customer (id, name) join customer.contacts join details (+query(4),+lazy(5))"; Query q = Ebean.createQuery(Order.class, oq); checkQuery(q); - String oq1 = "find order join customer join customer.contacts join details ( +query(4), +lazy(5) )"; + String oq1 = "find order join customer (id, name) join customer.contacts join details ( +query(4), +lazy(5) )"; SpiQuery q1 = (SpiQuery) Ebean.createQuery(Order.class, oq1); checkQuery(q1); - String oq2 = "find order join customer join customer.contacts join details ( +query(4), +lazy(5) , *)"; + String oq2 = "find order join customer (id, name) join customer.contacts join details ( +query(4), +lazy(5) , *)"; SpiQuery q2 = (SpiQuery) Ebean.createQuery(Order.class, oq2); checkQuery(q2); - String oq3 = "find order join customer join customer.contacts join details (+query(4),+lazy(5),*)"; + String oq3 = "find order join customer (id, name) join customer.contacts join details (+query(4),+lazy(5),*)"; SpiQuery q3 = (SpiQuery) Ebean.createQuery(Order.class, oq3); checkQuery(q3); - String oq4 = "find order join customer join customer.contacts join details (+query(4) +lazy(5) *)"; + String oq4 = "find order join customer (id, name) join customer.contacts join details (+query(4) +lazy(5) *)"; SpiQuery q4 = (SpiQuery) Ebean.createQuery(Order.class, oq4); checkQuery(q4); @@ -41,23 +44,25 @@ public class TestQueryParsing extends BaseTestCase { SpiQuery sq = (SpiQuery) q; OrmQueryDetail detail = sq.getDetail(); + assertTrue(detail.getChunk(null, false).allProperties()); - Assert.assertNotNull(detail.getChunk("customer", false)); - Assert.assertFalse(detail.getChunk("customer", false).isQueryFetch()); - Assert.assertFalse(detail.getChunk("customer", false).isLazyFetch()); + assertNotNull(detail.getChunk("customer", false)); + assertFalse(detail.getChunk("customer", false).isQueryFetch()); + assertFalse(detail.getChunk("customer", false).isLazyFetch()); + assertFalse(detail.getChunk("customer", false).allProperties()); + assertEquals("id, name",detail.getChunk("customer", false).getProperties()); - Assert.assertNotNull(detail.getChunk("customer.contacts", false)); - Assert.assertFalse(detail.getChunk("customer.contacts", false).isQueryFetch()); - Assert.assertFalse(detail.getChunk("customer.contacts", false).isLazyFetch()); + assertNotNull(detail.getChunk("customer.contacts", false)); + assertFalse(detail.getChunk("customer.contacts", false).isQueryFetch()); + assertFalse(detail.getChunk("customer.contacts", false).isLazyFetch()); + assertTrue(detail.getChunk("customer.contacts", false).allProperties()); - Assert.assertNotNull(detail.getChunk("details", false)); - Assert.assertTrue(detail.getChunk("details", false).isQueryFetch()); - Assert.assertTrue(detail.getChunk("details", false).isLazyFetch()); - - Assert.assertEquals(4, detail.getChunk("details", false).getQueryFetchBatch()); - Assert.assertEquals(5, detail.getChunk("details", false).getLazyFetchBatch()); - - Assert.assertTrue(detail.getChunk("details", false).allProperties()); + assertNotNull(detail.getChunk("details", false)); + assertTrue(detail.getChunk("details", false).isQueryFetch()); + assertTrue(detail.getChunk("details", false).isLazyFetch()); + assertEquals(4, detail.getChunk("details", false).getQueryFetchBatch()); + assertEquals(5, detail.getChunk("details", false).getLazyFetchBatch()); + assertTrue(detail.getChunk("details", false).allProperties()); } diff --git a/src/test/java/com/avaje/tests/query/other/TestQueryPlanHash.java b/src/test/java/com/avaje/tests/query/other/TestQueryPlanHash.java deleted file mode 100644 index 8e57deb97..000000000 --- a/src/test/java/com/avaje/tests/query/other/TestQueryPlanHash.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.avaje.tests.query.other; - -import org.junit.Assert; -import org.junit.Test; - -import com.avaje.ebean.Ebean; -import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.tests.model.basic.Order; - -public class TestQueryPlanHash { - - @Test - public void test() { - - - HashQueryPlanBuilder builder1 = new HashQueryPlanBuilder(); - - SpiQuery squery = (SpiQuery)Ebean.find(Order.class); - squery.queryAutoTuneHash(builder1); - - squery.where().in("id", 1,2,3); - - HashQueryPlanBuilder builder2 = new HashQueryPlanBuilder(); - squery.queryAutoTuneHash(builder2); - int q1BindHash = squery.queryBindHash(); - - Assert.assertNotSame(builder1.build(), builder2.build()); - - HashQueryPlanBuilder builder3 = new HashQueryPlanBuilder(); - SpiQuery squery2 = (SpiQuery)Ebean.find(Order.class); - squery2.where().in("id", 2,2,3); - - squery2.queryAutoTuneHash(builder3); - int q2BindHash = squery2.queryBindHash(); - - Assert.assertEquals(builder3.build(), builder2.build()); - - Assert.assertTrue(q1BindHash != q2BindHash); - - HashQueryPlanBuilder builder4 = new HashQueryPlanBuilder(); - SpiQuery squery4 = (SpiQuery)Ebean.find(Order.class); - squery4.where().in("name", 2,2,3); - - squery4.queryAutoTuneHash(builder4); - int q4BindHash = squery4.queryBindHash(); - - //different query plan - Assert.assertTrue(!builder3.build().equals(builder4.build())); - - //same bind hash - Assert.assertTrue(q4BindHash == q2BindHash); - - } - -}