From 769e4bb488b509bc069231ce7ac474b80e9f3fb5 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Sat, 13 Feb 2016 20:13:16 +1300 Subject: [PATCH 01/18] #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - initial --- .../ebeaninternal/api/SpiExpression.java | 28 +++++------ .../ebeaninternal/api/SpiExpressionList.java | 16 ++++--- .../server/expression/AbstractExpression.java | 6 +++ .../expression/AllEqualsExpression.java | 21 ++++----- .../server/expression/BetweenExpression.java | 10 ++-- .../expression/BetweenPropertyExpression.java | 14 +++--- .../CaseInsensitiveEqualExpression.java | 8 ++-- .../expression/DefaultExampleExpression.java | 32 +++++++------ .../server/expression/ExistsExpression.java | 19 ++++---- .../server/expression/IdExpression.java | 14 +++--- .../server/expression/IdInExpression.java | 19 ++++---- .../server/expression/InExpression.java | 10 ++-- .../server/expression/InQueryExpression.java | 20 ++++---- .../server/expression/JsonPathExpression.java | 7 +-- .../server/expression/JunctionExpression.java | 21 ++++----- .../server/expression/LikeExpression.java | 9 ++-- .../server/expression/LogicExpression.java | 18 +++---- .../expression/NonPrepareExpression.java | 15 ++++++ .../server/expression/NoopExpression.java | 8 ++-- .../server/expression/NotExpression.java | 15 +++--- .../server/expression/NullExpression.java | 10 ++-- .../server/expression/RawExpression.java | 15 +++--- .../server/expression/SimpleExpression.java | 10 ++-- .../server/querydefn/DefaultOrmQuery.java | 47 ++++++++++--------- .../server/querydefn/OrmQueryDetail.java | 26 +++++----- .../server/querydefn/OrmQueryProperties.java | 21 ++++----- .../util/DefaultExpressionList.java | 19 +++----- .../DefaultExampleExpressionTest.java | 4 +- .../server/expression/InExpressionTest.java | 16 +++---- 29 files changed, 239 insertions(+), 239 deletions(-) create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/NonPrepareExpression.java diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java index 28cdf61c5..d2987fc53 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java @@ -17,32 +17,26 @@ 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(); - + /** * Add some sql to the query. *

@@ -61,7 +55,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. */ diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java index 1cacf0a45..98c7ee1fd 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java @@ -1,13 +1,12 @@ 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. */ @@ -59,13 +58,18 @@ public interface SpiExpressionList extends ExpressionList { * * @return the list of all the bind values in order. */ - ArrayList buildBindValues(SpiExpressionRequest request); - + List buildBindValues(SpiExpressionRequest request); + + /** + * Prepare the expressions contained in the list. For example, compile sub-query expressions etc. + */ + void prepareExpression(BeanQueryRequest request); + /** * Calculate a hash based on the expressions but excluding the actual bind * values. */ - void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder); + void queryPlanHash(HashQueryPlanBuilder builder); /** * Validate all the properties/paths used in this expression list. 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..63b72bcb6 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; @@ -43,6 +44,11 @@ public abstract class AbstractExpression implements SpiExpression { } } + @Override + public void prepareExpression(BeanQueryRequest request) { + // do nothing + } + @Override public void validate(SpiExpressionValidation validation) { validation.validate(getPropertyName()); 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..2b338b234 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java @@ -1,18 +1,16 @@ 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; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; -class AllEqualsExpression implements SpiExpression { +import java.util.Map; +import java.util.Map.Entry; + +class AllEqualsExpression extends NonPrepareExpression { private static final long serialVersionUID = -8691773558205937025L; @@ -26,6 +24,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 +43,7 @@ class AllEqualsExpression implements SpiExpression { } } + @Override public void addBindValues(SpiExpressionRequest request) { if (propMap.isEmpty()) { @@ -57,6 +57,7 @@ class AllEqualsExpression implements SpiExpression { } } + @Override public void addSql(SpiExpressionRequest request) { if (propMap.isEmpty()) { @@ -92,7 +93,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); @@ -104,10 +106,7 @@ class AllEqualsExpression implements SpiExpression { } } - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - queryAutoTuneHash(builder); - } - + @Override public int queryBindHash() { int hc = 31; 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..20aa4dbf5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java @@ -21,25 +21,25 @@ 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 ? "); } - 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(); 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..b3a92dd00 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java @@ -1,9 +1,7 @@ package com.avaje.ebeaninternal.server.expression; -import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.ManyWhereJoins; -import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; @@ -12,7 +10,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 +30,7 @@ class BetweenPropertyExpression implements SpiExpression { return propName; } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { ElPropertyDeploy elProp = desc.getElPropertyDeploy(name(lowProperty)); @@ -55,20 +54,19 @@ class BetweenPropertyExpression implements SpiExpression { 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(); } 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..2492b96c3 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java @@ -41,15 +41,13 @@ class CaseInsensitiveEqualExpression extends AbstractExpression { 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(); } 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..8b642f30b 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,7 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio this.likeType = likeType; } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins whereManyJoins) { if (list != null) { for (int i = 0; i < list.size(); i++) { @@ -94,31 +95,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 +141,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 +153,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 +171,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++) { 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..7071bdb48 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java @@ -28,19 +28,17 @@ public class ExistsExpression implements SpiExpression { this.not = not; } - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { - builder.add(ExistsExpression.class).add(not); - - subQuery.queryAutoTuneHash(builder); - } - - 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); + } - queryAutoTuneHash(builder); + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { + builder.add(ExistsExpression.class).add(not); + subQuery.queryAutoTuneHash(builder); } /** @@ -52,10 +50,12 @@ public class ExistsExpression implements SpiExpression { return ebeanServer.compileQuery(subQuery, queryRequest.getTransaction()); } + @Override public int queryBindHash() { return subQuery.queryBindHash(); } + @Override public void addSql(SpiExpressionRequest request) { String subSelect = compiledSubQuery.getGeneratedSql(); @@ -69,6 +69,7 @@ public class ExistsExpression implements SpiExpression { request.append(") "); } + @Override public void addBindValues(SpiExpressionRequest request) { List bindParams = compiledSubQuery.getPredicates().getWhereExprBindValues(); 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..27493d159 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java @@ -1,6 +1,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.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 +24,7 @@ class IdExpression implements SpiExpression { /** * Always returns false. */ + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { } @@ -34,6 +34,7 @@ class IdExpression implements SpiExpression { // always valid } + @Override public void addBindValues(SpiExpressionRequest request) { // 'flatten' EmbeddedId and multiple Id cases @@ -45,6 +46,7 @@ class IdExpression implements SpiExpression { } } + @Override public void addSql(SpiExpressionRequest request) { DefaultExpressionRequest r = (DefaultExpressionRequest) request; @@ -56,15 +58,13 @@ 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(); } 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..cff55539a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java @@ -1,21 +1,19 @@ package com.avaje.ebeaninternal.server.expression; -import java.util.List; - -import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.ManyWhereJoins; -import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; 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,15 +75,13 @@ 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(); } 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..477f46ad3 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java @@ -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) { @@ -88,15 +90,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++) { 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..1934fc3cf 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java @@ -28,18 +28,15 @@ class InQueryExpression extends AbstractExpression { this.not = not; } - public void queryAutoTuneHash(HashQueryPlanBuilder builder) { - builder.add(InQueryExpression.class).add(propName).add(not); - subQuery.queryAutoTuneHash(builder); + @Override + public void prepareExpression(BeanQueryRequest request) { + compiledSubQuery = compileSubQuery(request); } - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { - - // queryPlanHash executes prior to addSql() or addBindValues() - // ... so compiledQuery will exist - compiledSubQuery = compileSubQuery(request); - - queryAutoTuneHash(builder); + @Override + public void queryPlanHash(HashQueryPlanBuilder builder) { + builder.add(InQueryExpression.class).add(propName).add(not); + subQuery.queryAutoTuneHash(builder); } /** @@ -51,10 +48,12 @@ 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(); @@ -70,6 +69,7 @@ class InQueryExpression extends AbstractExpression { request.append(") "); } + @Override public void addBindValues(SpiExpressionRequest request) { List bindParams = compiledSubQuery.getPredicates().getWhereExprBindValues(); 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..547fa7001 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java @@ -58,15 +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); - } - @Override public int queryBindHash() { int hc = (value == null) ? 0 : value.hashCode(); 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..d4d88ffad 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java @@ -131,24 +131,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); } } 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..cf3e49ae0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java @@ -1,7 +1,6 @@ package com.avaje.ebeaninternal.server.expression; import com.avaje.ebean.LikeType; -import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.server.el.ElPropertyValue; @@ -61,15 +60,13 @@ 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(); } 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..71d4fe100 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java @@ -74,19 +74,19 @@ 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) { + 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); } public int queryBindHash() { 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..e72bbf19a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NonPrepareExpression.java @@ -0,0 +1,15 @@ +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 + } +} 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..6fe932fe3 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java @@ -26,13 +26,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 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..21c6c2dbe 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java @@ -40,17 +40,18 @@ final class NotExpression implements SpiExpression { request.append(") "); } + @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); } public int queryBindHash() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java index bcbab597d..c84784501 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java @@ -20,10 +20,12 @@ class NullExpression extends AbstractExpression { this.notNull = notNull; } + @Override public void addBindValues(SpiExpressionRequest request) { } + @Override public void addSql(SpiExpressionRequest request) { String propertyName = getPropertyName(); @@ -42,14 +44,12 @@ class NullExpression extends AbstractExpression { /** * 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..7b37424e1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java @@ -1,14 +1,12 @@ 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; -class RawExpression implements SpiExpression { +class RawExpression extends NonPrepareExpression { private static final long serialVersionUID = 7973903141340334606L; @@ -21,6 +19,7 @@ class RawExpression implements SpiExpression { this.values = values; } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { } @@ -30,6 +29,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 +38,7 @@ class RawExpression implements SpiExpression { } } + @Override public void addSql(SpiExpressionRequest request) { request.append(sql); } @@ -45,14 +46,12 @@ 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(); } 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..20e4cd6f4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java @@ -24,6 +24,7 @@ public class SimpleExpression extends AbstractExpression { return Op.EQ.equals(type); } + @Override public void addBindValues(SpiExpressionRequest request) { ElPropertyValue prop = getElProp(request); @@ -50,6 +51,7 @@ public class SimpleExpression extends AbstractExpression { request.addBindValue(value); } + @Override public void addSql(SpiExpressionRequest request) { String propertyName = getPropertyName(); @@ -72,15 +74,13 @@ public class SimpleExpression extends AbstractExpression { /** * 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(); } 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..f21eae234 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -759,7 +759,7 @@ 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) { + private HashQueryPlan calculateHash(HashQueryPlanBuilder builder) { // exclude bind values and things unrelated to the sql being generated @@ -780,31 +780,17 @@ public class DefaultOrmQuery implements SpiQuery { builder.add(rootTableAlias); if (detail != null) { - detail.queryPlanHash(request, builder); + detail.queryPlanHash(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); - } + if (whereExpressions != null) { + whereExpressions.queryPlanHash(builder); + } + if (havingExpressions != null) { + havingExpressions.queryPlanHash(builder); } return builder.build(); @@ -816,7 +802,7 @@ public class DefaultOrmQuery implements SpiQuery { */ public HashQueryPlan queryAutoTuneHash(HashQueryPlanBuilder builder) { - return calculateHash(null, builder); + return calculateHash(builder); } /** @@ -830,10 +816,25 @@ public class DefaultOrmQuery implements SpiQuery { */ public HashQueryPlan queryPlanHash(BeanQueryRequest request) { - queryPlanHash = calculateHash(request, null); + prepareExpressions(request); + + queryPlanHash = calculateHash(null); return queryPlanHash; } + /** + * Prepare the expressions (compile sub-queries etc). + */ + private void prepareExpressions(BeanQueryRequest request) { + + if (whereExpressions != null) { + whereExpressions.prepareExpression(request); + } + if (havingExpressions != null) { + havingExpressions.prepareExpression(request); + } + } + /** * Calculate a hash based on the bind values used in the query. *

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..4140c607e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -1,5 +1,14 @@ package com.avaje.ebeaninternal.server.querydefn; +import com.avaje.ebean.FetchConfig; +import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; +import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; +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; @@ -11,17 +20,6 @@ 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; -import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; -import com.avaje.ebeaninternal.server.el.ElPropertyValue; -import com.avaje.ebeaninternal.server.query.SplitName; - /** * Represents the internal structure of an Object Relational query. *

@@ -65,17 +63,17 @@ public class OrmQueryDetail implements Serializable { /** * Calculate the hash for the query plan. */ - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { + public void queryPlanHash(HashQueryPlanBuilder builder) { if (baseProps == null) { builder.add(false); } else { builder.add(true); - baseProps.queryPlanHash(request, builder); + baseProps.queryPlanHash(builder); } if (fetchPaths != null) { for (OrmQueryProperties p : fetchPaths.values()) { - p.queryPlanHash(request, builder); + p.queryPlanHash(builder); } } } 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..75171d86e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -1,18 +1,9 @@ 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; @@ -23,6 +14,14 @@ import com.avaje.ebeaninternal.server.lib.util.StringHelper; import com.avaje.ebeaninternal.server.query.SplitName; import com.avaje.ebeaninternal.util.FilterExpressionList; +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; + /** * Represents the Properties of an Object Relational query. */ @@ -334,7 +333,7 @@ public class OrmQueryProperties implements Serializable { * Calculate the query plan hash. */ @SuppressWarnings("unchecked") - public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { + public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(path); if (properties != null) { @@ -344,7 +343,7 @@ public class OrmQueryProperties implements Serializable { } builder.add(filterMany != null); if (filterMany != null) { - filterMany.queryPlanHash(request, builder); + filterMany.queryPlanHash(builder); } builder.add(lazyFetchBatch); builder.add(queryFetchBatch); diff --git a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java index 7715da72c..313c31288 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java @@ -349,15 +349,10 @@ public class DefaultExpressionList implements SpiExpressionList { 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,11 +361,10 @@ 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); } } @@ -380,8 +374,7 @@ public class DefaultExpressionList implements SpiExpressionList { 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; } 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..4cdade37c 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java @@ -42,8 +42,10 @@ public class DefaultExampleExpressionTest extends BaseTestCase { BeanQueryRequest request = create(query, desc); + expr.prepareExpression(request); + HashQueryPlanBuilder builder = new HashQueryPlanBuilder(); - expr.queryPlanHash(request, builder); + expr.queryPlanHash(builder); TDSpiExpressionRequest req = new TDSpiExpressionRequest(desc); expr.addBindValues(req); 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..6930d0942 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/expression/InExpressionTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/InExpressionTest.java @@ -20,10 +20,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 +38,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 +55,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 +72,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()); } From ca944eb7563aff8a0dfcbf7307f19713f353d04e Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Sat, 13 Feb 2016 20:52:41 +1300 Subject: [PATCH 02/18] #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - refactor InQuery and Exists query --- .../api/SpiExpressionRequest.java | 6 ++-- .../server/deploy/IntersectionRow.java | 3 +- .../server/expression/ExistsExpression.java | 33 ++++++++----------- .../server/expression/InQueryExpression.java | 25 ++++++-------- .../server/query/CQueryPredicates.java | 20 +++++------ .../util/DefaultExpressionList.java | 2 +- .../util/DefaultExpressionRequest.java | 5 +-- 7 files changed, 41 insertions(+), 53 deletions(-) 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/server/deploy/IntersectionRow.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/IntersectionRow.java index 68e0d5474..a55a88115 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; @@ -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/expression/ExistsExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java index 7071bdb48..1c5c1852e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java @@ -21,7 +21,9 @@ public class ExistsExpression implements SpiExpression { private final SpiQuery subQuery; - private transient CQuery compiledSubQuery; + private List bindParams; + + private String sql; public ExistsExpression(SpiQuery subQuery, boolean not) { this.subQuery = subQuery; @@ -30,26 +32,26 @@ public class ExistsExpression implements SpiExpression { @Override public void prepareExpression(BeanQueryRequest request) { - // queryPlanHash executes prior to addSql() or addBindValues() - // ... so compiledQuery will exist - compiledSubQuery = compileSubQuery(request); - } - @Override - public void queryPlanHash(HashQueryPlanBuilder builder) { - builder.add(ExistsExpression.class).add(not); - subQuery.queryAutoTuneHash(builder); + CQuery subQuery = compileSubQuery(request); + this.bindParams = subQuery.getPredicates().getWhereExprBindValues(); + this.sql = subQuery.getGeneratedSql().replace('\n', ' '); } /** * Compile/build the sub query. */ private 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(); @@ -58,26 +60,17 @@ public class ExistsExpression implements SpiExpression { @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)); } 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 1934fc3cf..21ba19cc2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java @@ -20,7 +20,9 @@ class InQueryExpression extends AbstractExpression { private final SpiQuery subQuery; - private transient CQuery compiledSubQuery; + private List bindParams; + + private String sql; public InQueryExpression(String propertyName, SpiQuery subQuery, boolean not) { super(propertyName); @@ -30,13 +32,16 @@ class InQueryExpression extends AbstractExpression { @Override public void prepareExpression(BeanQueryRequest request) { - compiledSubQuery = compileSubQuery(request); + + CQuery subQuery = compileSubQuery(request); + this.bindParams = subQuery.getPredicates().getWhereExprBindValues(); + this.sql = subQuery.getGeneratedSql().replace('\n', ' '); } @Override public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(InQueryExpression.class).add(propName).add(not); - subQuery.queryAutoTuneHash(builder); + builder.add(sql).add(bindParams.size()); } /** @@ -56,28 +61,18 @@ class InQueryExpression extends AbstractExpression { @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(getPropertyName()).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)); } 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..152ac6712 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; @@ -24,6 +14,14 @@ 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 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/util/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java index 313c31288..7df81fc96 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java @@ -340,7 +340,7 @@ public class DefaultExpressionList implements SpiExpressionList { } @Override - public ArrayList buildBindValues(SpiExpressionRequest request) { + public List buildBindValues(SpiExpressionRequest request) { for (int i = 0, size = list.size(); i < size; i++) { SpiExpression expression = list.get(i); diff --git a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java index 510d62ba0..739071934 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java @@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.util; 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; @@ -140,7 +141,7 @@ public class DefaultExpressionRequest implements SpiExpressionRequest { return sql.toString(); } - public ArrayList getBindValues() { + public List getBindValues() { return bindValues; } From 076f8a03b909fc5165fc94b03c57cee1fac599b6 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Sat, 13 Feb 2016 21:22:52 +1300 Subject: [PATCH 03/18] #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - move to protected calculateQueryPlanHash() --- .../com/avaje/ebeaninternal/api/SpiQuery.java | 10 --- .../server/querydefn/DefaultOrmQuery.java | 18 +---- .../server/querydefn/DefaultOrmQueryTest.java | 67 +++++++++++-------- .../tests/query/other/TestQueryPlanHash.java | 56 ---------------- 4 files changed, 42 insertions(+), 109 deletions(-) delete mode 100644 src/test/java/com/avaje/tests/query/other/TestQueryPlanHash.java diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java index b96a08987..a03634ff0 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -466,16 +466,6 @@ 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). - *

- * Excludes bind values and occurs prior to AutoTune potentially - * tuning/modifying the query. - *

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

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 f21eae234..7f3dea20d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -759,13 +759,10 @@ public class DefaultOrmQuery implements SpiQuery { /** * Calculate the query hash for either AutoTune query tuning or Query Plan caching. */ - private HashQueryPlan calculateHash(HashQueryPlanBuilder builder) { + HashQueryPlan calculateQueryPlanHash() { // exclude bind values and things unrelated to the sql being generated - - if (builder == null) { - builder = new HashQueryPlanBuilder(); - } + HashQueryPlanBuilder builder = new HashQueryPlanBuilder(); builder.add((type == null ? 0 : type.ordinal() + 1)); builder.add(autoTuned).add(distinct).add(sqlDistinct).add(query); @@ -796,15 +793,6 @@ public class DefaultOrmQuery implements SpiQuery { return builder.build(); } - /** - * 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). - */ - public HashQueryPlan queryAutoTuneHash(HashQueryPlanBuilder builder) { - - return calculateHash(builder); - } - /** * Calculate a hash that should be unique for the generated SQL across a given bean type. *

@@ -818,7 +806,7 @@ public class DefaultOrmQuery implements SpiQuery { prepareExpressions(request); - queryPlanHash = calculateHash(null); + queryPlanHash = calculateQueryPlanHash(); return queryPlanHash; } 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..e3c5dbf9a 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,65 @@ 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) + DefaultOrmQuery q1 = (DefaultOrmQuery)Ebean.find(Order.class).where().in("name", "a","b","c").query(); + DefaultOrmQuery q2 = (DefaultOrmQuery)Ebean.find(Order.class).where().in("id", 2,2,3).query(); + + assertThat(q1.calculateQueryPlanHash()).isNotEqualTo(q2.calculateQueryPlanHash()); + assertThat(q1.queryBindHash()).isNotEqualTo(q2.queryBindHash()); + } + + @Test + public void when_sameWhereWithDiffBindValues_then_planSame_bindDiff() { + + DefaultOrmQuery q1 = (DefaultOrmQuery)Ebean.find(Order.class).where().in("id", 1,2,3).query(); + DefaultOrmQuery q2 = (DefaultOrmQuery)Ebean.find(Order.class).where().in("id", 2,2,3).query(); + + assertThat(q1.calculateQueryPlanHash()).isEqualTo(q2.calculateQueryPlanHash()); + assertThat(q1.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.calculateQueryPlanHash()).isEqualTo(q2.calculateQueryPlanHash()); + assertThat(q1.queryBindHash()).isEqualTo(q2.queryBindHash()); + } + + + @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"); - 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) .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); - + assertThat(query1.calculateQueryPlanHash()).isNotEqualTo(query2.calculateQueryPlanHash()); } @Test - public void test_AutofetchHash_diffFirstRows() throws Exception { + public void when_diffFirstMaxRows_then_differentPlan() throws Exception { - SpiQuery query1 = (SpiQuery)Ebean.find(Order.class) + 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.calculateQueryPlanHash()).isNotEqualTo(query2.calculateQueryPlanHash()); } } \ No newline at end of file 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); - - } - -} From 165d4aca921583cc15a8f4e3e19ec530847ce482 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Mon, 15 Feb 2016 10:21:22 +1300 Subject: [PATCH 04/18] #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - remove unused --- .../server/querydefn/OrmQueryProperties.java | 12 ------------ 1 file changed, 12 deletions(-) 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 75171d86e..271c1dd68 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -317,18 +317,6 @@ 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. */ From 3d91a4f9278022972b9c778db89c692cebe5f7dc Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Mon, 15 Feb 2016 23:44:56 +1300 Subject: [PATCH 05/18] #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - sameByPlan() & sameByBind() --- .../ebeaninternal/api/CQueryPlanKey.java | 14 + .../avaje/ebeaninternal/api/HashQuery.java | 18 +- .../api/HashQueryPlanBuilder.java | 25 +- .../ebeaninternal/api/SpiExpression.java | 16 + .../ebeaninternal/api/SpiExpressionList.java | 53 +-- .../com/avaje/ebeaninternal/api/SpiQuery.java | 12 +- .../server/core/DefaultServer.java | 2 +- .../server/core/OrmQueryRequest.java | 42 +- .../server/deploy/BeanDescriptor.java | 8 +- .../server/deploy/IntersectionRow.java | 2 +- .../server/deploy/TableJoin.java | 26 ++ .../server/deploy/TableJoinColumn.java | 143 +++--- .../server/expression/AbstractExpression.java | 20 +- .../expression/AllEqualsExpression.java | 48 +- .../server/expression/BetweenExpression.java | 22 +- .../expression/BetweenPropertyExpression.java | 19 + .../CaseInsensitiveEqualExpression.java | 26 +- .../expression/DefaultExampleExpression.java | 45 ++ .../expression}/DefaultExpressionList.java | 68 ++- .../expression}/DefaultExpressionRequest.java | 17 +- .../server/expression/ExistsExpression.java | 48 +- .../expression}/FilterExpressionList.java | 3 +- .../server/expression/IdExpression.java | 11 +- .../server/expression/IdInExpression.java | 25 +- .../server/expression/InExpression.java | 33 +- .../server/expression/InQueryExpression.java | 44 +- .../server/expression/JsonPathExpression.java | 25 +- .../server/expression/JunctionExpression.java | 48 +- .../server/expression/LikeExpression.java | 27 +- .../server/expression/LogicExpression.java | 40 +- .../expression/NonPrepareExpression.java | 5 + .../server/expression/NoopExpression.java | 15 + .../server/expression/NotExpression.java | 30 +- .../server/expression/NullExpression.java | 21 +- .../server/expression/RawExpression.java | 28 ++ .../ebeaninternal/server/expression/Same.java | 33 ++ .../server/expression/SimpleExpression.java | 34 +- .../server/query/CQueryPlan.java | 33 +- .../server/query/CQueryPlanStats.java | 2 +- .../server/query/CQueryPredicates.java | 2 +- .../server/query/RawSqlQueryPlanKey.java | 56 +++ .../server/querydefn/DefaultOrmQuery.java | 62 +-- .../server/querydefn/OrmQueryPlanKey.java | 149 ++++++ .../server/querydefn/OrmQueryProperties.java | 2 +- .../java/com/avaje/ebean/BaseTestCase.java | 13 + .../server/deploy/TableJoinColumnTest.java | 49 ++ .../server/deploy/TableJoinTest.java | 67 +++ .../expression/AllEqualsExpressionTest.java | 118 +++++ .../server/expression/BaseExpressionTest.java | 13 + .../expression/BetweenExpressionTest.java | 69 +++ .../BetweenPropertyExpressionTest.java | 45 ++ .../CaseInsensitiveEqualExpressionTest.java | 50 ++ .../DefaultExampleExpressionTest.java | 78 +++- .../expression/DefaultExpressionListTest.java | 98 ++++ .../expression/ExistsExpressionTest.java | 85 ++++ .../server/expression/IdExpressionTest.java | 41 ++ .../server/expression/IdInExpressionTest.java | 67 +++ .../server/expression/InExpressionTest.java | 72 ++- .../expression/InQueryExpressionTest.java | 89 ++++ .../expression/JsonPathExpressionTest.java | 81 ++++ .../expression/JunctionExpressionTest.java | 60 +++ .../server/expression/LikeExpressionTest.java | 66 +++ .../expression/LogicExpressionTest.java | 78 ++++ ...QueryTest.java => NoopExpressionTest.java} | 28 +- .../server/expression/NotExpressionTest.java | 58 +++ .../server/expression/NullExpressionTest.java | 91 ++++ .../server/expression/RawExpressionTest.java | 50 ++ .../server/expression/SameTest.java | 29 ++ .../expression/SimpleExpressionTest.java | 58 +++ .../server/query/RawSqlQueryPlanKeyTest.java | 61 +++ .../server/querydefn/DefaultOrmQueryTest.java | 10 +- .../server/querydefn/OrmQueryPlanKeyTest.java | 432 ++++++++++++++++++ 72 files changed, 3119 insertions(+), 339 deletions(-) create mode 100644 src/main/java/com/avaje/ebeaninternal/api/CQueryPlanKey.java rename src/main/java/com/avaje/ebeaninternal/{util => server/expression}/DefaultExpressionList.java (92%) rename src/main/java/com/avaje/ebeaninternal/{util => server/expression}/DefaultExpressionRequest.java (93%) rename src/main/java/com/avaje/ebeaninternal/{util => server/expression}/FilterExpressionList.java (96%) create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/Same.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/RawSqlQueryPlanKey.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKey.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumnTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/deploy/TableJoinTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpressionTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/BaseExpressionTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/BetweenExpressionTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpressionTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpressionTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionListTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/ExistsExpressionTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/IdExpressionTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/IdInExpressionTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/InQueryExpressionTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/JsonPathExpressionTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/JunctionExpressionTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/LikeExpressionTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/LogicExpressionTest.java rename src/test/java/com/avaje/ebeaninternal/server/expression/{NoopExpressionQueryTest.java => NoopExpressionTest.java} (64%) create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/NotExpressionTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/NullExpressionTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/RawExpressionTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/SameTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/SimpleExpressionTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/query/RawSqlQueryPlanKeyTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java diff --git a/src/main/java/com/avaje/ebeaninternal/api/CQueryPlanKey.java b/src/main/java/com/avaje/ebeaninternal/api/CQueryPlanKey.java new file mode 100644 index 000000000..b169dca82 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/CQueryPlanKey.java @@ -0,0 +1,14 @@ +package com.avaje.ebeaninternal.api; + +/** + * Key used for caching query plans for ORM and RawSql queries. + */ +public interface CQueryPlanKey { + + /** + * Used by read audit such that we can log read audit entries without the full sql + * (which would make the read audit logs verbose). + */ + String getPartialKey(); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/HashQuery.java b/src/main/java/com/avaje/ebeaninternal/api/HashQuery.java index 0a560a57b..5d8f6f3ae 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/HashQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/HashQuery.java @@ -5,32 +5,18 @@ package com.avaje.ebeaninternal.api; */ public class HashQuery { - private final HashQueryPlan planHash; + private final CQueryPlanKey planHash; private final int bindHash; /** * Create the HashQuery. */ - public HashQuery(HashQueryPlan planHash, int bindHash) { + public HashQuery(CQueryPlanKey planHash, int bindHash) { this.planHash = planHash; this.bindHash = bindHash; } - /** - * Return the query plan hash. - */ - public HashQueryPlan getPlanHash() { - return planHash; - } - - /** - * Return the bind values hash. - */ - public int getBindHash() { - return bindHash; - } - public int hashCode() { int hc = 31 * planHash.hashCode(); hc = 31 * hc + bindHash; diff --git a/src/main/java/com/avaje/ebeaninternal/api/HashQueryPlanBuilder.java b/src/main/java/com/avaje/ebeaninternal/api/HashQueryPlanBuilder.java index ecbe059c1..9365d6b20 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/HashQueryPlanBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/api/HashQueryPlanBuilder.java @@ -9,14 +9,12 @@ public class HashQueryPlanBuilder { private int bindCount; - private String rawSql; - public HashQueryPlanBuilder() { this.planHash = 92821; } public String toString() { - return planHash+":"+bindCount+(rawSql != null ? ":r" : ""); + return planHash+":"+bindCount; } /** @@ -58,19 +56,24 @@ public class HashQueryPlanBuilder { bindCount += extraBindCount; } - /** - * Add raw sql to the hash. - */ - public void addRawSql(String rawSql) { - this.rawSql = rawSql; + public void bindIfNotNull(Object someValue) { + if (someValue != null) { + bindCount++; + } } /** * Build and return the calculated HashQueryPlan. */ - public HashQueryPlan build() { - return new HashQueryPlan(rawSql, planHash, bindCount); + public String build() { + return planHash+"_"+bindCount; } - + public int getPlanHash() { + return planHash; + } + + public int getBindCount() { + return bindCount; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java index d2987fc53..bacd508de 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java @@ -37,6 +37,16 @@ public interface SpiExpression extends Expression { */ int queryBindHash(); + /** + * Return true if the expression is the same without taking into account bind values. + */ + boolean isSameByPlan(SpiExpression other); + + /** + * Return true if the expression is the same with respect to bind values. + */ + boolean isSameByBind(SpiExpression other); + /** * Add some sql to the query. *

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

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

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

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

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

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

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

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

- *

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

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

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

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

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

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

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

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

- *

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

+ * Prepare the query which prepares any expressions (sub-query expressions etc) and calculates the query plan key. */ - public HashQueryPlan queryPlanHash(BeanQueryRequest request) { + public CQueryPlanKey prepare(BeanQueryRequest request) { prepareExpressions(request); - queryPlanHash = calculateQueryPlanHash(); - return queryPlanHash; + queryPlanKey = createQueryPlanKey(); + return queryPlanKey; } /** @@ -852,7 +830,7 @@ public class DefaultOrmQuery implements SpiQuery { // so queryPlanHash is calculated well before this method is called int hc = queryBindHash(); - return new HashQuery(queryPlanHash, hc); + return new HashQuery(queryPlanKey, hc); } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKey.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKey.java new file mode 100644 index 000000000..7f7f7a41e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKey.java @@ -0,0 +1,149 @@ +package com.avaje.ebeaninternal.server.querydefn; + +import com.avaje.ebean.OrderBy; +import com.avaje.ebean.RawSql; +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.CQueryPlanKey; +import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.deploy.TableJoin; + +/** + * Query plan key for ORM queries. + */ +public class OrmQueryPlanKey implements CQueryPlanKey { + + private final TableJoin includeTableJoin; + private final String orderByAsSting; + private final OrmQueryDetail detail; + private final BindParams bindParams; + private final SpiExpression whereExpressions; + private final SpiExpression havingExpressions; + private final RawSql rawSql; + private final boolean hasIdValue; + + private final SpiQuery.Type type; + private final int maxRows; + private final int firstRow; + private final boolean disableLazyLoading; + private final String rawWhereClause; + private final String query; + private final String additionalWhere; + private final String additionalHaving; + private final boolean distinct; + private final boolean sqlDistinct; + private final String mapKey; + private final SpiQuery.TemporalMode temporalMode; + private final boolean forUpdate; + private final String rootTableAlias; + + private final int planHash; + private final int bindCount; + + public OrmQueryPlanKey(TableJoin includeTableJoin, SpiQuery.Type type, OrmQueryDetail detail, int maxRows, int firstRow, boolean disableLazyLoading, String rawWhereClause, OrderBy orderBy, String query, String additionalWhere, String additionalHaving, boolean distinct, boolean sqlDistinct, String mapKey, Object id, BindParams bindParams, SpiExpression whereExpressions, SpiExpression havingExpressions, SpiQuery.TemporalMode temporalMode, boolean forUpdate, String rootTableAlias, RawSql rawSql) { + + this.includeTableJoin = includeTableJoin; + this.type = type; + this.detail = detail; + this.maxRows = maxRows; + this.firstRow = firstRow; + this.disableLazyLoading = disableLazyLoading; + this.rawWhereClause = rawWhereClause; + this.orderByAsSting = (orderBy == null) ? null : orderBy.toStringFormat(); + this.query = query; + this.additionalWhere = additionalWhere; + this.additionalHaving = additionalHaving; + this.distinct = distinct; + this.sqlDistinct = sqlDistinct; + this.mapKey = mapKey; + this.hasIdValue = (id != null); + this.bindParams = bindParams; + this.whereExpressions = whereExpressions; + this.havingExpressions = havingExpressions; + this.temporalMode = temporalMode; + this.forUpdate = forUpdate; + this.rootTableAlias = rootTableAlias; + this.rawSql = rawSql; + + // exclude bind values and things unrelated to the sql being generated + HashQueryPlanBuilder builder = new HashQueryPlanBuilder(); + + builder.add((type == null ? 0 : type.ordinal() + 1)); + builder.add(distinct).add(sqlDistinct).add(query); + builder.add(firstRow).add(maxRows); + builder.add(orderBy).add(forUpdate); + builder.add(rawWhereClause).add(additionalWhere).add(additionalHaving); + builder.add(mapKey); + builder.add(disableLazyLoading); + builder.add(hasIdValue); + builder.add(temporalMode); + builder.add(rawSql == null ? 0 : rawSql.queryHash()); + builder.add(includeTableJoin != null ? includeTableJoin.queryHash() : 0); + builder.add(rootTableAlias); + + if (detail != null) { + detail.queryPlanHash(builder); + } + if (bindParams != null) { + bindParams.buildQueryPlanHash(builder); + } + if (whereExpressions != null) { + whereExpressions.queryPlanHash(builder); + } + if (havingExpressions != null) { + havingExpressions.queryPlanHash(builder); + } + + this.planHash = builder.getPlanHash(); + this.bindCount = builder.getBindCount(); + } + + @Override + public String getPartialKey() { + return planHash + "_" + bindCount; + } + + @Override + public int hashCode() { + return planHash; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + OrmQueryPlanKey that = (OrmQueryPlanKey) o; + + if (planHash != that.planHash) return false; + if (bindCount != that.bindCount) return false; + + if (maxRows != that.maxRows) return false; + if (firstRow != that.firstRow) return false; + if (disableLazyLoading != that.disableLazyLoading) return false; + + if (distinct != that.distinct) return false; + if (sqlDistinct != that.sqlDistinct) return false; + if (forUpdate != that.forUpdate) return false; + if (hasIdValue != that.hasIdValue) return false; + if (type != that.type) return false; + if (temporalMode != that.temporalMode) return false; + if (includeTableJoin != null ? !includeTableJoin.equals(that.includeTableJoin) : that.includeTableJoin != null) return false; + if (orderByAsSting != null ? !orderByAsSting.equals(that.orderByAsSting) : that.orderByAsSting != null) return false; + + if (whereExpressions != null ? !whereExpressions.isSameByPlan(that.whereExpressions) : that.whereExpressions != null) return false; + if (havingExpressions != null ? !havingExpressions.isSameByPlan(that.havingExpressions) : that.havingExpressions != null) return false; + +// if (detail != null ? !detail.equals(that.detail) : that.detail != null) return false; +// if (bindParams != null ? !bindParams.equals(that.bindParams) : that.bindParams != null) return false; +// if (rawSql != null ? !rawSql.equals(that.rawSql) : that.rawSql != null) return false; + + if (rawWhereClause != null ? !rawWhereClause.equals(that.rawWhereClause) : that.rawWhereClause != null) return false; + if (query != null ? !query.equals(that.query) : that.query != null) return false; + if (additionalWhere != null ? !additionalWhere.equals(that.additionalWhere) : that.additionalWhere != null) return false; + if (additionalHaving != null ? !additionalHaving.equals(that.additionalHaving) : that.additionalHaving != null) return false; + if (mapKey != null ? !mapKey.equals(that.mapKey) : that.mapKey != null) return false; + return rootTableAlias != null ? rootTableAlias.equals(that.rootTableAlias) : that.rootTableAlias == null; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java index 271c1dd68..467c99468 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -12,7 +12,7 @@ import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.server.expression.FilterExprPath; import com.avaje.ebeaninternal.server.lib.util.StringHelper; import com.avaje.ebeaninternal.server.query.SplitName; -import com.avaje.ebeaninternal.util.FilterExpressionList; +import com.avaje.ebeaninternal.server.expression.FilterExpressionList; import java.io.Serializable; import java.util.ArrayList; diff --git a/src/test/java/com/avaje/ebean/BaseTestCase.java b/src/test/java/com/avaje/ebean/BaseTestCase.java index 07bda7c83..cbc438435 100644 --- a/src/test/java/com/avaje/ebean/BaseTestCase.java +++ b/src/test/java/com/avaje/ebean/BaseTestCase.java @@ -1,6 +1,7 @@ package com.avaje.ebean; import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import org.avaje.agentloader.AgentLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,4 +30,16 @@ public class BaseTestCase { SpiEbeanServer spi = (SpiEbeanServer)Ebean.getDefaultServer(); return spi.getDatabasePlatform().getName().equals("h2"); } + + protected BeanDescriptor getBeanDescriptor(Class cls) { + return spiEbeanServer().getBeanDescriptor(cls); + } + + protected SpiEbeanServer spiEbeanServer() { + return (SpiEbeanServer) Ebean.getDefaultServer(); + } + + protected EbeanServer server() { + return Ebean.getDefaultServer(); + } } diff --git a/src/test/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumnTest.java b/src/test/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumnTest.java new file mode 100644 index 000000000..9c9f9679d --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumnTest.java @@ -0,0 +1,49 @@ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoinColumn; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TableJoinColumnTest { + + TableJoinColumn col(String localDbColumn, String foreignDbColumn, boolean insertable, boolean updateable) { + DeployTableJoinColumn column = new DeployTableJoinColumn(localDbColumn, foreignDbColumn, insertable, updateable); + return new TableJoinColumn(column); + } + + @Test + public void equals_when_same() { + assertSame(col("a", "b", true, true), col("a", "b", true, true)); + } + + @Test + public void equals_when_diffFirstCol() { + assertDifferent(col("a", "b", true, true), col("c", "b", true, true)); + } + + @Test + public void equals_when_diffSecondCol() { + assertDifferent(col("a", "b", true, true), col("a", "c", true, true)); + } + + @Test + public void equals_when_diffInsertable() { + assertDifferent(col("a", "b", true, true), col("a", "b", false, true)); + } + + @Test + public void equals_when_diffUpdateable() { + assertDifferent(col("a", "b", true, true), col("a", "b", true, false)); + } + + private void assertDifferent(TableJoinColumn col, TableJoinColumn col2) { + assertThat(col).isNotEqualTo(col2); + assertThat(col.hashCode()).isNotEqualTo(col2.hashCode()); + } + + private void assertSame(TableJoinColumn col, TableJoinColumn col2) { + assertThat(col).isEqualTo(col2); + assertThat(col.hashCode()).isEqualTo(col2.hashCode()); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/deploy/TableJoinTest.java b/src/test/java/com/avaje/ebeaninternal/server/deploy/TableJoinTest.java new file mode 100644 index 000000000..0b0758ca3 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/deploy/TableJoinTest.java @@ -0,0 +1,67 @@ +package com.avaje.ebeaninternal.server.deploy; + + +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoin; +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoinColumn; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TableJoinTest { + + DeployTableJoinColumn col(String localDbColumn, String foreignDbColumn, boolean insertable, boolean updateable) { + return new DeployTableJoinColumn(localDbColumn, foreignDbColumn, insertable, updateable); + } + + TableJoin table(String tableName, String col1, String col2) { + DeployTableJoin deploy = new DeployTableJoin(); + deploy.setTable(tableName); + deploy.addJoinColumn(col(col1, col2, true, true)); + return new TableJoin(deploy); + } + + TableJoin table(String tableName, String col1, String col2, String col3, String col4) { + DeployTableJoin deploy = new DeployTableJoin(); + deploy.setTable(tableName); + deploy.addJoinColumn(col(col1, col2, true, true)); + deploy.addJoinColumn(col(col3, col4, true, true)); + return new TableJoin(deploy); + } + + @Test + public void equals_when_same() { + assertSame(table("myTable", "a", "b"), table("myTable", "a", "b")); + } + + @Test + public void equals_when_diffTable() { + assertDifferent(table("myTable", "a", "b"), table("diffTable", "a", "b")); + } + + @Test + public void equals_when_diffColumn() { + assertDifferent(table("myTable", "a", "b"), table("myTable", "c", "b")); + } + + @Test + public void equals_when_moreColumns() { + assertDifferent(table("myTable", "a", "b"), table("myTable", "a", "b", "c", "d")); + } + + @Test + public void equals_when_lessColumns() { + assertDifferent(table("myTable", "a", "b", "c", "d"), table("myTable", "a", "b")); + } + + void assertSame(TableJoin join1, TableJoin join2) { + assertThat(join1).isEqualTo(join1); + assertThat(join1.hashCode()).isEqualTo(join1.hashCode()); + } + + + void assertDifferent(TableJoin join1, TableJoin join2) { + assertThat(join1).isNotEqualTo(join2); + assertThat(join1.hashCode()).isNotEqualTo(join2.hashCode()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpressionTest.java new file mode 100644 index 000000000..619519225 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpressionTest.java @@ -0,0 +1,118 @@ +package com.avaje.ebeaninternal.server.expression; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class AllEqualsExpressionTest { + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp("a", 10).isSameByPlan(exp("a", 10))).isTrue(); + } + + @Test + public void isSameByBind_when_sameMultiple() { + + assertThat(exp("a", 10, "b", "23").isSameByBind(exp("a", 10, "b", "23"))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBindValue() { + + assertThat(exp("a", 10).isSameByPlan(exp("a", 20))).isTrue(); + } + + @Test + public void isSameByPlan_when_multiple() { + + assertThat(exp("a", 10, "b", 20, "c", 30).isSameByPlan(exp("a", 10, "b", 20, "c", 30))).isTrue(); + } + + @Test + public void isSameByPlan_when_less() { + + assertThat(exp("a", 10, "b", 20, "c", 30).isSameByPlan(exp("a", 10, "b", 20))).isFalse(); + } + + @Test + public void isSameByPlan_when_more() { + + assertThat(exp("a", 10, "b", 20).isSameByPlan(exp("a", 10, "b", 20, "c", 30))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffProperty_diff() { + + assertThat(exp("a", 10).isSameByPlan(exp("b", 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffType_diff() { + + assertThat(exp("a", 10).isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByPlan_when_diffBindByNull_last() { + + assertThat(exp("a", 10).isSameByPlan(exp("a", null))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffBindByNull_first() { + + assertThat(exp("a", null).isSameByPlan(exp("a", 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_differentExpressionType() { + + assertThat(exp("a", null).isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByBind_when_diffBindByNull_last() { + + assertThat(exp("a", 10).isSameByBind(exp("a", null))).isFalse(); + } + + @Test + public void isSameByBind_when_diffBindByNull_first() { + + assertThat(exp("a", null).isSameByBind(exp("a", 10))).isFalse(); + } + + + @NotNull + private AllEqualsExpression exp(Map propMap) { + return new AllEqualsExpression(propMap); + } + + AllEqualsExpression exp(String key0, Object val1) { + LinkedHashMap map = new LinkedHashMap(); + map.put(key0, val1); + return exp(map); + } + + + AllEqualsExpression exp(String key0, Object val1, String key2, Object val2) { + LinkedHashMap map = new LinkedHashMap(); + map.put(key0, val1); + map.put(key2, val2); + return exp(map); + } + + AllEqualsExpression exp(String key0, Object val1, String key2, Object val2, String key3, Object val3) { + LinkedHashMap map = new LinkedHashMap(); + map.put(key0, val1); + map.put(key2, val2); + map.put(key3, val3); + return exp(map); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/BaseExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/BaseExpressionTest.java new file mode 100644 index 000000000..5da0e4876 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/BaseExpressionTest.java @@ -0,0 +1,13 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.tests.model.basic.Order; + +public abstract class BaseExpressionTest extends BaseTestCase { + + protected DefaultExpressionRequest newExpressionRequest() { + BeanDescriptor desc = getBeanDescriptor(Order.class); + return new DefaultExpressionRequest(desc); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/BetweenExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/BetweenExpressionTest.java new file mode 100644 index 000000000..bec8bca44 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/BetweenExpressionTest.java @@ -0,0 +1,69 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebeaninternal.api.SpiExpression; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class BetweenExpressionTest extends BaseExpressionTest { + + @Test + public void addSql() throws Exception { + + DefaultExpressionRequest expReq = newExpressionRequest(); + + BetweenExpression exp = new BetweenExpression("startDate", 1, 2); + exp.addSql(expReq); + + assertThat(expReq.getSql()).isEqualTo("startDate between ? and ? "); + } + + @Test + public void copyForPlanKey_isSameInstance() throws Exception { + + BetweenExpression exp = new BetweenExpression("startDate", 1, 2); + SpiExpression other = exp.copyForPlanKey(); + + assertThat(exp).isSameAs(other); + } + + @Test + public void isSameByPlan_when_properties_match() throws Exception { + + BetweenExpression exp0 = new BetweenExpression("startDate", 1, 2); + BetweenExpression exp1 = new BetweenExpression("startDate", 3, 4); + + assertThat(exp0.isSameByPlan(exp1)).isTrue(); + assertThat(exp1.isSameByPlan(exp0)).isTrue(); + } + + @Test + public void isSameByPlan_when_properties_do_not_match() throws Exception { + + BetweenExpression exp0 = new BetweenExpression("startDate", 1, 2); + BetweenExpression exp1 = new BetweenExpression("endDate", 1, 2); + + assertThat(exp0.isSameByPlan(exp1)).isFalse(); + assertThat(exp1.isSameByPlan(exp0)).isFalse(); + } + + @Test + public void isSameByBind_when_values_do_not_match() throws Exception { + + BetweenExpression exp0 = new BetweenExpression("startDate", 1, 2); + BetweenExpression exp1 = new BetweenExpression("startDate", 1, 3); + + assertThat(exp0.isSameByBind(exp1)).isFalse(); + assertThat(exp1.isSameByBind(exp0)).isFalse(); + } + + @Test + public void isSameByBind_when_values_match() throws Exception { + + BetweenExpression exp0 = new BetweenExpression("startDate", 1, 2); + BetweenExpression exp1 = new BetweenExpression("startDate", 1, 2); + + assertThat(exp0.isSameByBind(exp1)).isTrue(); + assertThat(exp1.isSameByBind(exp0)).isTrue(); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpressionTest.java new file mode 100644 index 000000000..1e08d89d5 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpressionTest.java @@ -0,0 +1,45 @@ +package com.avaje.ebeaninternal.server.expression; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class BetweenPropertyExpressionTest { + + @NotNull + private BetweenPropertyExpression exp(String lowProperty, String highProperty, Object value) { + return new BetweenPropertyExpression(lowProperty, highProperty, value); + } + + @Test + public void isSameByPlan_when_same() { + assertThat(exp("a", "b", 10).isSameByPlan(exp("a", "b", 10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffValue() { + assertThat(exp("a", "b", 10).isSameByPlan(exp("a", "b", 20))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffProperty() { + assertThat(exp("a", "b", 10).isSameByPlan(exp("a", "c", 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffExpressionType() { + assertThat(exp("a", "b", 10).isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByBind_when_same() { + assertThat(exp("a", "b", 10).isSameByBind(exp("a", "b", 10))).isTrue(); + } + + @Test + public void isSameByBind_when_diff() { + assertThat(exp("a", "b", 10).isSameByBind(exp("a", "b", 20))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpressionTest.java new file mode 100644 index 000000000..03d8c388e --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpressionTest.java @@ -0,0 +1,50 @@ +package com.avaje.ebeaninternal.server.expression; + + +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class CaseInsensitiveEqualExpressionTest { + + CaseInsensitiveEqualExpression exp(String propName, String value) { + return new CaseInsensitiveEqualExpression(propName, value); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp("a", "10").isSameByPlan(exp("a", "10"))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_same() { + + assertThat(exp("a", "10").isSameByPlan(exp("a", "20"))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffProperty_diff() { + + assertThat(exp("a", "10").isSameByPlan(exp("b", "10"))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffType_diff() { + + assertThat(exp("a", "10").isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp("a", "10").isSameByBind(exp("b", "10"))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp("a", "10").isSameByBind(exp("b", "20"))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java index 4cdade37c..3c0bc757a 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java @@ -1,13 +1,11 @@ package com.avaje.ebeaninternal.server.expression; import com.avaje.ebean.BaseTestCase; -import com.avaje.ebean.Ebean; import com.avaje.ebean.LikeType; import com.avaje.ebean.Query; import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; -import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.server.core.OrmQueryRequest; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; @@ -22,42 +20,68 @@ import static org.assertj.core.api.Assertions.assertThat; public class DefaultExampleExpressionTest extends BaseTestCase { - @Test - public void test() { - - SpiEbeanServer server = (SpiEbeanServer)Ebean.getDefaultServer(); - BeanDescriptor desc = server.getBeanDescriptor(Customer.class); - - SpiQuery query = (SpiQuery)server.find(Customer.class); - + Customer customer() { Address address = new Address(); address.setCity("billingAddress.city"); Customer customer = new Customer(); customer.setName("name"); customer.setBillingAddress(address); + return customer; + } + DefaultExampleExpression exp() { + Customer customer = customer(); + return new DefaultExampleExpression((EntityBean)customer, false, LikeType.EQUAL_TO); + } - DefaultExampleExpression expr = new DefaultExampleExpression((EntityBean)customer, false, LikeType.EQUAL_TO); + DefaultExampleExpression expExtra() { + Customer customer = customer(); + customer.setSmallnote("smallNote"); + return new DefaultExampleExpression((EntityBean)customer, false, LikeType.EQUAL_TO); + } + DefaultExampleExpression expDiffName() { + Customer customer = customer(); + customer.setName("otherName"); + return new DefaultExampleExpression((EntityBean)customer, false, LikeType.EQUAL_TO); + } - BeanQueryRequest request = create(query, desc); + BeanDescriptor customerBeanDescriptor() { + return getBeanDescriptor(Customer.class); + } + + DefaultExampleExpression prepare(DefaultExampleExpression expr) { + + SpiQuery query = (SpiQuery)spiEbeanServer().find(Customer.class); + BeanQueryRequest request = create(query, customerBeanDescriptor()); expr.prepareExpression(request); + return expr; + } + + @Test + public void test() { + + DefaultExampleExpression expr = exp(); + + prepare(expr); + HashQueryPlanBuilder builder = new HashQueryPlanBuilder(); expr.queryPlanHash(builder); - TDSpiExpressionRequest req = new TDSpiExpressionRequest(desc); + TDSpiExpressionRequest req = new TDSpiExpressionRequest(customerBeanDescriptor()); expr.addBindValues(req); assertThat(req.bindValues).contains("name", "billingAddress.city"); - address.setCity("Auckland"); + Customer customer = customer(); customer.setName("Rob"); + customer.getBillingAddress().setCity("Auckland"); ResetBasicData.reset(); - Query query1 = server.find(Customer.class) + Query query1 = server().find(Customer.class) .where().exampleLike(customer) .query(); @@ -72,4 +96,28 @@ public class DefaultExampleExpressionTest extends BaseTestCase { return new OrmQueryRequest(null, null, query, desc, null); } + @Test + public void isSameByPlan_whenSame() { + + assertThat(prepare(exp()).isSameByPlan(prepare(exp()))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBindValue_stillSame() { + + assertThat(prepare(exp()).isSameByPlan(prepare(expDiffName()))).isTrue(); + } + + @Test + public void isSameByPlan_when_extraExpression_then_different() { + + assertThat(prepare(exp()).isSameByPlan(prepare(expExtra()))).isFalse(); + } + + @Test + public void isSameByPlan_when_lessExpression_then_different() { + + assertThat(prepare(expExtra()).isSameByPlan(prepare(exp()))).isFalse(); + } + } \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionListTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionListTest.java new file mode 100644 index 000000000..a5a688fd8 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionListTest.java @@ -0,0 +1,98 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.ExpressionList; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class DefaultExpressionListTest { + + + DefaultExpressionList exp() { + + return new DefaultExpressionList(null, new DefaultExpressionFactory(true), null); + } + + DefaultExpressionList spi(ExpressionList list) { + return (DefaultExpressionList)list; + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(spi(exp().eq("a", 10).eq("b", 20)) + .isSameByPlan(spi(exp().eq("a", 10).eq("b", 20)))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffExpressionType() { + + assertThat(spi(exp().eq("a", 10).eq("b", 20)) + .isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByPlan_when_less() { + + assertThat(spi(exp().eq("a", 10).eq("b", 20)) + .isSameByPlan(spi(exp().eq("a", 10)))).isFalse(); + } + + @Test + public void isSameByPlan_when_lessEmptyLast() { + + assertThat(spi(exp().eq("a", 10).eq("b", 20)) + .isSameByPlan(spi(exp()))).isFalse(); + } + + @Test + public void isSameByPlan_when_lessEmptyFirst() { + + assertThat(spi(exp()) + .isSameByPlan(spi(exp().eq("a", 10)))).isFalse(); + } + + @Test + public void isSameByPlan_when_more() { + + assertThat(spi(exp().eq("a", 10).eq("b", 20)) + .isSameByPlan(spi(exp().eq("a", 10).eq("b", 20).eq("c", 30)))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffProperties() { + + assertThat(spi(exp().eq("a", 10).eq("b", 20)) + .isSameByPlan(spi(exp().eq("c", 10).eq("b", 20)))).isFalse(); + } + + + @Test + public void isSameByBind_when_same() { + + assertThat(spi(exp().eq("a", 10).eq("b", 20)) + .isSameByBind(spi(exp().eq("a", 10).eq("b", 20)))).isTrue(); + } + + @Test + public void isSameByBind_when_diffValues() { + + assertThat(spi(exp().eq("a", 10).eq("b", 20)) + .isSameByBind(spi(exp().eq("a", 10).eq("b", 30)))).isFalse(); + } + + @Test + public void isSameByBind_when_less() { + + assertThat(spi(exp().eq("a", 10)) + .isSameByBind(spi(exp().eq("a", 10).eq("b", 20)))).isFalse(); + } + + @Test + public void isSameByBind_when_more() { + + assertThat(spi(exp().eq("a", 10)) + .isSameByBind(spi(exp().eq("a", 10).eq("b", 20)))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/ExistsExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/ExistsExpressionTest.java new file mode 100644 index 000000000..b3ff17e81 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/ExistsExpressionTest.java @@ -0,0 +1,85 @@ +package com.avaje.ebeaninternal.server.expression; + + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.util.Arrays; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class ExistsExpressionTest { + + + @NotNull + private ExistsExpression exp(boolean not, String sql, Object... bindValues) { + return new ExistsExpression(not, sql, Arrays.asList(bindValues)); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp(true,"a", 10).isSameByPlan(exp(true,"a", 10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_same() { + + assertThat(exp(true,"a", 10).isSameByPlan(exp(true,"a", 20))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffNot() { + + assertThat(exp(true,"a", 10).isSameByPlan(exp(false,"a", 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffSql() { + + assertThat(exp(true,"a", 10).isSameByPlan(exp(true,"b", 10))).isFalse(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp(true,"a", 10).isSameByBind(exp(true,"a", 10))).isTrue(); + } + + @Test + public void isSameByBind_when_sameMultipleBindValues() { + + assertThat(exp(true,"a", 10, "ABC", 20).isSameByBind(exp(true,"a", 10, "ABC", 20))).isTrue(); + } + + @Test + public void isSameByBind_when_diffMultipleBindValues() { + + assertThat(exp(true,"a", 10, "ABC", 20).isSameByBind(exp(true,"a", 10, "ABC", 21))).isFalse(); + } + + @Test + public void isSameByBind_when_diffMultipleBindValuesByOrder() { + + assertThat(exp(true,"a", 10, "ABC", 20).isSameByBind(exp(true,"a", 10, 20, "ABC"))).isFalse(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp(true,"a", 10).isSameByBind(exp(true,"a", 20))).isFalse(); + } + + @Test + public void isSameByBind_when_lessBindValues() { + + assertThat(exp(true,"a", 10, 20).isSameByBind(exp(true,"a", 20))).isFalse(); + } + + @Test + public void isSameByBind_when_moreBindValues() { + + assertThat(exp(true,"a", 10).isSameByBind(exp(true,"a", 10, 20))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/IdExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/IdExpressionTest.java new file mode 100644 index 000000000..e4836e0b9 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/IdExpressionTest.java @@ -0,0 +1,41 @@ +package com.avaje.ebeaninternal.server.expression; + + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class IdExpressionTest { + + + @NotNull + private IdExpression exp(Object value) { + return new IdExpression(value); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp(10).isSameByPlan(exp(10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_same() { + + assertThat(exp(10).isSameByPlan(exp(20))).isTrue(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp(10).isSameByBind(exp(10))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp(10).isSameByBind(exp(20))).isFalse(); + assertThat(exp(10).isSameByBind(exp("junk"))).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/IdInExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/IdInExpressionTest.java new file mode 100644 index 000000000..b1b2ab7b2 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/IdInExpressionTest.java @@ -0,0 +1,67 @@ +package com.avaje.ebeaninternal.server.expression; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.util.Arrays; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class IdInExpressionTest { + + + @NotNull + private IdInExpression exp(Object... values) { + return new IdInExpression(Arrays.asList(values)); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp(10).isSameByPlan(exp(10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_same() { + + assertThat(exp(10).isSameByPlan(exp(20))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBindCount() { + + assertThat(exp(10).isSameByPlan(exp(10, 20))).isFalse(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp(10).isSameByBind(exp(10))).isTrue(); + } + + @Test + public void isSameByBind_when_mulitpleSameBindValues() { + + assertThat(exp(10,"ABC", 20).isSameByBind(exp(10, "ABC", 20))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp(10).isSameByBind(exp("junk"))).isFalse(); + } + + @Test + public void isSameByBind_when_lessBindValues() { + + assertThat(exp(10,"ABC", 20).isSameByBind(exp(10, "ABC"))).isFalse(); + } + + + @Test + public void isSameByBind_when_moreBindValues() { + + assertThat(exp(10,"ABC").isSameByBind(exp(10, "ABC", 30))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/InExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/InExpressionTest.java index 6930d0942..b26b09917 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/expression/InExpressionTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/InExpressionTest.java @@ -1,12 +1,16 @@ package com.avaje.ebeaninternal.server.expression; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import static org.junit.Assert.*; +import static org.assertj.core.api.StrictAssertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; public class InExpressionTest { @@ -87,4 +91,70 @@ public class InExpressionTest { } return list; } + + @NotNull + private InExpression exp(String propName, boolean not, Object... values) { + return new InExpression(propName, Arrays.asList(values), not); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp("a", false, 10).isSameByPlan(exp("a", false, 10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffPropertyName() { + + assertThat(exp("a", false, 10).isSameByPlan(exp("b", false, 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffNot() { + + assertThat(exp("a", false, 10).isSameByPlan(exp("a", true, 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffBind_same() { + + assertThat(exp("a", false, 10).isSameByPlan(exp("a", false, 10, 20))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffBindCount() { + + assertThat(exp("a", false, 10).isSameByPlan(exp("a", false, 10, 20))).isFalse(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp("a", false, 10).isSameByBind(exp("a", false, 10))).isTrue(); + } + + @Test + public void isSameByBind_when_sameMultipleBindValues() { + + assertThat(exp("a", false, 10, "ABC", 20).isSameByBind(exp("a", false, 10, "ABC", 20))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp("a", false, 10).isSameByBind(exp("a", false, "foo"))).isFalse(); + } + + @Test + public void isSameByBind_when_lessBindValues() { + + assertThat(exp("a", false, 10, "ABC", 20).isSameByBind(exp("a", false, 10, "ABC"))).isFalse(); + } + + @Test + public void isSameByBind_when_moreBindValues() { + + assertThat(exp("a", false, 10, "ABC").isSameByBind(exp("a", false, 10, "ABC", 30))).isFalse(); + } + } \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/InQueryExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/InQueryExpressionTest.java new file mode 100644 index 000000000..c1e15dedf --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/InQueryExpressionTest.java @@ -0,0 +1,89 @@ +package com.avaje.ebeaninternal.server.expression; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.util.Arrays; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class InQueryExpressionTest { + + + @NotNull + private InQueryExpression exp(String propertyName, boolean not, String sql, Object... bindValues) { + return new InQueryExpression(propertyName, not, sql, Arrays.asList(bindValues)); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp("name",true,"sql", 10).isSameByPlan(exp("name",true,"sql", 10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_same() { + + assertThat(exp("name",true,"sql", 10).isSameByPlan(exp("name",true,"sql", 20))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffNPropertyName() { + + assertThat(exp("name",true,"sql", 10).isSameByPlan(exp("nameDiff",true,"sql", 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffNot() { + + assertThat(exp("name",true,"sql", 10).isSameByPlan(exp("name",false,"sql", 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffSql() { + + assertThat(exp("name",true,"sql", 10).isSameByPlan(exp("name",true,"sqlDiff", 10))).isFalse(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp("name",true,"sql", 10).isSameByBind(exp("name",true,"sql", 10))).isTrue(); + } + + @Test + public void isSameByBind_when_sameMultipleBindValues() { + + assertThat(exp("name",true,"sql", 10, "ABC", 20).isSameByBind(exp("name",true,"sql", 10, "ABC", 20))).isTrue(); + } + + @Test + public void isSameByBind_when_diffMultipleBindValues() { + + assertThat(exp("name",true,"sql", 10, "ABC", 20).isSameByBind(exp("name",true,"sql", 10, "ABC", 21))).isFalse(); + } + + @Test + public void isSameByBind_when_diffMultipleBindValuesByOrder() { + + assertThat(exp("name",true,"sql", 10, "ABC", 20).isSameByBind(exp("name",true,"sql", 10, 20, "ABC"))).isFalse(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp("name",true,"sql", 10).isSameByBind(exp("name",true,"sql", 20))).isFalse(); + } + + @Test + public void isSameByBind_when_lessBindValues() { + + assertThat(exp("name",true,"sql", 10, 20).isSameByBind(exp("name",true,"sql", 20))).isFalse(); + } + + @Test + public void isSameByBind_when_moreBindValues() { + + assertThat(exp("name",true,"sql", 10).isSameByBind(exp("name",true,"sql", 10, 20))).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/JsonPathExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/JsonPathExpressionTest.java new file mode 100644 index 000000000..5dda9c091 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/JsonPathExpressionTest.java @@ -0,0 +1,81 @@ +package com.avaje.ebeaninternal.server.expression; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class JsonPathExpressionTest { + + @NotNull + private JsonPathExpression exp(String propertyName, String path, Op operator, Object value) { + return new JsonPathExpression(propertyName, path, operator, value); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByPlan(exp("a", "path", Op.EQ, 10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_same() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByPlan(exp("a", "path", Op.EQ, 20))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffPath() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByPlan(exp("a", "pathDiff", Op.EQ, 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffProperty_diff() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByPlan(exp("b", "path", Op.EQ, 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffOperator_diff() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByPlan(exp("a", "path", Op.LT, 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffType_diff() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByBind(exp("a", "path", Op.LT, 10))).isTrue(); + } + + @Test + public void isSameByBind_when_sameNullBindValues() { + + assertThat(exp("a", "path", Op.EQ, null).isSameByBind(exp("a", "path", Op.LT, null))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByBind(exp("a", "path", Op.EQ, "junk"))).isFalse(); + } + + @Test + public void isSameByBind_when_diffFirstNullBindValues() { + + assertThat(exp("a", "path", Op.EQ, null).isSameByBind(exp("a", "path", Op.LT, 10))).isFalse(); + } + + @Test + public void isSameByBind_when_diffLastNullBindValues() { + + assertThat(exp("a", "path", Op.EQ, 10).isSameByBind(exp("a", "path", Op.LT, null))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/JunctionExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/JunctionExpressionTest.java new file mode 100644 index 000000000..a4bd1c166 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/JunctionExpressionTest.java @@ -0,0 +1,60 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.Expr; +import com.avaje.ebean.Expression; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class JunctionExpressionTest { + + Expression eq(String propName, int value) { + return Expr.eq(propName, value); + } + + + DefaultExpressionList exp(Expression... expressions) { + + DefaultExpressionList list = new DefaultExpressionList(null, new DefaultExpressionFactory(true), null); + for (Expression ex : expressions) { + list.add(ex); + } + return list; + } + + JunctionExpression and(DefaultExpressionList list) { + return new JunctionExpression.Conjunction(list); + } + + JunctionExpression or(DefaultExpressionList list) { + return new JunctionExpression.Disjunction(list); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(and(exp(eq("a", 10), eq("b", 10))) + .isSameByPlan(and(exp(eq("a", 10), eq("b", 10))))).isTrue(); + } + + @Test + public void copyForPlanKey_isSameByPlan_when_same() { + + assertThat(and(exp(eq("a", 10), eq("b", 10)).copyForPlanKey()) + .isSameByPlan(and(exp(eq("a", 10), eq("b", 10))))).isTrue(); + } + + @Test + public void copyForPlanKey_isSameByPlan_when_diff() { + + assertThat(and(exp(eq("a", 10), eq("b", 10)).copyForPlanKey()) + .isSameByPlan(and(exp(eq("a", 10), eq("c", 10))))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffType() { + + assertThat(and(exp(eq("a", 10), eq("b", 10))) + .isSameByPlan(or(exp(eq("a", 10), eq("b", 10))))).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/LikeExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/LikeExpressionTest.java new file mode 100644 index 000000000..85a2aef8f --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/LikeExpressionTest.java @@ -0,0 +1,66 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.LikeType; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class LikeExpressionTest extends BaseExpressionTest { + + + @NotNull + private LikeExpression exp(String propertyName, String value, boolean caseInsensitive, LikeType type) { + return new LikeExpression(propertyName, value, caseInsensitive, type); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp("a", "rob", true, LikeType.STARTS_WITH) + .isSameByPlan(exp("a", "rob", true, LikeType.STARTS_WITH))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_then_stillSame() { + + assertThat(exp("a", "rob", true, LikeType.STARTS_WITH) + .isSameByPlan(exp("a", "bor", true, LikeType.STARTS_WITH))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffCaseInsensitive() { + + assertThat(exp("a", "rob", true, LikeType.STARTS_WITH) + .isSameByPlan(exp("a", "rob", false, LikeType.STARTS_WITH))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffLikeType() { + + assertThat(exp("a", "rob", true, LikeType.STARTS_WITH) + .isSameByPlan(exp("a", "rob", true, LikeType.ENDS_WITH))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffProperty() { + + assertThat(exp("a", "rob", true, LikeType.STARTS_WITH) + .isSameByPlan(exp("b", "rob", true, LikeType.STARTS_WITH))).isFalse(); + } + + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp("a", "rob", true, LikeType.STARTS_WITH) + .isSameByBind(exp("a", "rob", true, LikeType.STARTS_WITH))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp("a", "rob", true, LikeType.STARTS_WITH) + .isSameByBind(exp("a", "bor", true, LikeType.STARTS_WITH))).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/LogicExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/LogicExpressionTest.java new file mode 100644 index 000000000..742a6ebd0 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/LogicExpressionTest.java @@ -0,0 +1,78 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.Expr; +import com.avaje.ebean.Expression; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LogicExpressionTest extends BaseExpressionTest { + + Expression eq(String propName, int value) { + return Expr.eq(propName, value); + } + + LogicExpression and(Expression a, Expression b) { + return new LogicExpression.And(a, b); + } + + LogicExpression or(Expression a, Expression b) { + return new LogicExpression.Or(a, b); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(and(eq("a", 10), eq("b", 10)) + .isSameByPlan(and(eq("a", 10), eq("b", 10)))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_then_stillSame() { + + assertThat(and(eq("a", 10), eq("b", 10)) + .isSameByPlan(and(eq("a", 20), eq("b", 20)))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffExp1_then_diff() { + + assertThat(and(eq("a", 10), eq("b", 10)) + .isSameByPlan(and(eq("c", 10), eq("b", 10)))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffExp2_then_diff() { + + assertThat(and(eq("a", 10), eq("b", 10)) + .isSameByPlan(and(eq("a", 10), eq("c", 10)))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffType_then_diff() { + + assertThat(or(eq("a", 10), eq("b", 10)) + .isSameByPlan(and(eq("a", 10), eq("c", 10)))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffExpressionType() { + + assertThat(or(eq("a", 10), eq("b", 10)) + .isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(and(eq("a", 10), eq("b", 10)) + .isSameByBind(and(eq("a", 10), eq("c", 10)))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(and(eq("a", 10), eq("b", 10)) + .isSameByBind(and(eq("a", 10), eq("c", 20)))).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/NoopExpressionQueryTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/NoopExpressionTest.java similarity index 64% rename from src/test/java/com/avaje/ebeaninternal/server/expression/NoopExpressionQueryTest.java rename to src/test/java/com/avaje/ebeaninternal/server/expression/NoopExpressionTest.java index 9a42ea729..2f473c454 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/expression/NoopExpressionQueryTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/NoopExpressionTest.java @@ -8,7 +8,8 @@ import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; -public class NoopExpressionQueryTest extends BaseTestCase { + +public class NoopExpressionTest extends BaseTestCase { @Test public void test() { @@ -39,4 +40,29 @@ public class NoopExpressionQueryTest extends BaseTestCase { assertThat(generatedSql).contains("select t0.id c0 from o_customer t0 where t0.name is null and 1=1 and t0.status is not null"); } + + @Test + public void isSameByPlan_when_same() { + + assertThat(new NoopExpression().isSameByPlan(new NoopExpression())).isTrue(); + } + + @Test + public void isSameByPlan_when_diffExpressionType() { + + assertThat(new NoopExpression().isSameByPlan(null)).isFalse(); + } + + @Test + public void isSameByBind_when_same() { + + assertThat(new NoopExpression().isSameByBind(new NoopExpression())).isTrue(); + } + + @Test + public void isSameByBind_when_diffExpressionType() { + + assertThat(new NoopExpression().isSameByBind(null)).isTrue(); + } + } diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/NotExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/NotExpressionTest.java new file mode 100644 index 000000000..4bb5abe33 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/NotExpressionTest.java @@ -0,0 +1,58 @@ +package com.avaje.ebeaninternal.server.expression; + + +import com.avaje.ebean.Expression; +import org.junit.Test; + +import static com.avaje.ebean.Expr.eq; +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class NotExpressionTest { + + + NotExpression not(Expression expression) { + return new NotExpression(expression); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(not(eq("a", 10)) + .isSameByPlan(not(eq("a", 10)))).isTrue(); + } + + @Test + public void isSameByPlan_when_sameByPlan() { + + assertThat(not(eq("a", 10)) + .isSameByPlan(not(eq("a", 20)))).isTrue(); + } + + @Test + public void isSameByPlan_when_different() { + + assertThat(not(eq("a", 10)) + .isSameByPlan(not(eq("b", 10)))).isFalse(); + } + + @Test + public void isSameByPlan_when_differentExpressionType() { + + assertThat(not(eq("a", 10)) + .isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByBind_when_same() { + + assertThat(not(eq("a", 10)) + .isSameByBind(not(eq("b", 10)))).isTrue(); + } + + @Test + public void isSameByBind_when_different() { + + assertThat(not(eq("a", 10)) + .isSameByBind(not(eq("a", 20)))).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/NullExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/NullExpressionTest.java new file mode 100644 index 000000000..ec2187d14 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/NullExpressionTest.java @@ -0,0 +1,91 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebeaninternal.api.SpiExpression; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class NullExpressionTest extends BaseExpressionTest { + + NullExpression nullExp(String propertyName, boolean notNull) { + return new NullExpression(propertyName, notNull); + } + + @Test + public void addSql_when_notNull() throws Exception { + + DefaultExpressionRequest expReq = newExpressionRequest(); + + nullExp("id", true).addSql(expReq); + + assertThat(expReq.getSql()).isEqualTo("id is not null "); + } + + @Test + public void addSql_when_null() throws Exception { + + DefaultExpressionRequest expReq = newExpressionRequest(); + + nullExp("id", false).addSql(expReq); + + assertThat(expReq.getSql()).isEqualTo("id is null "); + } + + @Test + public void addSql_when_notNull_and_assocOne() throws Exception { + + DefaultExpressionRequest expReq = newExpressionRequest(); + + nullExp("customer", true).addSql(expReq); + + assertThat(expReq.getSql()).isEqualTo("customer.id is not null "); + } + + @Test + public void addSql_when_null_and_assocOne() throws Exception { + + DefaultExpressionRequest expReq = newExpressionRequest(); + + nullExp("customer", false).addSql(expReq); + + assertThat(expReq.getSql()).isEqualTo("customer.id is null "); + } + + @Test + public void copyForPlanKey_isSameInstance() throws Exception { + + NullExpression exp = nullExp("customer.name", false); + SpiExpression other = exp.copyForPlanKey(); + + assertThat(exp).isSameAs(other); + } + + @Test + public void isSameByBind_true() throws Exception { + + assertThat(nullExp("customer.name", false) + .isSameByBind(nullExp("customer.name", false))).isTrue(); + } + + @Test + public void isSameByPlan_true() throws Exception { + + assertThat(nullExp("customer.name", false) + .isSameByPlan(nullExp("customer.name", false))).isTrue(); + } + + @Test + public void isSameByPlan_false_when_notNullDiff() throws Exception { + + assertThat(new NullExpression("customer.name", false) + .isSameByPlan(new NullExpression("customer.name", true))).isFalse(); + } + + @Test + public void isSameByPlan_false_when_propertyNameDiff() throws Exception { + + assertThat(new NullExpression("customer.startDate", true) + .isSameByPlan(new NullExpression("customer.name", true))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/RawExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/RawExpressionTest.java new file mode 100644 index 000000000..f5382dc6d --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/RawExpressionTest.java @@ -0,0 +1,50 @@ +package com.avaje.ebeaninternal.server.expression; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class RawExpressionTest { + + @NotNull + private RawExpression exp(String sql, Object... values) { + return new RawExpression(sql, values); + } + + @Test + public void isSameByPlan_when_same() { + assertThat(exp("a", 10).isSameByPlan(exp("a", 10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBindValues() { + assertThat(exp("a", 10).isSameByPlan(exp("a", 20))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffSql() { + assertThat(exp("a", 10).isSameByPlan(exp("b", 10))).isFalse(); + } + + @Test + public void isSameByBind_when_same() { + assertThat(exp("a", 10).isSameByBind(exp("a", 10))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + assertThat(exp("a", 10).isSameByBind(exp("a", 20))).isFalse(); + } + + @Test + public void isSameByBind_when_moreBindValues() { + assertThat(exp("a", 10).isSameByBind(exp("a", 10, 20))).isFalse(); + } + + @Test + public void isSameByBind_when_lessBindValues() { + assertThat(exp("a", 10, 20).isSameByBind(exp("a", 10))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/SameTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/SameTest.java new file mode 100644 index 000000000..35c9afed0 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/SameTest.java @@ -0,0 +1,29 @@ +package com.avaje.ebeaninternal.server.expression; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class SameTest { + + @Test + public void sameByNull() throws Exception { + + assertThat(Same.sameByNull("a", "a")).isTrue(); + assertThat(Same.sameByNull("a", "b")).isTrue(); + assertThat(Same.sameByNull(null, null)).isTrue(); + assertThat(Same.sameByNull("a", null)).isFalse(); + assertThat(Same.sameByNull(null, "a")).isFalse(); + } + + @Test + public void sameByValue() throws Exception { + + assertThat(Same.sameByValue("a", "a")).isTrue(); + assertThat(Same.sameByValue("a", "b")).isFalse(); + assertThat(Same.sameByValue(null, null)).isTrue(); + assertThat(Same.sameByValue("a", null)).isFalse(); + assertThat(Same.sameByValue(null, "a")).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/SimpleExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/SimpleExpressionTest.java new file mode 100644 index 000000000..b6fd1be47 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/SimpleExpressionTest.java @@ -0,0 +1,58 @@ +package com.avaje.ebeaninternal.server.expression; + + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SimpleExpressionTest extends BaseExpressionTest { + + @NotNull + private SimpleExpression exp(String propertyName, Op operator, Object value) { + return new SimpleExpression(propertyName, operator, value); + } + + @Test + public void isSameByPlan_when_same() { + + assertThat(exp("a", Op.EQ, 10).isSameByPlan(exp("a", Op.EQ, 10))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffBind_same() { + + assertThat(exp("a", Op.EQ, 10).isSameByPlan(exp("a", Op.EQ, 20))).isTrue(); + } + + @Test + public void isSameByPlan_when_diffProperty_diff() { + + assertThat(exp("a", Op.EQ, 10).isSameByPlan(exp("b", Op.EQ, 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffOperator_diff() { + + assertThat(exp("a", Op.EQ, 10).isSameByPlan(exp("a", Op.LT, 10))).isFalse(); + } + + @Test + public void isSameByPlan_when_diffType_diff() { + + assertThat(exp("a", Op.EQ, 10).isSameByPlan(new NoopExpression())).isFalse(); + } + + @Test + public void isSameByBind_when_sameBindValues() { + + assertThat(exp("a", Op.EQ, 10).isSameByBind(exp("a", Op.LT, 10))).isTrue(); + } + + @Test + public void isSameByBind_when_diffBindValues() { + + assertThat(exp("a", Op.EQ, 10).isSameByBind(exp("a", Op.EQ, "junk"))).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/query/RawSqlQueryPlanKeyTest.java b/src/test/java/com/avaje/ebeaninternal/server/query/RawSqlQueryPlanKeyTest.java new file mode 100644 index 000000000..2d8c10083 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/query/RawSqlQueryPlanKeyTest.java @@ -0,0 +1,61 @@ +package com.avaje.ebeaninternal.server.query; + + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RawSqlQueryPlanKeyTest { + + + @Test + public void test_equals_same_instance() { + + RawSqlQueryPlanKey key = key("select foo", true, true, ""); + assertThat(key).isEqualTo(key); + assertThat(key.hashCode()).isEqualTo(key.hashCode()); + } + + @Test + public void test_equals_diff_instance() { + + assertThat(key("select foo", true, true, "")).isEqualTo(key("select foo", true, true, "")); + assertThat(key("select foo", true, true, "").hashCode()).isEqualTo(key("select foo", true, true, "").hashCode()); + } + + @Test + public void test_notEquals_diff_sql() { + + assertThat(key("select foo", true, true, "")).isNotEqualTo(key("select bar", true, true, "")); + assertThat(key("select foo", true, true, "").hashCode()).isNotEqualTo(key("select bar", true, true, "").hashCode()); + } + + @Test + public void test_notEquals_diff_rawSqlFlag() { + + assertThat(key("select foo", true, true, "")).isNotEqualTo(key("select foo", false, true, "")); + assertThat(key("select foo", true, true, "").hashCode()).isNotEqualTo(key("select foo", false, true, "").hashCode()); + } + + @Test + public void test_notEquals_diff_rowNumberIncluded() { + + assertThat(key("select foo", true, true, "")).isNotEqualTo(key("select foo", true, false, "")); + assertThat(key("select foo", true, true, "").hashCode()).isNotEqualTo(key("select foo", true, false, "").hashCode()); + } + + + @Test + public void test_notEquals_diff_logWhereSql() { + + assertThat(key("select foo", true, true, "")).isNotEqualTo(key("select foo", true, true, "a")); + assertThat(key("select foo", true, true, "").hashCode()).isNotEqualTo(key("select foo", true, true, "a").hashCode()); + } + + @NotNull + private RawSqlQueryPlanKey key(String sql, boolean rawSql, boolean rowNumberIncluded, String logWhereSql) { + return new RawSqlQueryPlanKey(sql, rawSql, rowNumberIncluded, logWhereSql); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java index e3c5dbf9a..50b4f558a 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java @@ -16,7 +16,7 @@ public class DefaultOrmQueryTest { DefaultOrmQuery q1 = (DefaultOrmQuery)Ebean.find(Order.class).where().in("name", "a","b","c").query(); DefaultOrmQuery q2 = (DefaultOrmQuery)Ebean.find(Order.class).where().in("id", 2,2,3).query(); - assertThat(q1.calculateQueryPlanHash()).isNotEqualTo(q2.calculateQueryPlanHash()); + assertThat(q1.createQueryPlanKey()).isNotEqualTo(q2.createQueryPlanKey()); assertThat(q1.queryBindHash()).isNotEqualTo(q2.queryBindHash()); } @@ -26,7 +26,7 @@ public class DefaultOrmQueryTest { DefaultOrmQuery q1 = (DefaultOrmQuery)Ebean.find(Order.class).where().in("id", 1,2,3).query(); DefaultOrmQuery q2 = (DefaultOrmQuery)Ebean.find(Order.class).where().in("id", 2,2,3).query(); - assertThat(q1.calculateQueryPlanHash()).isEqualTo(q2.calculateQueryPlanHash()); + assertThat(q1.createQueryPlanKey()).isEqualTo(q2.createQueryPlanKey()); assertThat(q1.queryBindHash()).isNotEqualTo(q2.queryBindHash()); } @@ -36,7 +36,7 @@ public class DefaultOrmQueryTest { DefaultOrmQuery q1 = (DefaultOrmQuery)Ebean.find(Order.class).where().in("id", 1,2,3).query(); DefaultOrmQuery q2 = (DefaultOrmQuery)Ebean.find(Order.class).where().in("id", 1,2,3).query(); - assertThat(q1.calculateQueryPlanHash()).isEqualTo(q2.calculateQueryPlanHash()); + assertThat(q1.createQueryPlanKey()).isEqualTo(q2.createQueryPlanKey()); assertThat(q1.queryBindHash()).isEqualTo(q2.queryBindHash()); } @@ -55,7 +55,7 @@ public class DefaultOrmQueryTest { .fetch("details", "orderQty, unitPrice") .fetch("details.product", "sku, name"); - assertThat(query1.calculateQueryPlanHash()).isNotEqualTo(query2.calculateQueryPlanHash()); + assertThat(query1.createQueryPlanKey()).isNotEqualTo(query2.createQueryPlanKey()); } @Test @@ -69,7 +69,7 @@ public class DefaultOrmQueryTest { .setFirstRow(1) .setMaxRows(0); - assertThat(query1.calculateQueryPlanHash()).isNotEqualTo(query2.calculateQueryPlanHash()); + assertThat(query1.createQueryPlanKey()).isNotEqualTo(query2.createQueryPlanKey()); } } \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java new file mode 100644 index 000000000..f6cd5df9b --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java @@ -0,0 +1,432 @@ +package com.avaje.ebeaninternal.server.querydefn; + + +import com.avaje.ebean.OrderBy; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionList; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.deploy.TableJoin; +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoin; +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoinColumn; +import com.avaje.ebeaninternal.server.expression.BaseExpressionTest; +import com.avaje.tests.model.basic.Customer; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OrmQueryPlanKeyTest extends BaseExpressionTest { + + + @Test + public void equals_when_defaults() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertSame(key1, key2); + } + + @Test + public void equals_when_diffTableJoinNull() { + + TableJoin tableJoin = tableJoin("id", "customer_id"); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffTableJoin() { + + TableJoin tableJoin1 = tableJoin("id", "customer_id"); + TableJoin tableJoin2 = tableJoin("id", "other_customer_id"); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin1, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(tableJoin2, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_sameTableJoin() { + + TableJoin tableJoin1 = tableJoin("id", "customer_id"); + TableJoin tableJoin2 = tableJoin("id", "customer_id"); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin1, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(tableJoin2, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertSame(key1, key2); + } + + @NotNull + private TableJoin tableJoin(String col1, String col2) { + DeployTableJoin deploy = new DeployTableJoin(); + deploy.setTable("myTable"); + deploy.addJoinColumn(new DeployTableJoinColumn(col1, col2)); + return new TableJoin(deploy); + } + + @Test + public void equals_when_diffQueryType() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.LIST, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_firstRowsDifferent() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 10, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_maxRowsDifferent() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_firstRowsMaxRowsSame() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 20, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 20, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertSame(key1, key2); + } + + @Test + public void equals_when_diffDisableLazyLoading() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, true, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffRawWhereNull() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, "rawWhere", null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffRawWhere() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, "rawWhere", null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, "rawDiff", null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffOrderByNull() { + + OrderBy o1 = new OrderBy("id"); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, o1, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertDifferent(key1, key2); + } + + @Test + public void equals_when_orderBySame() { + + OrderBy o1 = new OrderBy("id, name"); + OrderBy o2 = new OrderBy("id, name"); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, o1, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, o2, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + + assertSame(key1, key2); + } + + @Test + public void equals_when_diffQueryNull() { + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, "query", null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + + @Test + public void equals_when_diffQuery() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, "query", null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, "queryDiff", null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_querySame() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, "query", null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, "query", null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + @Test + public void equals_when_diffAddWhereNull() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, "addWhere", null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffAddWhere() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, "addWhere", null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, "diff", null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_sameAddWhere() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, "addWhere", null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, "addWhere", null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + @Test + public void equals_when_diffAddHavingNull() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, "addHaving", false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffAddHaving() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, "addHaving", false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, "diff", false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_sameAddHaving() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, "addHaving", false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, "addHaving", false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + + @Test + public void equals_when_diffDistinct() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_sameDistinct() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + @Test + public void equals_when_diffSqlDistinct() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_sameSqlDistinct() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + @Test + public void equals_when_diffMapKeyNull() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffMapKey() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, "diff", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_sameMapKey() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + @Test + public void equals_when_diffIdNull() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, 42, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_idBothGiven() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, 42, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, 23, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + @Test + public void equals_when_diffTemporalMode() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.DRAFT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffForUpdate() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, true, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffRootAliasNull() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "rootAlias", null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffRootAlias() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "rootAlias", null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "diff", null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_sameRootAlias() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "rootAlias", null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "rootAlias", null); + assertSame(key1, key2); + } + + SpiExpressionList list_id_eq_42() { + return (SpiExpressionList) server().find(Customer.class) + .where().eq("id", 42); + } + + SpiExpressionList list_id_eq_43() { + return (SpiExpressionList) server().find(Customer.class) + .where().eq("id", 43); + } + + SpiExpressionList list_id_eq_42_and_name_eq_rob() { + return (SpiExpressionList) server().find(Customer.class) + .where().eq("id", 43).eq("name", "rob"); + } + + + @Test + public void equals_when_sameWhere() { + + + SpiExpressionList list1 = list_id_eq_42(); + SpiExpressionList list2 = list_id_eq_43(); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, list2, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + + @Test + public void equals_when_diffWhere() { + + SpiExpressionList where1 = list_id_eq_42(); + SpiExpressionList where2 = list_id_eq_42_and_name_eq_rob(); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, where1, null, SpiQuery.TemporalMode.DRAFT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, where2, null, SpiQuery.TemporalMode.DRAFT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffWhereNullLast() { + + SpiExpressionList list1 = list_id_eq_42(); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffWhereNullFirst() { + + SpiExpressionList list1 = list_id_eq_42(); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffHaving() { + + SpiExpression having1 = list_id_eq_42().copyForPlanKey(); + SpiExpression having2 = list_id_eq_42_and_name_eq_rob().copyForPlanKey(); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, having2, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_sameHaving() { + + SpiExpression having1 = list_id_eq_42().copyForPlanKey(); + SpiExpression having2 = list_id_eq_42().copyForPlanKey(); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, having2, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + @Test + public void equals_when_havingNullLast() { + + SpiExpression having1 = list_id_eq_42().copyForPlanKey(); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + @Test + public void equals_when_havingNullFirst() { + + SpiExpression having1 = list_id_eq_42().copyForPlanKey(); + + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null); + assertSame(key1, key2); + } + + private void assertDifferent(OrmQueryPlanKey key1, OrmQueryPlanKey key2) { + assertThat(key1).isNotEqualTo(key2); + assertThat(key1.hashCode()).isNotEqualTo(key2.hashCode()); + } + + private void assertSame(OrmQueryPlanKey key1, OrmQueryPlanKey key2) { + assertThat(key1).isEqualTo(key2); + assertThat(key1.hashCode()).isEqualTo(key2.hashCode()); + } +} \ No newline at end of file From fe2c2e5331a31d8ab76b9af89679dee3dd841eb6 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Mon, 15 Feb 2016 23:46:28 +1300 Subject: [PATCH 06/18] #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - sameByPlan() & sameByBind() --- .../ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java index f6cd5df9b..ae1af12d8 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java @@ -407,7 +407,7 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null); OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); - assertSame(key1, key2); + assertDifferent(key1, key2); } @Test @@ -417,7 +417,7 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null); OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, null, null, null, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null); - assertSame(key1, key2); + assertDifferent(key1, key2); } private void assertDifferent(OrmQueryPlanKey key1, OrmQueryPlanKey key2) { From 5c59066ff814b9af6b0440bb9f50be5c2e8b6525 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Tue, 16 Feb 2016 01:06:30 +1300 Subject: [PATCH 07/18] #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - rawSql key --- .../java/com/avaje/ebean/DRawSqlParser.java | 2 +- src/main/java/com/avaje/ebean/RawSql.java | 127 +++++++++++------- .../server/querydefn/DefaultOrmQuery.java | 11 +- .../server/querydefn/OrmQueryPlanKey.java | 35 ++--- .../com/avaje/ebean/ColumnMappingTest.java | 65 +++++++++ src/test/java/com/avaje/ebean/ColumnTest.java | 58 ++++++++ .../java/com/avaje/ebean/RawSqlKeyTest.java | 64 +++++++++ 7 files changed, 283 insertions(+), 79 deletions(-) create mode 100644 src/test/java/com/avaje/ebean/ColumnMappingTest.java create mode 100644 src/test/java/com/avaje/ebean/ColumnTest.java create mode 100644 src/test/java/com/avaje/ebean/RawSqlKeyTest.java 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/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/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 0c07ea385..83c6b4f1e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -760,18 +760,9 @@ public class DefaultOrmQuery implements SpiQuery { */ CQueryPlanKey createQueryPlanKey() { - DefaultExpressionList where = null; - DefaultExpressionList having = null; - if (whereExpressions != null) { - where = whereExpressions.copyForPlanKey(); - } - if (havingExpressions != null) { - having = havingExpressions.copyForPlanKey(); - } - queryPlanKey = new OrmQueryPlanKey(includeTableJoin, type, detail, maxRows, firstRow, disableLazyLoading, rawWhereClause, orderBy, query, additionalWhere, additionalHaving, - distinct, sqlDistinct, mapKey, id, bindParams, where, having, + distinct, sqlDistinct, mapKey, id, bindParams, whereExpressions, havingExpressions, temporalMode, forUpdate, rootTableAlias, rawSql); return queryPlanKey; diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKey.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKey.java index 7f7f7a41e..616d8b962 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKey.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKey.java @@ -17,12 +17,10 @@ public class OrmQueryPlanKey implements CQueryPlanKey { private final TableJoin includeTableJoin; private final String orderByAsSting; private final OrmQueryDetail detail; - private final BindParams bindParams; - private final SpiExpression whereExpressions; - private final SpiExpression havingExpressions; - private final RawSql rawSql; + private final 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; @@ -58,13 +56,12 @@ public class OrmQueryPlanKey implements CQueryPlanKey { this.sqlDistinct = sqlDistinct; this.mapKey = mapKey; this.hasIdValue = (id != null); - this.bindParams = bindParams; - this.whereExpressions = whereExpressions; - this.havingExpressions = havingExpressions; + this.where = (whereExpressions == null) ? null : whereExpressions.copyForPlanKey(); + this.having = (havingExpressions == null) ? null : havingExpressions.copyForPlanKey(); this.temporalMode = temporalMode; this.forUpdate = forUpdate; this.rootTableAlias = rootTableAlias; - this.rawSql = rawSql; + this.rawSqlKey = (rawSql == null) ? null : rawSql.getKey(); // exclude bind values and things unrelated to the sql being generated HashQueryPlanBuilder builder = new HashQueryPlanBuilder(); @@ -78,7 +75,7 @@ public class OrmQueryPlanKey implements CQueryPlanKey { builder.add(disableLazyLoading); builder.add(hasIdValue); builder.add(temporalMode); - builder.add(rawSql == null ? 0 : rawSql.queryHash()); + builder.add(rawSqlKey == null ? 0 : rawSqlKey.hashCode()); builder.add(includeTableJoin != null ? includeTableJoin.queryHash() : 0); builder.add(rootTableAlias); @@ -88,11 +85,11 @@ public class OrmQueryPlanKey implements CQueryPlanKey { if (bindParams != null) { bindParams.buildQueryPlanHash(builder); } - if (whereExpressions != null) { - whereExpressions.queryPlanHash(builder); + if (where != null) { + where.queryPlanHash(builder); } - if (havingExpressions != null) { - havingExpressions.queryPlanHash(builder); + if (having != null) { + having.queryPlanHash(builder); } this.planHash = builder.getPlanHash(); @@ -118,11 +115,9 @@ public class OrmQueryPlanKey implements CQueryPlanKey { 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; @@ -131,13 +126,11 @@ public class OrmQueryPlanKey implements CQueryPlanKey { if (temporalMode != that.temporalMode) return false; if (includeTableJoin != null ? !includeTableJoin.equals(that.includeTableJoin) : that.includeTableJoin != null) return false; if (orderByAsSting != null ? !orderByAsSting.equals(that.orderByAsSting) : that.orderByAsSting != null) return false; - - if (whereExpressions != null ? !whereExpressions.isSameByPlan(that.whereExpressions) : that.whereExpressions != null) return false; - if (havingExpressions != null ? !havingExpressions.isSameByPlan(that.havingExpressions) : that.havingExpressions != null) return false; + if (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 (bindParams != null ? !bindParams.equals(that.bindParams) : that.bindParams != null) return false; -// if (rawSql != null ? !rawSql.equals(that.rawSql) : that.rawSql != null) return false; if (rawWhereClause != null ? !rawWhereClause.equals(that.rawWhereClause) : that.rawWhereClause != null) return false; if (query != null ? !query.equals(that.query) : that.query != null) return false; 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/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 From 1aeffd31e08effb50e2e39ec9597033322ff8c50 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Tue, 16 Feb 2016 01:46:12 +1300 Subject: [PATCH 08/18] #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - no effective change, reorder methods --- .../server/querydefn/DefaultOrmQuery.java | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) 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 83c6b4f1e..71ec91532 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -344,20 +344,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; } @@ -404,6 +390,20 @@ public class DefaultOrmQuery implements SpiQuery { return manyWhereJoins; } + /** + * 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 List removeQueryJoins() { List queryJoins = detail.removeSecondaryQueries(); if (queryJoins != null) { @@ -447,6 +447,39 @@ public class DefaultOrmQuery implements SpiQuery { detail.convertManyFetchJoinsToQueryJoins(beanDescriptor, lazyLoadManyPath, allowOne, queryBatch); } + 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); + } + } + /** * Setup to be a delete query. */ @@ -465,7 +498,6 @@ public class DefaultOrmQuery implements SpiQuery { public void setSelectId() { // clear select and fetch joins.. detail.clear(); - select(beanDescriptor.getIdBinder().getIdProperty()); } @@ -932,18 +964,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; @@ -1165,18 +1190,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 + "]"; } @@ -1270,19 +1283,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; From 4df2d4020ebcaccb26bec3e1b76dba7a7dc8ec94 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Tue, 16 Feb 2016 08:57:12 +1300 Subject: [PATCH 09/18] #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - tidy up createQueryRequest --- .../ebeaninternal/api/SpiEbeanServer.java | 11 ------ .../com/avaje/ebeaninternal/api/SpiQuery.java | 5 +++ .../server/core/DefaultServer.java | 36 +++---------------- .../server/core/OrmQueryRequest.java | 15 +++++--- .../server/querydefn/DefaultOrmQuery.java | 13 +++++++ .../ebeaninternal/api/TDSpiEbeanServer.java | 10 ------ 6 files changed, 34 insertions(+), 56 deletions(-) diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java index 7e8b76d9b..69b0a4742 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java @@ -130,22 +130,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/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java index c9def4a75..02f48ae67 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -294,6 +294,11 @@ public interface SpiQuery extends Query { */ boolean initManyWhereJoins(); + /** + * Return true if one Many fetch join is allowed. + */ + boolean isAllowOneManyFetch(); + /** * Return the joins required to support predicates on the many properties. */ 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 d00d5793b..5667dd5fe 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; @@ -1059,7 +1058,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,13 +1066,7 @@ 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) { @@ -1087,29 +1080,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { query.setSqlDistinct(true); } - boolean allowOneManyFetch = true; - if (Mode.LAZYLOAD_MANY.equals(query.getMode())) { - allowOneManyFetch = false; + query.convertManyFetchJoinsToQueryJoins(query.isAllowOneManyFetch(), queryBatchSize); - } 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 + OrmQueryRequest request = new OrmQueryRequest(this, queryEngine, query, desc, (SpiTransaction) t); + request.adapterPreQuery(); 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 952dfa2ca..9cf5e7886 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java @@ -10,6 +10,7 @@ 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.HashQuery; @@ -129,6 +130,16 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe return loadContext; } + /** + * Run BeanQueryAdapter preQuery() if needed. + */ + public void adapterPreQuery() { + BeanQueryAdapter queryAdapter = beanDescriptor.getQueryAdapter(); + if (queryAdapter != null) { + queryAdapter.preQuery(this); + } + } + /** * Prepare the query and calculate the query plan key. */ @@ -328,10 +339,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. */ 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 71ec91532..a288d9a80 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -440,6 +440,19 @@ public class DefaultOrmQuery implements SpiQuery { this.lazyLoadManyPath = lazyLoadManyPath; } + @Override + public boolean isAllowOneManyFetch() { + + if (Mode.LAZYLOAD_MANY.equals(getMode())) { + return false; + + } else if (hasMaxRowsOrFirstRow() && !isRawSql() && !isSqlSelect()) { + return false; + } + + return true; + } + /** * Convert any many joins fetch joins to query joins. */ 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; From ae7827c48339e4f70dda2ee250d56dbed7502f1d Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Tue, 16 Feb 2016 09:21:10 +1300 Subject: [PATCH 10/18] #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - tidy up createQueryRequest - part 2 --- src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java | 3 --- src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java | 2 +- .../com/avaje/ebeaninternal/server/core/DefaultServer.java | 3 +-- .../com/avaje/ebeaninternal/server/core/OrmQueryRequest.java | 3 ++- .../avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java | 3 ++- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java index 69b0a4742..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; diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java index 02f48ae67..5b137ae40 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -348,7 +348,7 @@ public interface SpiQuery extends Query { /** * Convert any many joins fetch joins to query joins. */ - void convertManyFetchJoinsToQueryJoins(boolean allowOne, int queryBatch); + void convertManyFetchJoinsToQueryJoins(int queryBatch); /** * Return the TransactionContext. 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 5667dd5fe..7119dc9be 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -1080,10 +1080,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { query.setSqlDistinct(true); } - query.convertManyFetchJoinsToQueryJoins(query.isAllowOneManyFetch(), queryBatchSize); + query.convertManyFetchJoinsToQueryJoins(queryBatchSize); OrmQueryRequest request = new OrmQueryRequest(this, queryEngine, query, desc, (SpiTransaction) t); - request.adapterPreQuery(); 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 9cf5e7886..f67908495 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java @@ -133,7 +133,7 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe /** * Run BeanQueryAdapter preQuery() if needed. */ - public void adapterPreQuery() { + private void adapterPreQuery() { BeanQueryAdapter queryAdapter = beanDescriptor.getQueryAdapter(); if (queryAdapter != null) { queryAdapter.preQuery(this); @@ -144,6 +144,7 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe * Prepare the query and calculate the query plan key. */ public void prepareQuery() { + adapterPreQuery(); this.queryPlanKey = query.prepare(this); } 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 a288d9a80..d6608f104 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -456,7 +456,8 @@ public class DefaultOrmQuery implements SpiQuery { /** * Convert any many joins fetch joins to query joins. */ - public void convertManyFetchJoinsToQueryJoins(boolean allowOne, int queryBatch) { + public void convertManyFetchJoinsToQueryJoins(int queryBatch) { + boolean allowOne = isAllowOneManyFetch(); detail.convertManyFetchJoinsToQueryJoins(beanDescriptor, lazyLoadManyPath, allowOne, queryBatch); } From af554410fd318c490cfbef682798fa1bfb170176 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Tue, 16 Feb 2016 16:10:39 +1300 Subject: [PATCH 11/18] #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - tidy up OrmQueryDetail --- .../server/querydefn/OrmQueryDetail.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) 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 4140c607e..bce3000e8 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -172,17 +172,13 @@ 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 @@ -332,8 +328,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' @@ -353,6 +348,15 @@ public class OrmQueryDetail implements Serializable { } } + /** + * 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); + } + /** * Return true if this is actually the root level of a +query/+lazy loading query. */ From 69bf1758bc3e5c730193441c3191e1565050db92 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Tue, 16 Feb 2016 17:07:56 +1300 Subject: [PATCH 12/18] #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - tidy up OrmQueryRequest --- .../avaje/ebeaninternal/api/LoadContext.java | 16 ++++------ .../com/avaje/ebeaninternal/api/SpiQuery.java | 14 ++------ .../server/core/DefaultServer.java | 20 +----------- .../server/core/OrmQueryRequest.java | 21 +++++++++--- .../server/loadcontext/DLoadContext.java | 32 ++++++------------- .../server/querydefn/DefaultOrmQuery.java | 17 +++++++--- 6 files changed, 47 insertions(+), 73 deletions(-) diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadContext.java b/src/main/java/com/avaje/ebeaninternal/api/LoadContext.java index 3a29927e2..82fd3c8c7 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. @@ -23,16 +26,11 @@ 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. - *

+ * Register any secondary queries (+query or +lazy) with their appropriate LoadBeanContext or LoadManyContext. */ - void registerSecondaryQueries(SpiQuery query); - - /** + void registerSecondaryQueries(List queryJoins, List lazyJoins); + + /** * 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/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java index 5b137ae40..8a472a7d0 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -289,16 +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 true if one Many fetch join is allowed. - */ - boolean isAllowOneManyFetch(); - /** * Return the joins required to support predicates on the many properties. */ @@ -346,9 +336,9 @@ public interface SpiQuery extends Query { void setLazyLoadManyPath(String lazyLoadManyPath); /** - * Convert any many joins fetch joins to query joins. + * Convert joins as necessary to query joins etc. */ - void convertManyFetchJoinsToQueryJoins(int queryBatch); + void convertJoins(int queryBatchSize); /** * Return the TransactionContext. 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 7119dc9be..65b6382f6 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -462,19 +462,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); @@ -1073,17 +1064,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { 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); - } - - query.convertManyFetchJoinsToQueryJoins(queryBatchSize); - OrmQueryRequest request = new OrmQueryRequest(this, queryEngine, query, desc, (SpiTransaction) t); - request.prepareQuery(); + request.prepareQuery(queryBatchSize); 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 f67908495..69288b7be 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java @@ -13,9 +13,9 @@ 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.LoadContext; -import com.avaje.ebeaninternal.api.CQueryPlanKey; import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.api.SpiQuery.Type; @@ -28,6 +28,7 @@ import com.avaje.ebeaninternal.server.deploy.DeployPropertyParserMap; import com.avaje.ebeaninternal.server.loadcontext.DLoadContext; import com.avaje.ebeaninternal.server.query.CQueryPlan; import com.avaje.ebeaninternal.server.query.CancelableQuery; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext; import javax.persistence.PersistenceException; @@ -62,6 +63,10 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe private CQueryPlanKey queryPlanKey; + private List queryJoins; + + private List lazyJoins; + /** * Create the InternalQueryRequest. */ @@ -143,8 +148,14 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe /** * Prepare the query and calculate the query plan key. */ - public void prepareQuery() { + public void prepareQuery(int queryBatchSize) { + adapterPreQuery(); + + // determine extra joins required to support where clause predicates on *ToMany properties + query.convertJoins(queryBatchSize); + this.queryJoins = query.removeQueryJoins(); + this.lazyJoins = query.removeLazyJoins(); this.queryPlanKey = query.prepare(this); } @@ -207,7 +218,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.registerSecondaryQueries(queryJoins, lazyJoins); } /** @@ -231,7 +242,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(); } /** @@ -321,7 +332,7 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe */ @SuppressWarnings("unchecked") public Set findSet() { - return (Set)queryEngine.findMany(this); + return (Set) queryEngine.findMany(this); } /** 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..efd6490bd 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java @@ -119,8 +119,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); } } @@ -138,30 +137,19 @@ public class DLoadContext implements LoadContext { } /** - * 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. - *

+ * Register the +query and +lazy secondary queries with their appropriate LoadBeanContext or LoadManyContext. */ - public void registerSecondaryQueries(SpiQuery query) { + public void registerSecondaryQueries(List queryJoins, List lazyJoins) { - secQuery = query.removeQueryJoins(); + this.secQuery = queryJoins; if (secQuery != null) { for (int i = 0; i < secQuery.size(); i++) { - OrmQueryProperties props = secQuery.get(i); - registerSecondaryQuery(props); + registerSecondaryQuery(secQuery.get(i)); } } - - List lazyQueries = query.removeLazyJoins(); - if (lazyQueries != null) { - for (int i = 0; i < lazyQueries.size(); i++) { - OrmQueryProperties lazyProps = lazyQueries.get(i); - registerSecondaryQuery(lazyProps); + if (lazyJoins != null) { + for (int i = 0; i < lazyJoins.size(); i++) { + registerSecondaryQuery(lazyJoins.get(i)); } } } @@ -172,14 +160,12 @@ public class DLoadContext implements LoadContext { */ private void registerSecondaryQuery(OrmQueryProperties props) { - String propName = props.getPath(); - ElPropertyValue elGetValue = rootDescriptor.getElGetValue(propName); + ElPropertyValue elGetValue = rootDescriptor.getElGetValue(props.getPath()); boolean many = elGetValue.getBeanProperty().containsMany(); registerSecondaryNode(many, props); } - public ObjectGraphNode getObjectGraphNode(String path) { ObjectGraphNode node = nodePathMap.get(path); 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 d6608f104..2fb6afb81 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -378,12 +378,14 @@ public class DefaultOrmQuery implements SpiQuery { /** * Return true if the where expressions contains a many property. */ - public boolean initManyWhereJoins() { + private void initManyWhereJoins() { manyWhereJoins = new ManyWhereJoins(); if (whereExpressions != null) { whereExpressions.containsMany(beanDescriptor, manyWhereJoins); } - return !manyWhereJoins.isEmpty(); + if (!manyWhereJoins.isEmpty()) { + setSqlDistinct(true); + } } public ManyWhereJoins getManyWhereJoins() { @@ -440,8 +442,7 @@ public class DefaultOrmQuery implements SpiQuery { this.lazyLoadManyPath = lazyLoadManyPath; } - @Override - public boolean isAllowOneManyFetch() { + private boolean isAllowOneManyFetch() { if (Mode.LAZYLOAD_MANY.equals(getMode())) { return false; @@ -453,10 +454,16 @@ public class DefaultOrmQuery implements SpiQuery { return true; } + @Override + public void convertJoins(int queryBatchSize) { + initManyWhereJoins(); + convertManyFetchJoinsToQueryJoins(queryBatchSize); + } + /** * Convert any many joins fetch joins to query joins. */ - public void convertManyFetchJoinsToQueryJoins(int queryBatch) { + private void convertManyFetchJoinsToQueryJoins(int queryBatch) { boolean allowOne = isAllowOneManyFetch(); detail.convertManyFetchJoinsToQueryJoins(beanDescriptor, lazyLoadManyPath, allowOne, queryBatch); } From 2fcaab44c65668e93bf5c2bd80d3bf497f37c066 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Wed, 17 Feb 2016 10:52:55 +1300 Subject: [PATCH 13/18] #566 - Refactor internals - Initial tidy on OrmQueryDetail and OrmQueryDetailProperties --- .../com/avaje/ebean/text/PathProperties.java | 10 +- .../api/HashQueryPlanBuilder.java | 17 ++ .../server/core/DefaultServer.java | 2 +- .../server/deploy/BeanDescriptor.java | 6 +- .../deploy/meta/DeployBeanDescriptor.java | 5 +- .../ebeaninternal/server/expression/Same.java | 23 ++ .../server/query/CQueryBuilder.java | 4 +- .../server/querydefn/OrmQueryDetail.java | 94 +++++-- .../querydefn/OrmQueryDetailParser.java | 4 +- .../server/querydefn/OrmQueryProperties.java | 259 ++++++------------ .../querydefn/OrmQueryPropertiesParser.java | 183 +++++++++++++ .../server/querydefn/SimpleTextParser.java | 4 + ...faultServer_createOrmQueryRequestTest.java | 126 +++++++++ .../server/querydefn/DefaultOrmQueryTest.java | 18 -- .../querydefn/OrmQueryDetailParserTest.java | 73 +++-- .../server/querydefn/OrmQueryDetailTest.java | 62 ++++- .../OrmQueryPropertiesParserTest.java | 132 +++++++++ .../querydefn/OrmQueryPropertiesTest.java | 50 ++++ .../server/querydefn/TestQueryLanguage.java | 23 +- .../tests/autofetch/TunedQueryInfoTest.java | 2 +- .../avaje/tests/query/TestQueryParsing.java | 49 ++-- 21 files changed, 833 insertions(+), 313 deletions(-) create mode 100644 src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesParser.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesParserTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesTest.java 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/HashQueryPlanBuilder.java b/src/main/java/com/avaje/ebeaninternal/api/HashQueryPlanBuilder.java index 9365d6b20..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. */ @@ -33,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. */ 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 65b6382f6..db55f32cf 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -1038,7 +1038,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); 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 85c5f06e5..fada56d65 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -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; /** @@ -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; } 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/Same.java b/src/main/java/com/avaje/ebeaninternal/server/expression/Same.java index 5f46e954b..05e483d94 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/Same.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/Same.java @@ -1,5 +1,8 @@ package com.avaje.ebeaninternal.server.expression; +import java.util.Collection; +import java.util.Iterator; + /** * Utility to help isSame methods. */ @@ -19,6 +22,26 @@ public class Same { 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. */ 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..9f5bf3181 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.getChunk(path, true).setDefaultProperties(null, pathProps.get(path)); } // check if @Id property included in RawSql 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 bce3000e8..b9d643b67 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -7,6 +7,7 @@ import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; import com.avaje.ebeaninternal.server.el.ElPropertyValue; import com.avaje.ebeaninternal.server.query.SplitName; +import org.jetbrains.annotations.NotNull; import javax.persistence.PersistenceException; import java.io.Serializable; @@ -18,7 +19,6 @@ 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. @@ -38,6 +38,7 @@ public class OrmQueryDetail implements Serializable { /** * Root level properties. */ + @NotNull private OrmQueryProperties baseProps = new OrmQueryProperties(); /** @@ -60,17 +61,17 @@ public class OrmQueryDetail implements Serializable { return copy; } + public int queryPlanHash() { + HashQueryPlanBuilder builder = new HashQueryPlanBuilder(); + queryPlanHash(builder); + return builder.getPlanHash(); + } + /** * Calculate the hash for the query plan. */ public void queryPlanHash(HashQueryPlanBuilder builder) { - if (baseProps == null) { - builder.add(false); - } else { - builder.add(true); - baseProps.queryPlanHash(builder); - } - + baseProps.queryPlanHash(builder); if (fetchPaths != null) { for (OrmQueryProperties p : fetchPaths.values()) { p.queryPlanHash(builder); @@ -79,11 +80,10 @@ public class OrmQueryDetail implements Serializable { } /** - * 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) { @@ -92,10 +92,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; } } @@ -103,21 +109,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(); @@ -135,7 +174,7 @@ public class OrmQueryDetail implements Serializable { } public boolean containsProperty(String property) { - return baseProps == null || baseProps.isIncluded(property); + return baseProps.isIncluded(property); } /** @@ -399,9 +438,6 @@ 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()); } @@ -417,14 +453,14 @@ public class OrmQueryDetail implements Serializable { } 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.hasProperties()); } /** 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..a54d56da7 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; @@ -155,7 +157,7 @@ public class OrmQueryDetailParser { private void readSelect() { String path = null; String props = parser.nextWord(); - if (props.startsWith("(")) { + if (props != null && props.startsWith("(")) { props = props.substring(1, props.length() - 1); OrmQueryProperties base = new OrmQueryProperties(path, props); detail.setBase(base); 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 467c99468..2d2b12c09 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -10,13 +10,12 @@ 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.query.SplitName; import com.avaje.ebeaninternal.server.expression.FilterExpressionList; +import com.avaje.ebeaninternal.server.expression.Same; +import com.avaje.ebeaninternal.server.query.SplitName; import java.io.Serializable; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; @@ -32,8 +31,7 @@ public class OrmQueryProperties implements Serializable { private String parentPath; private String path; - private String properties; - + private String rawProperties; private String trimmedProperties; /** @@ -57,7 +55,7 @@ public class OrmQueryProperties implements Serializable { * 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; + private LinkedHashSet included; /** * Included bean joins. @@ -92,8 +90,10 @@ public class OrmQueryProperties implements Serializable { this.parentPath = SplitName.parent(path); } + /** + * Construct for root so path (and parentPath) are null. + */ public OrmQueryProperties() { - this(null); } /** @@ -137,25 +137,17 @@ public class OrmQueryProperties implements Serializable { * This can include the +query and +lazy type hints. *

*/ - public void setProperties(String properties) { - this.properties = properties; - this.trimmedProperties = properties; - parseProperties(); + public void setProperties(String rawProperties) { - if (!isAllProperties()) { - Set parsed = parseIncluded(trimmedProperties); - if (parsed.contains("*")) { - this.included = null; - } else { - this.included = parsed; - } - } else { - this.included = null; - } - } + OrmQueryPropertiesParser.Response response = OrmQueryPropertiesParser.parse(rawProperties); - private boolean isAllProperties() { - return (trimmedProperties == null) || (trimmedProperties.length() == 0) || "*".equals(trimmedProperties); + this.rawProperties = rawProperties; + this.trimmedProperties = response.properties; + this.included = response.included; + this.lazyFetchBatch = response.lazyFetchBatch; + this.queryFetchBatch = response.queryFetchBatch; + this.cache = response.cache; + this.readOnly = response.readOnly; } /** @@ -204,8 +196,8 @@ public class OrmQueryProperties implements Serializable { /** * Set the properties from deployment default FetchTypes. */ - public void setDefaultProperties(String properties, Set included) { - this.properties = properties; + public void setDefaultProperties(String properties, LinkedHashSet included) { + this.rawProperties = properties; this.trimmedProperties = properties; this.included = included; } @@ -215,7 +207,7 @@ public class OrmQueryProperties implements Serializable { */ public void setTunedProperties(OrmQueryProperties tunedProperties) { if (tunedProperties.hasProperties()) { - this.properties = tunedProperties.properties; + this.rawProperties = tunedProperties.rawProperties; this.trimmedProperties = tunedProperties.trimmedProperties; this.included = tunedProperties.included; this.queryFetchBatch = Math.max(queryFetchBatch, tunedProperties.queryFetchBatch); @@ -264,7 +256,7 @@ public class OrmQueryProperties implements Serializable { OrmQueryProperties copy = new OrmQueryProperties(); copy.parentPath = parentPath; copy.path = path; - copy.properties = properties; + copy.rawProperties = rawProperties; copy.trimmedProperties = trimmedProperties; copy.cache = cache; copy.readOnly = readOnly; @@ -273,7 +265,7 @@ public class OrmQueryProperties implements Serializable { copy.lazyFetchBatch = lazyFetchBatch; copy.filterMany = filterMany; if (included != null) { - copy.included = new HashSet(included); + copy.included = new LinkedHashSet(included); } if (includedBeanJoin != null) { copy.includedBeanJoin = new HashSet(includedBeanJoin); @@ -290,17 +282,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,35 +321,22 @@ public class OrmQueryProperties implements Serializable { } /** - * Calculate the query plan hash. + * Return the raw properties. */ - @SuppressWarnings("unchecked") - public void queryPlanHash(HashQueryPlanBuilder builder) { - - builder.add(path); - if (properties != null) { - builder.add(properties); - } else { - builder.add(included); - } - builder.add(filterMany != null); - if (filterMany != null) { - filterMany.queryPlanHash(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 has properties. Returning false means this part of the + * path is a partial object. */ public boolean hasProperties() { - return properties != null || included != null; + return included != null; + } + + public boolean allProperties() { + return included == null; } /** @@ -370,10 +360,6 @@ public class OrmQueryProperties implements Serializable { includedBeanJoin.add(propertyName); } - public boolean allProperties() { - return included == null; - } - /** * This excludes the bean joined properties. *

@@ -386,7 +372,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; @@ -400,30 +386,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) { @@ -480,96 +446,47 @@ public class OrmQueryProperties implements Serializable { 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 lazyFetchBatch == p2.lazyFetchBatch + && queryFetchBatch == p2.queryFetchBatch + && queryFetchAll == p2.queryFetchAll; + } + + /** + * 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(lazyFetchBatch); + builder.add(queryFetchBatch); + builder.add(queryFetchAll); + } + } 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..fce8b056d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesParser.java @@ -0,0 +1,183 @@ +package com.avaje.ebeaninternal.server.querydefn; + +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 int queryFetchBatch; + final int lazyFetchBatch; + 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.queryFetchBatch = queryFetchBatch; + this.lazyFetchBatch = lazyFetchBatch; + this.properties = properties; + this.included = included; + } + + public Response() { + this.readOnly = false; + this.cache = false; + this.queryFetchBatch = -1; + this.lazyFetchBatch = -1; + 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/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/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java b/src/test/java/com/avaje/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java new file mode 100644 index 000000000..fb98c62d8 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java @@ -0,0 +1,126 @@ +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.api.SpiQuery; +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.StrictAssertions.assertThat; + +public class DefaultServer_createOrmQueryRequestTest extends BaseTestCase { + + static DefaultServer defaultServer = (DefaultServer)Ebean.getDefaultServer(); + + Query query() { + return defaultServer.find(Order.class); + } + + OrmQueryRequest queryRequest(Query query) { + return (OrmQueryRequest)defaultServer.createQueryRequest(SpiQuery.Type.LIST, query, null); + } + + 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)); + } + + 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/querydefn/DefaultOrmQueryTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java index 50b4f558a..d3016e706 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java @@ -40,24 +40,6 @@ public class DefaultOrmQueryTest { assertThat(q1.queryBindHash()).isEqualTo(q2.queryBindHash()); } - - @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"); - - assertThat(query1.createQueryPlanKey()).isNotEqualTo(query2.createQueryPlanKey()); - } - @Test public void when_diffFirstMaxRows_then_differentPlan() throws Exception { 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..b12516431 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java @@ -2,20 +2,70 @@ package com.avaje.ebeaninternal.server.querydefn; import org.junit.Test; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.*; public class OrmQueryDetailTest { + OrmQueryDetail parse(String query) { + return new OrmQueryDetailParser(query).parse(); + } @Test - public void test_isAutoTuneEqual() { + public void isAutoTuneEqual_when_fetchOrderIsDifferent_then_stillEqual() { - OrmQueryDetailParser parser1 = new OrmQueryDetailParser("select (id,name) fetch customer (name) fetch details (code)"); - OrmQueryDetail detail1 = parser1.parse(); - - OrmQueryDetailParser parser2 = new OrmQueryDetailParser("select (id,name) fetch details (code) fetch customer (name)"); - OrmQueryDetail detail2 = parser2.parse(); + 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"); + } } \ 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..ab519e1bb --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesParserTest.java @@ -0,0 +1,132 @@ +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.lazyFetchBatch).isEqualTo(0); + assertThat(res.included).isNull(); + } + + @Test + public void when_hasLazyValue() throws Exception { + + OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("+lazy(20)"); + assertThat(res.lazyFetchBatch).isEqualTo(20); + assertThat(res.included).isNull(); + } + + @Test + public void when_hasLazyValue_last() throws Exception { + + OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("name,+lazy(20)"); + assertThat(res.lazyFetchBatch).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.lazyFetchBatch).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.lazyFetchBatch).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.lazyFetchBatch).isEqualTo(20); + assertThat(res.queryFetchBatch).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.lazyFetchBatch).isEqualTo(-1); + assertThat(res.queryFetchBatch).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..b1cc03494 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesTest.java @@ -0,0 +1,50 @@ +package com.avaje.ebeaninternal.server.querydefn; + +import org.junit.Test; + +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 + 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/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()); } From 5c6d6608ef11d1a72252e50597ae59f91d66d0e4 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Wed, 17 Feb 2016 13:59:48 +1300 Subject: [PATCH 14/18] #566 - Refactor internals - Initial tidy on OrmQueryDetail and OrmQueryDetailProperties - internals for fetch paths etc --- .../autotune/service/ProfileOrigin.java | 2 +- .../server/deploy/BeanDescriptor.java | 2 +- .../server/query/CQueryBuilder.java | 2 +- .../server/query/SqlTreeBuilder.java | 8 +- .../server/querydefn/DefaultOrmQuery.java | 2 +- .../server/querydefn/OrmQueryDetail.java | 63 +++--- .../querydefn/OrmQueryDetailParser.java | 5 +- .../server/querydefn/OrmQueryProperties.java | 186 +++++++++--------- ...faultServer_createOrmQueryRequestTest.java | 179 ++++++++++++++++- .../server/querydefn/OrmQueryDetailTest.java | 49 ++++- .../querydefn/OrmQueryPropertiesTest.java | 39 ++++ .../tests/query/TestQueryFindIterate.java | 5 +- 12 files changed, 397 insertions(+), 145 deletions(-) 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/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index fada56d65..2e14bd300 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -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/query/CQueryBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java index 9f5bf3181..ba9acce78 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java @@ -353,7 +353,7 @@ public class CQueryBuilder { // transfer PathProperties into OrmQueryDetail for (String path : pathProps.getPaths()) { - detail.getChunk(path, true).setDefaultProperties(null, pathProps.get(path)); + detail.fetch(path, pathProps.get(path)); } // check if @Id property included in RawSql 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 2fb6afb81..7d05cec6f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -1008,7 +1008,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; } 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 b9d643b67..4f6383c3b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -13,12 +13,12 @@ import javax.persistence.PersistenceException; 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; /** * Represents the internal structure of an Object Relational query. @@ -46,8 +46,6 @@ public class OrmQueryDetail implements Serializable { */ private LinkedHashMap fetchPaths = new LinkedHashMap(8); - private LinkedHashSet includes = new LinkedHashSet(8); - /** * Return a deep copy of the OrmQueryDetail. */ @@ -57,7 +55,6 @@ 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; } @@ -170,7 +167,7 @@ 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) { @@ -215,7 +212,6 @@ public class OrmQueryDetail implements Serializable { for (int i = 0; i < matchingPaths.size(); i++) { String path = matchingPaths.get(i); - includes.remove(path); OrmQueryProperties secQuery = fetchPaths.remove(path); if (secQuery != null) { props.add(secQuery); @@ -228,7 +224,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); } } @@ -257,29 +252,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); + } } /** @@ -289,7 +279,6 @@ public class OrmQueryDetail implements Serializable { *

*/ public void clear() { - includes.clear(); fetchPaths.clear(); } @@ -299,11 +288,16 @@ 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)); } public void sortFetchPaths(BeanDescriptor d) { @@ -438,15 +432,14 @@ public class OrmQueryDetail implements Serializable { public void setDefaultSelectClause(BeanDescriptor desc) { if (desc.hasDefaultSelectClause() && !hasSelectClause()) { - 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()); } } } @@ -489,7 +482,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 { @@ -498,9 +491,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); @@ -509,9 +502,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 a54d56da7..7b2109288 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java @@ -64,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(); @@ -157,7 +156,7 @@ public class OrmQueryDetailParser { private void readSelect() { String path = null; String props = parser.nextWord(); - if (props != null && props.startsWith("(")) { + if (props.startsWith("(")) { props = props.substring(1, props.length() - 1); OrmQueryProperties base = new OrmQueryProperties(path, props); detail.setBase(base); 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 2d2b12c09..625537773 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -28,11 +28,11 @@ public class OrmQueryProperties implements Serializable { private static final long serialVersionUID = -8785582703966455658L; - private String parentPath; - private String path; + private final String parentPath; + private final String path; - private String rawProperties; - private String trimmedProperties; + private final String rawProperties; + private final String trimmedProperties; /** * NB: -1 means no +query, 0 means use the default batch size. @@ -55,7 +55,7 @@ public class OrmQueryProperties implements Serializable { * 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 LinkedHashSet included; + private final LinkedHashSet included; /** * Included bean joins. @@ -83,25 +83,110 @@ 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; + } + + 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.lazyFetchBatch = response.lazyFetchBatch; + this.queryFetchBatch = response.queryFetchBatch; + this.cache = response.cache; + this.readOnly = response.readOnly; + + if (fetchConfig != null) { + this.fetchConfig = fetchConfig; + lazyFetchBatch = fetchConfig.getLazyBatchSize(); + queryFetchBatch = fetchConfig.getQueryBatchSize(); + queryFetchAll = fetchConfig.isQueryAll(); + } + } + + 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.lazyFetchBatch = -1; + this.queryFetchBatch = -1; + this.cache = false; + this.readOnly = false; + this.queryFetchAll = false; } /** - * Construct for root so path (and parentPath) are null. + * Join the set of properties into a comma delimited string. */ - public OrmQueryProperties() { + 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(); } /** - * Used by query language parser. + * Copy constructor. */ - public OrmQueryProperties(String path, String properties) { - this(path); - setProperties(properties); + 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.queryFetchAll = source.queryFetchAll; + this.queryFetchBatch = source.queryFetchBatch; + this.lazyFetchBatch = source.lazyFetchBatch; + 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); } /** @@ -115,41 +200,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 rawProperties) { - - OrmQueryPropertiesParser.Response response = OrmQueryPropertiesParser.parse(rawProperties); - - this.rawProperties = rawProperties; - this.trimmedProperties = response.properties; - this.included = response.included; - this.lazyFetchBatch = response.lazyFetchBatch; - this.queryFetchBatch = response.queryFetchBatch; - this.cache = response.cache; - this.readOnly = response.readOnly; - } - /** * Return the expressions used to filter on this path. This should be a many path to use this * method. @@ -193,28 +247,6 @@ public class OrmQueryProperties implements Serializable { this.filterMany = filterMany; } - /** - * Set the properties from deployment default FetchTypes. - */ - public void setDefaultProperties(String properties, LinkedHashSet included) { - this.rawProperties = 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.rawProperties = tunedProperties.rawProperties; - 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. */ @@ -249,30 +281,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.rawProperties = rawProperties; - 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 LinkedHashSet(included); - } - if (includedBeanJoin != null) { - copy.includedBeanJoin = new HashSet(includedBeanJoin); - } - return copy; - } - public boolean hasSelectClause() { if ("*".equals(trimmedProperties)) { // explicitly selected all properties 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 index fb98c62d8..7d7b0caf2 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java @@ -11,18 +11,18 @@ import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; import com.avaje.tests.model.basic.Order; import org.junit.Test; -import static org.assertj.core.api.StrictAssertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; public class DefaultServer_createOrmQueryRequestTest extends BaseTestCase { - static DefaultServer defaultServer = (DefaultServer)Ebean.getDefaultServer(); + static DefaultServer defaultServer = (DefaultServer) Ebean.getDefaultServer(); Query query() { return defaultServer.find(Order.class); } OrmQueryRequest queryRequest(Query query) { - return (OrmQueryRequest)defaultServer.createQueryRequest(SpiQuery.Type.LIST, query, null); + return (OrmQueryRequest) defaultServer.createQueryRequest(SpiQuery.Type.LIST, query, null); } OrmQueryDetail detail(Query query) { @@ -87,26 +87,26 @@ public class DefaultServer_createOrmQueryRequestTest extends BaseTestCase { // 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()))); + 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"))); + 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) + 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) + DefaultOrmQuery query2 = (DefaultOrmQuery) Ebean.find(Order.class) .select("status, shipDate") .fetch("details", "orderQty, unitPrice") .fetch("details.product", "sku, name"); @@ -114,6 +114,171 @@ public class DefaultServer_createOrmQueryRequestTest extends BaseTestCase { 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()); 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 b12516431..31c3dac5a 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java @@ -3,7 +3,9 @@ package com.avaje.ebeaninternal.server.querydefn; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.*; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; public class OrmQueryDetailTest { @@ -68,4 +70,49 @@ public class OrmQueryDetailTest { 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"); + } + } \ 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 index b1cc03494..8e50cc033 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesTest.java @@ -2,6 +2,8 @@ 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 { @@ -12,6 +14,43 @@ public class OrmQueryPropertiesTest { 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() { 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) From 27045baf44672f7adcb040e3207a796565329e32 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Wed, 17 Feb 2016 16:33:38 +1300 Subject: [PATCH 15/18] #566 - Refactor internals - OrmQueryDetailProperties with final FetchConfig --- .../java/com/avaje/ebean/FetchConfig.java | 18 ++ .../com/avaje/ebeaninternal/api/SpiQuery.java | 2 +- .../server/core/DefaultServer.java | 3 +- .../server/core/OrmQueryRequest.java | 5 +- .../server/querydefn/DefaultOrmQuery.java | 38 ++-- .../server/querydefn/OrmQueryDetail.java | 26 +-- .../server/querydefn/OrmQueryProperties.java | 72 +++----- .../querydefn/OrmQueryPropertiesParser.java | 14 +- .../java/com/avaje/ebean/FetchConfigTest.java | 168 ++++++++++++++++++ .../server/querydefn/OrmQueryDetailTest.java | 57 +++++- .../OrmQueryPropertiesParserTest.java | 19 +- 11 files changed, 314 insertions(+), 108 deletions(-) create mode 100644 src/test/java/com/avaje/ebean/FetchConfigTest.java 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/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java index 8a472a7d0..08bd5b9d4 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -338,7 +338,7 @@ public interface SpiQuery extends Query { /** * Convert joins as necessary to query joins etc. */ - void convertJoins(int queryBatchSize); + void convertJoins(); /** * Return the TransactionContext. 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 db55f32cf..eba6fd89d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -31,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; @@ -1065,7 +1064,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } OrmQueryRequest request = new OrmQueryRequest(this, queryEngine, query, desc, (SpiTransaction) t); - request.prepareQuery(queryBatchSize); + 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 69288b7be..c3666a8bf 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java @@ -148,12 +148,11 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe /** * Prepare the query and calculate the query plan key. */ - public void prepareQuery(int queryBatchSize) { + public void prepareQuery() { adapterPreQuery(); - // determine extra joins required to support where clause predicates on *ToMany properties - query.convertJoins(queryBatchSize); + query.convertJoins(); this.queryJoins = query.removeQueryJoins(); this.lazyJoins = query.removeLazyJoins(); this.queryPlanKey = query.prepare(this); 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 7d05cec6f..71ad5a8f3 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -375,10 +375,7 @@ public class DefaultOrmQuery implements SpiQuery { return expressionFactory; } - /** - * Return true if the where expressions contains a many property. - */ - private void initManyWhereJoins() { + private void createExtraJoinsToSupportManyWhereClause() { manyWhereJoins = new ManyWhereJoins(); if (whereExpressions != null) { whereExpressions.containsMany(beanDescriptor, manyWhereJoins); @@ -388,6 +385,9 @@ public class DefaultOrmQuery implements SpiQuery { } } + /** + * Return the extra joins required to support the where clause for 'Many' properties. + */ public ManyWhereJoins getManyWhereJoins() { return manyWhereJoins; } @@ -442,32 +442,30 @@ public class DefaultOrmQuery implements SpiQuery { this.lazyLoadManyPath = lazyLoadManyPath; } + @Override + public void convertJoins() { + + createExtraJoinsToSupportManyWhereClause(); + markQueryJoins(); + } + + /** + * Limit the number of fetch joins to Many properties, mark as query joins as needed. + */ + 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; } - @Override - public void convertJoins(int queryBatchSize) { - initManyWhereJoins(); - convertManyFetchJoinsToQueryJoins(queryBatchSize); - } - - /** - * Convert any many joins fetch joins to query joins. - */ - private void convertManyFetchJoinsToQueryJoins(int queryBatch) { - boolean allowOne = isAllowOneManyFetch(); - detail.convertManyFetchJoinsToQueryJoins(beanDescriptor, lazyLoadManyPath, allowOne, queryBatch); - } - protected void setOrmQueryDetail(OrmQueryDetail detail) { this.detail = detail; } 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 4f6383c3b..d96c7f91f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -7,7 +7,6 @@ import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; import com.avaje.ebeaninternal.server.el.ElPropertyValue; import com.avaje.ebeaninternal.server.query.SplitName; -import org.jetbrains.annotations.NotNull; import javax.persistence.PersistenceException; import java.io.Serializable; @@ -38,13 +37,12 @@ public class OrmQueryDetail implements Serializable { /** * Root level properties. */ - @NotNull private OrmQueryProperties baseProps = new OrmQueryProperties(); /** * Contains the fetch/lazy/query joins and their properties. */ - private LinkedHashMap fetchPaths = new LinkedHashMap(8); + private LinkedHashMap fetchPaths = new LinkedHashMap(); /** * Return a deep copy of the OrmQueryDetail. @@ -302,7 +300,7 @@ public class OrmQueryDetail implements Serializable { public void sortFetchPaths(BeanDescriptor d) { - LinkedHashMap sorted = new LinkedHashMap(fetchPaths.size()); + LinkedHashMap sorted = new LinkedHashMap(); for (OrmQueryProperties p : fetchPaths.values()) { sortFetchPaths(d, p, sorted); @@ -311,8 +309,7 @@ public class OrmQueryDetail implements Serializable { 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)) { @@ -325,8 +322,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(); @@ -340,12 +336,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; @@ -369,16 +362,11 @@ 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); - } } /** 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 625537773..6697ae3b4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -28,35 +28,27 @@ public class OrmQueryProperties implements Serializable { private static final long serialVersionUID = -8785582703966455658L; + protected static final FetchConfig DEFAULT_FETCH = new FetchConfig(); + private final String parentPath; private final String path; private final String rawProperties; private final String trimmedProperties; - /** - * NB: -1 means no +query, 0 means use the default batch size. - */ - private int queryFetchBatch = -1; - private boolean queryFetchAll; + private final LinkedHashSet included; + + private final FetchConfig fetchConfig; /** - * NB: -1 means no +lazy, 0 means use the default batch size. + * Flag set when this fetch path needs to be a query join. */ - 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 final LinkedHashSet included; - /** * Included bean joins. */ @@ -70,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; @@ -98,6 +89,7 @@ public class OrmQueryProperties implements Serializable { this.rawProperties = null; this.trimmedProperties = null; this.included = null; + this.fetchConfig = DEFAULT_FETCH; } public OrmQueryProperties(String path, String rawProperties) { @@ -113,16 +105,12 @@ public class OrmQueryProperties implements Serializable { this.rawProperties = rawProperties; this.trimmedProperties = response.properties; this.included = response.included; - this.lazyFetchBatch = response.lazyFetchBatch; - this.queryFetchBatch = response.queryFetchBatch; this.cache = response.cache; this.readOnly = response.readOnly; - if (fetchConfig != null) { this.fetchConfig = fetchConfig; - lazyFetchBatch = fetchConfig.getLazyBatchSize(); - queryFetchBatch = fetchConfig.getQueryBatchSize(); - queryFetchAll = fetchConfig.isQueryAll(); + } else { + this.fetchConfig = response.fetchConfig; } } @@ -137,11 +125,9 @@ public class OrmQueryProperties implements Serializable { this.included = parsedProperties; this.rawProperties = join(parsedProperties); this.trimmedProperties = rawProperties; - this.lazyFetchBatch = -1; - this.queryFetchBatch = -1; this.cache = false; this.readOnly = false; - this.queryFetchAll = false; + this.fetchConfig = DEFAULT_FETCH; } /** @@ -172,9 +158,7 @@ public class OrmQueryProperties implements Serializable { this.trimmedProperties = source.trimmedProperties; this.cache = source.cache; this.readOnly = source.readOnly; - this.queryFetchAll = source.queryFetchAll; - this.queryFetchBatch = source.queryFetchBatch; - this.lazyFetchBatch = source.lazyFetchBatch; + this.fetchConfig = source.fetchConfig; this.filterMany = source.filterMany; this.included = (source.included == null) ? null : new LinkedHashSet(source.included); if (includedBeanJoin != null) { @@ -216,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; } @@ -409,9 +391,11 @@ 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; } public boolean isFetchJoin() { @@ -419,23 +403,23 @@ public class OrmQueryProperties implements Serializable { } public boolean isQueryFetch() { - return queryFetchBatch > -1; + return markForQueryJoin || getQueryFetchBatch() > -1; } public int getQueryFetchBatch() { - return queryFetchBatch; + return fetchConfig.getQueryBatchSize(); } public boolean isQueryFetchAll() { - return queryFetchAll; + return fetchConfig.isQueryAll(); } public boolean isLazyFetch() { - return lazyFetchBatch > -1; + return getLazyFetchBatch() > -1; } public int getLazyFetchBatch() { - return lazyFetchBatch; + return fetchConfig.getLazyBatchSize(); } public boolean isReadOnly() { @@ -474,9 +458,7 @@ public class OrmQueryProperties implements Serializable { if (!Same.sameByNull(filterMany, p2.filterMany)) return false; if (filterMany != null && !filterMany.isSameByPlan(p2.filterMany)) return false; - return lazyFetchBatch == p2.lazyFetchBatch - && queryFetchBatch == p2.queryFetchBatch - && queryFetchAll == p2.queryFetchAll; + return fetchConfig.equals(p2.fetchConfig); } /** @@ -492,9 +474,7 @@ public class OrmQueryProperties implements Serializable { if (filterMany != null) { filterMany.queryPlanHash(builder); } - builder.add(lazyFetchBatch); - builder.add(queryFetchBatch); - builder.add(queryFetchAll); + 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 index fce8b056d..3027dc20b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesParser.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesParser.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.querydefn; +import com.avaje.ebean.FetchConfig; import com.avaje.ebeaninternal.server.lib.util.StringHelper; import java.util.LinkedHashSet; @@ -18,25 +19,26 @@ public class OrmQueryPropertiesParser { final boolean readOnly; final boolean cache; - final int queryFetchBatch; - final int lazyFetchBatch; + 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.queryFetchBatch = queryFetchBatch; - this.lazyFetchBatch = lazyFetchBatch; 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.queryFetchBatch = -1; - this.lazyFetchBatch = -1; + this.fetchConfig = OrmQueryProperties.DEFAULT_FETCH; this.properties = ""; this.included = null; } 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/ebeaninternal/server/querydefn/OrmQueryDetailTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java index 31c3dac5a..55c7bfd98 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java @@ -1,5 +1,8 @@ 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.assertj.core.api.Assertions.assertThat; @@ -7,7 +10,7 @@ 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(); @@ -115,4 +118,56 @@ public class OrmQueryDetailTest { 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(); + } + + BeanDescriptor orderDesc() { + return getBeanDescriptor(Order.class); + } + } \ 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 index ab519e1bb..fbc800db3 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesParserTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesParserTest.java @@ -74,7 +74,7 @@ public class OrmQueryPropertiesParserTest { public void when_hasLazy() throws Exception { OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("+lazy"); - assertThat(res.lazyFetchBatch).isEqualTo(0); + assertThat(res.fetchConfig.getLazyBatchSize()).isEqualTo(0); assertThat(res.included).isNull(); } @@ -82,7 +82,7 @@ public class OrmQueryPropertiesParserTest { public void when_hasLazyValue() throws Exception { OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("+lazy(20)"); - assertThat(res.lazyFetchBatch).isEqualTo(20); + assertThat(res.fetchConfig.getLazyBatchSize()).isEqualTo(20); assertThat(res.included).isNull(); } @@ -90,7 +90,7 @@ public class OrmQueryPropertiesParserTest { public void when_hasLazyValue_last() throws Exception { OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("name,+lazy(20)"); - assertThat(res.lazyFetchBatch).isEqualTo(20); + assertThat(res.fetchConfig.getLazyBatchSize()).isEqualTo(20); assertThat(res.included).containsExactly("name"); } @@ -98,7 +98,7 @@ public class OrmQueryPropertiesParserTest { public void when_hasLazyValue_first() throws Exception { OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("+lazy(20),id,name"); - assertThat(res.lazyFetchBatch).isEqualTo(20); + assertThat(res.fetchConfig.getLazyBatchSize()).isEqualTo(20); assertThat(res.included).containsExactly("id", "name"); } @@ -106,17 +106,16 @@ public class OrmQueryPropertiesParserTest { public void when_allProperties() throws Exception { OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("+query(4),+lazy(5)"); - assertThat(res.lazyFetchBatch).isEqualTo(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.lazyFetchBatch).isEqualTo(20); - assertThat(res.queryFetchBatch).isEqualTo(30); + 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"); @@ -125,8 +124,8 @@ public class OrmQueryPropertiesParserTest { private void assertAllDefaults(OrmQueryPropertiesParser.Response res) { assertThat(res.cache).isFalse(); assertThat(res.readOnly).isFalse(); - assertThat(res.lazyFetchBatch).isEqualTo(-1); - assertThat(res.queryFetchBatch).isEqualTo(-1); + assertThat(res.fetchConfig.getLazyBatchSize()).isEqualTo(-1); + assertThat(res.fetchConfig.getQueryBatchSize()).isEqualTo(-1); assertThat(res.included).isNull(); } } \ No newline at end of file From 3d3948aa8ba81220f95fa223ca82fc94adb9e625 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Wed, 17 Feb 2016 16:47:38 +1300 Subject: [PATCH 16/18] #566 - Refactor internals - OrmQueryDetail, skip fetch path sort when empty --- .../server/querydefn/OrmQueryDetail.java | 15 ++++---- .../server/querydefn/OrmQueryDetailTest.java | 34 +++++++++++++++++++ 2 files changed, 43 insertions(+), 6 deletions(-) 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 d96c7f91f..7af5d97ac 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -298,15 +298,18 @@ public class OrmQueryDetail implements Serializable { 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(); - - 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) { 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 55c7bfd98..9a3b4edea 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java @@ -166,6 +166,40 @@ public class OrmQueryDetailTest extends BaseTestCase { 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); } From 439858b259b9ba91432a1b5195bf054f417a7240 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Wed, 17 Feb 2016 16:53:32 +1300 Subject: [PATCH 17/18] #566 - Refactor internals -OrmQueryDetailProperties - remove hasProperties() --- .../server/querydefn/OrmQueryDetail.java | 2 +- .../server/querydefn/OrmQueryProperties.java | 7 +----- .../server/querydefn/OrmQueryDetailTest.java | 23 +++++++++++++++++++ 3 files changed, 25 insertions(+), 7 deletions(-) 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 7af5d97ac..47fb077c6 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -444,7 +444,7 @@ public class OrmQueryDetail implements Serializable { * Return true if the query detail has neither select properties specified or any joins defined. */ public boolean isEmpty() { - return fetchPaths.isEmpty() && (!baseProps.hasProperties()); + return fetchPaths.isEmpty() && baseProps.allProperties(); } /** 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 6697ae3b4..00eaebc3c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -318,13 +318,8 @@ public class OrmQueryProperties implements Serializable { } /** - * Return true if this has properties. Returning false means this part of the - * path is a partial object. + * Return true if this includes all properties on the path. */ - public boolean hasProperties() { - return included != null; - } - public boolean allProperties() { return included == null; } 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 9a3b4edea..0ca744e09 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java @@ -16,6 +16,29 @@ public class OrmQueryDetailTest extends BaseTestCase { return new OrmQueryDetailParser(query).parse(); } + @Test + public void isEmpty_when_empty() { + + OrmQueryDetail detail = new OrmQueryDetail(); + assertThat(detail.isEmpty()).isTrue(); + } + + @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() { From c83606c1daf7b702859b68dc4e6a7534ca2f4e4e Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Wed, 17 Feb 2016 19:44:30 +1300 Subject: [PATCH 18/18] #566 - Refactor internals - SpiQuerySecondary and DLoadContext --- .../avaje/ebeaninternal/api/LoadContext.java | 5 -- .../com/avaje/ebeaninternal/api/SpiQuery.java | 18 +---- .../ebeaninternal/api/SpiQuerySecondary.java | 21 ++++++ .../server/core/OrmQueryRequest.java | 13 +--- .../server/loadcontext/DLoadBaseContext.java | 16 +--- .../server/loadcontext/DLoadBeanContext.java | 8 +- .../server/loadcontext/DLoadContext.java | 70 +++++++++-------- .../server/querydefn/DefaultOrmQuery.java | 9 ++- .../server/querydefn/OrmQueryProperties.java | 50 ++++++++++--- .../server/querydefn/OrmQuerySecondary.java | 39 ++++++++++ ...faultServer_createOrmQueryRequestTest.java | 7 +- .../core/OrmQueryRequestTestHelper.java | 18 +++++ .../server/loadcontext/DLoadContextTest.java | 75 +++++++++++++++++++ 13 files changed, 248 insertions(+), 101 deletions(-) create mode 100644 src/main/java/com/avaje/ebeaninternal/api/SpiQuerySecondary.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQuerySecondary.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/core/OrmQueryRequestTestHelper.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContextTest.java diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadContext.java b/src/main/java/com/avaje/ebeaninternal/api/LoadContext.java index 82fd3c8c7..c22199e9b 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/LoadContext.java +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadContext.java @@ -25,11 +25,6 @@ public interface LoadContext { */ void executeSecondaryQueries(OrmQueryRequest parentRequest); - /** - * Register any secondary queries (+query or +lazy) with their appropriate LoadBeanContext or LoadManyContext. - */ - void registerSecondaryQueries(List queryJoins, List lazyJoins); - /** * Return the node for a given path which is used by AutoTune profiling. */ diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java index 08bd5b9d4..69831f8f3 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -314,22 +314,6 @@ 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. */ @@ -338,7 +322,7 @@ public interface SpiQuery extends Query { /** * Convert joins as necessary to query joins etc. */ - void convertJoins(); + SpiQuerySecondary convertJoins(); /** * Return the TransactionContext. 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/core/OrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java index c3666a8bf..76ea1aaae 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java @@ -19,6 +19,7 @@ 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; @@ -28,7 +29,6 @@ import com.avaje.ebeaninternal.server.deploy.DeployPropertyParserMap; import com.avaje.ebeaninternal.server.loadcontext.DLoadContext; import com.avaje.ebeaninternal.server.query.CQueryPlan; import com.avaje.ebeaninternal.server.query.CancelableQuery; -import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext; import javax.persistence.PersistenceException; @@ -63,9 +63,7 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe private CQueryPlanKey queryPlanKey; - private List queryJoins; - - private List lazyJoins; + private SpiQuerySecondary secondaryQueries; /** * Create the InternalQueryRequest. @@ -152,9 +150,7 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe adapterPreQuery(); - query.convertJoins(); - this.queryJoins = query.removeQueryJoins(); - this.lazyJoins = query.removeLazyJoins(); + this.secondaryQueries = query.convertJoins(); this.queryPlanKey = query.prepare(this); } @@ -216,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(queryJoins, lazyJoins); + this.loadContext = new DLoadContext(this, secondaryQueries); } /** 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 efd6490bd..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() { @@ -136,36 +172,6 @@ public class DLoadContext implements LoadContext { return beanLoad; } - /** - * Register the +query and +lazy secondary queries with their appropriate LoadBeanContext or LoadManyContext. - */ - public void registerSecondaryQueries(List queryJoins, List lazyJoins) { - - this.secQuery = queryJoins; - if (secQuery != null) { - for (int i = 0; i < secQuery.size(); i++) { - registerSecondaryQuery(secQuery.get(i)); - } - } - if (lazyJoins != null) { - for (int i = 0; i < lazyJoins.size(); i++) { - registerSecondaryQuery(lazyJoins.get(i)); - } - } - } - - /** - * 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); - } - public ObjectGraphNode getObjectGraphNode(String path) { ObjectGraphNode node = nodePathMap.get(path); @@ -233,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/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 71ad5a8f3..258c404c4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -19,6 +19,7 @@ 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; @@ -406,7 +407,7 @@ public class DefaultOrmQuery implements SpiQuery { return false; } - public List removeQueryJoins() { + protected List removeQueryJoins() { List queryJoins = detail.removeSecondaryQueries(); if (queryJoins != null) { if (orderBy != null) { @@ -434,7 +435,7 @@ public class DefaultOrmQuery implements SpiQuery { return queryJoins; } - public List removeLazyJoins() { + protected List removeLazyJoins() { return detail.removeSecondaryLazyQueries(); } @@ -443,10 +444,12 @@ public class DefaultOrmQuery implements SpiQuery { } @Override - public void convertJoins() { + public SpiQuerySecondary convertJoins() { createExtraJoinsToSupportManyWhereClause(); markQueryJoins(); + + return new OrmQuerySecondary(removeQueryJoins(), removeLazyJoins()); } /** 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 00eaebc3c..f6fcf960f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -393,42 +393,72 @@ public class OrmQueryProperties implements Serializable { markForQueryJoin = true; } - public boolean isFetchJoin() { - return !isQueryFetch() && !isLazyFetch(); - } - + /** + * Return true if this path is a 'query join'. + */ public boolean isQueryFetch() { return markForQueryJoin || getQueryFetchBatch() > -1; } - public int getQueryFetchBatch() { - return fetchConfig.getQueryBatchSize(); - } - - public boolean isQueryFetchAll() { - return fetchConfig.isQueryAll(); + /** + * Return true if this path is a 'fetch join'. + */ + public boolean isFetchJoin() { + return !isQueryFetch() && !isLazyFetch(); } + /** + * Return true if this path is a lazy fetch. + */ public boolean isLazyFetch() { 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 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; } 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/test/java/com/avaje/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java b/src/test/java/com/avaje/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java index 7d7b0caf2..d61015f1e 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java @@ -5,7 +5,6 @@ import com.avaje.ebean.BaseTestCase; import com.avaje.ebean.Ebean; import com.avaje.ebean.FetchConfig; import com.avaje.ebean.Query; -import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.server.querydefn.DefaultOrmQuery; import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; import com.avaje.tests.model.basic.Order; @@ -15,14 +14,12 @@ import static org.assertj.core.api.Assertions.assertThat; public class DefaultServer_createOrmQueryRequestTest extends BaseTestCase { - static DefaultServer defaultServer = (DefaultServer) Ebean.getDefaultServer(); - Query query() { - return defaultServer.find(Order.class); + return server().find(Order.class); } OrmQueryRequest queryRequest(Query query) { - return (OrmQueryRequest) defaultServer.createQueryRequest(SpiQuery.Type.LIST, query, null); + return OrmQueryRequestTestHelper.queryRequest(query); } OrmQueryDetail detail(Query query) { 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/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