diff --git a/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java b/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java index 78d8e5f8c..527860200 100644 --- a/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java +++ b/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java @@ -395,6 +395,15 @@ public final class EntityBeanIntercept implements Serializable { this.owner._ebean_setEmbeddedLoaded(); this.lazyLoadProperty = -1; this.origValues = null; + // after save, transfer the mutable next values back to mutable info + if (mutableNext != null) { + for (int i = 0; i < mutableNext.length; i++) { + MutableValueNext next = mutableNext[i]; + if (next != null) { + mutableInfo(i, next.info()); + } + } + } this.mutableNext = null; for (int i = 0; i < flags.length; i++) { flags[i] &= ~(FLAG_CHANGED_PROP | FLAG_ORIG_VALUE_SET); @@ -1223,9 +1232,7 @@ public final class EntityBeanIntercept implements Serializable { if (mutableNext == null) { return null; } - final MutableValueNext next = mutableNext[propertyIndex]; - mutableInfo(propertyIndex, next.info()); - return next.content(); + return mutableNext[propertyIndex].content(); } } diff --git a/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/BaseQueryTuner.java b/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/BaseQueryTuner.java index af969fd53..b93be5cc8 100644 --- a/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/BaseQueryTuner.java +++ b/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/BaseQueryTuner.java @@ -146,7 +146,8 @@ public class BaseQueryTuner { case ID_LIST: case UPDATE: case DELETE: - case SUBQUERY: + case SQ_EXISTS: + case SQ_IN: return false; default: // not using autoTune when explicitly loading the l2 bean cache diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 11aac552a..5324aec82 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -20,22 +20,13 @@ - db2 + - com.ibm.jdbc - db2jcc4 - 4.23.42 + com.ibm.db2 + jcc + 11.5.5.0 test @@ -72,7 +63,7 @@ io.ebean ebean-ddl-generator - 12.9.4-RC1 + 12.11.0 test diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/BindParams.java b/ebean-core/src/main/java/io/ebeaninternal/api/BindParams.java index afb1e44b8..64a0a0287 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/BindParams.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/BindParams.java @@ -4,11 +4,7 @@ import io.ebeaninternal.server.persist.MultiValueWrapper; import io.ebeaninternal.server.querydefn.NaturalKeyBindParam; import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; /** @@ -54,12 +50,11 @@ public class BindParams implements Serializable { positionedParameters.clear(); } - public int queryBindHash() { - int hc = namedParameters.hashCode(); - for (Param positionedParameter : positionedParameters) { - hc = hc * 92821 + positionedParameter.hashCode(); + public void queryBindHash(BindValuesKey key) { + key.add(positionedParameters.size()); + for (Param param : positionedParameters) { + param.queryBindHash(key); } - return hc; } /** @@ -424,7 +419,14 @@ public class BindParams implements Serializable { @Override public boolean equals(Object o) { - return o != null && (o == this || (o instanceof Param) && hashCode() == o.hashCode()); + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Param param = (Param) o; + return isInParam == param.isInParam && isOutParam == param.isOutParam && type == param.type && Objects.equals(inValue, param.inValue); + } + + void queryBindHash(BindValuesKey key) { + key.add(isInParam).add(isOutParam).add(type).add(inValue); } /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/BindValuesKey.java b/ebean-core/src/main/java/io/ebeaninternal/api/BindValuesKey.java new file mode 100644 index 000000000..4a6eac324 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/api/BindValuesKey.java @@ -0,0 +1,35 @@ +package io.ebeaninternal.api; + +import java.util.ArrayList; +import java.util.List; + +/** + * BindValues used for L2 query cache key matching. + *

+ * The equals/hashCode implementation must meet the requirement that the query bind values + * match for L2 query cache hit (given the query plan hash is already a match). + */ +public class BindValuesKey { + + private final List values = new ArrayList<>(); + + /** + * Add a bind value. + */ + public BindValuesKey add(Object value) { + values.add(value); + return this; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof BindValuesKey && ((BindValuesKey) obj).values.equals(values); + } + + @Override + public int hashCode() { + return values.hashCode(); + } + + +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/HashQuery.java b/ebean-core/src/main/java/io/ebeaninternal/api/HashQuery.java index c14cb88d7..faddb7bee 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/HashQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/HashQuery.java @@ -6,15 +6,14 @@ package io.ebeaninternal.api; public class HashQuery { private final CQueryPlanKey planHash; - - private final int bindHash; + private final BindValuesKey bindValuesKey; /** * Create the HashQuery. */ - public HashQuery(CQueryPlanKey planHash, int bindHash) { + public HashQuery(CQueryPlanKey planHash, BindValuesKey bindValuesKey) { this.planHash = planHash; - this.bindHash = bindHash; + this.bindValuesKey = bindValuesKey; } @Override @@ -25,7 +24,7 @@ public class HashQuery { @Override public int hashCode() { int hc = 92821 * planHash.hashCode(); - hc = 92821 * hc + bindHash; + hc = 92821 * hc + bindValuesKey.hashCode(); return hc; } @@ -37,8 +36,7 @@ public class HashQuery { if (!(obj instanceof HashQuery)) { return false; } - HashQuery e = (HashQuery) obj; - return e.bindHash == bindHash && e.planHash.equals(planHash); + return e.bindValuesKey.equals(bindValuesKey) && e.planHash.equals(planHash); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/LoadBeanRequest.java b/ebean-core/src/main/java/io/ebeaninternal/api/LoadBeanRequest.java index fef666a63..9d72463a5 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/LoadBeanRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/LoadBeanRequest.java @@ -92,9 +92,7 @@ public class LoadBeanRequest extends LoadRequest { * Return the list of Id values for the beans in the lazy load buffer. */ public List getIdList() { - List idList = new ArrayList<>(); - BeanDescriptor desc = loadBuffer.getBeanDescriptor(); for (EntityBeanIntercept ebi : batch) { idList.add(desc.getId(ebi.getOwner())); @@ -106,10 +104,8 @@ public class LoadBeanRequest extends LoadRequest { * Configure the query for lazy loading execution. */ public void configureQuery(SpiQuery query, List idList) { - query.setMode(SpiQuery.Mode.LAZYLOAD_BEAN); query.setPersistenceContext(loadBuffer.getPersistenceContext()); - String mode = isLazy() ? "+lazy" : "+query"; query.setLoadDescription(mode, getDescription()); @@ -117,9 +113,7 @@ public class LoadBeanRequest extends LoadRequest { // cascade the batch size (if set) for further lazy loading query.setLazyLoadBatchSize(getBatchSize()); } - loadBuffer.configureQuery(query, lazyLoadProperty); - if (idList.size() == 1) { query.where().idEq(idList.get(0)); } else { @@ -131,19 +125,16 @@ public class LoadBeanRequest extends LoadRequest { * Load the beans into the L2 cache if that is requested and check for load failures due to deletes. */ public void postLoad(List list) { - Set loadedIds = new HashSet<>(); - BeanDescriptor desc = loadBuffer.getBeanDescriptor(); // collect Ids and maybe load bean cache - for (Object aList : list) { - EntityBean loadedBean = (EntityBean) aList; + for (Object bean : list) { + EntityBean loadedBean = (EntityBean) bean; loadedIds.add(desc.getId(loadedBean)); } if (isLoadCache()) { desc.cacheBeanPutAll(list); } - if (lazyLoadProperty != null) { for (EntityBeanIntercept ebi : batch) { // check if the underlying row in DB was deleted. Mark the bean as 'failed' if diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java index daf6418d0..fae60f49d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java @@ -8,6 +8,7 @@ import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.event.readaudit.ReadAuditLogger; import io.ebean.event.readaudit.ReadAuditPrepare; import io.ebean.meta.MetricVisitor; +import io.ebeaninternal.api.SpiQuery.Type; import io.ebeaninternal.server.core.SpiResultSet; import io.ebeaninternal.server.core.timezone.DataTimeZone; import io.ebeaninternal.server.deploy.BeanDescriptor; @@ -149,7 +150,7 @@ public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanCollect /** * Compile a query. */ - CQuery compileQuery(Query query, Transaction t); + CQuery compileQuery(Type type, Query query, Transaction t); /** * Execute the findId's query but without copying the query. diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiExpression.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiExpression.java index 007e9f29f..8fe986259 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiExpression.java @@ -54,9 +54,9 @@ public interface SpiExpression extends Expression { void queryPlanHash(StringBuilder builder); /** - * Return the hash value for the values that will be bound. + * Build the key for bind values of the query. */ - int queryBindHash(); + void queryBindKey(BindValuesKey key); /** * Return true if the expression is the same with respect to bind values. diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java index 12942e7d6..5a6ae2217 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java @@ -84,7 +84,7 @@ public interface SpiQuery extends Query, SpiQueryFetch, TxnProfileEventCod /** * Find single attribute. */ - ATTRIBUTE(FIND_ATTRIBUTE, "findAttribute"), + ATTRIBUTE(FIND_ATTRIBUTE, "findAttribute", false, false), /** * Find rowCount. @@ -92,9 +92,14 @@ public interface SpiQuery extends Query, SpiQueryFetch, TxnProfileEventCod COUNT(FIND_COUNT, "findCount"), /** - * A subquery used as part of a where clause. + * A subquery used as part of an exists where clause. */ - SUBQUERY(FIND_SUBQUERY, "subquery"), + SQ_EXISTS(FIND_SUBQUERY, "sqExists", false, false), + + /** + * A subquery used as part of an in where clause. + */ + SQ_IN(FIND_SUBQUERY, "sqIn", false, false), /** * Delete query. @@ -107,17 +112,21 @@ public interface SpiQuery extends Query, SpiQueryFetch, TxnProfileEventCod UPDATE(FIND_UPDATE, "update", true); private final boolean update; + private final boolean defaultSelect; private final String profileEventId; private final String label; Type(String profileEventId, String label) { - this(profileEventId, label, false); + this(profileEventId, label, false, true); } - Type(String profileEventId, String label, boolean update) { + this(profileEventId, label, update, true); + } + Type(String profileEventId, String label, boolean update, boolean defaultSelect) { this.profileEventId = profileEventId; this.label = label; this.update = update; + this.defaultSelect = defaultSelect; } /** @@ -127,6 +136,13 @@ public interface SpiQuery extends Query, SpiQueryFetch, TxnProfileEventCod return update; } + /** + * Return true if this allows default select clause. + */ + public boolean defaultSelect() { + return defaultSelect; + } + public String profileEventId() { return profileEventId; } @@ -629,13 +645,11 @@ public interface SpiQuery extends Query, SpiQueryFetch, TxnProfileEventCod CQueryPlanKey prepare(SpiOrmQueryRequest request); /** - * Calculate a hash based on the bind values used in the query. + * Build the key for the bind values used in the query (for l2 query cache). *

- * Combined with queryPlanHash() to return getQueryHash (a unique hash for a - * query). - *

+ * Combined with queryPlanHash() to return queryHash (a unique key for a query). */ - int queryBindHash(); + void queryBindKey(BindValuesKey key); /** * Identifies queries that are exactly the same including bind variables. diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 2040e3f78..c52befdcc 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -526,8 +526,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { * Compile a query. Only valid for ORM queries. */ @Override - public CQuery compileQuery(Query query, Transaction t) { - SpiOrmQueryRequest qr = createQueryRequest(Type.SUBQUERY, query, t); + public CQuery compileQuery(Type type, Query query, Transaction t) { + SpiOrmQueryRequest qr = createQueryRequest(type, query, t); OrmQueryRequest orm = (OrmQueryRequest) qr; return cqueryEngine.buildQuery(orm); } @@ -2055,17 +2055,15 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return transactionManager; } - public void register(BeanPersistController c) { - List> list = beanDescriptorManager.getBeanDescriptorList(); - for (BeanDescriptor aList : list) { - aList.register(c); + public void register(BeanPersistController controller) { + for (BeanDescriptor desc : beanDescriptorManager.getBeanDescriptorList()) { + desc.register(controller); } } public void deregister(BeanPersistController c) { - List> list = beanDescriptorManager.getBeanDescriptorList(); - for (BeanDescriptor aList : list) { - aList.deregister(c); + for (BeanDescriptor desc : beanDescriptorManager.getBeanDescriptorList()) { + desc.deregister(c); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index 8e8b02b6f..055f2605a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -2519,7 +2519,7 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { if (propName.indexOf('(') > -1) { return findSqlTreeFormula(propName, path); } - return _findBeanProperty(propName); + return findProperty(propName); } /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java index 79ca0beb1..f0184948e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java @@ -702,13 +702,11 @@ public class DeployBeanDescriptor { } public void sortProperties() { - ArrayList list = new ArrayList<>(propMap.values()); list.sort(PROP_ORDER); - propMap = new LinkedHashMap<>(list.size()); - for (DeployBeanProperty aList : list) { - addBeanProperty(aList); + for (DeployBeanProperty property : list) { + addBeanProperty(property); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/AbstractTextExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/AbstractTextExpression.java index d30649149..363bcb08d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/AbstractTextExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/AbstractTextExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -37,9 +38,9 @@ public abstract class AbstractTextExpression extends AbstractExpression { } @Override - public int queryBindHash() { - return 0; - } + public void queryBindKey(BindValuesKey key) { + // do nothing, only execute against document store + }; @Override public boolean isSameByBind(SpiExpression other) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/AllEqualsExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/AllEqualsExpression.java index 5a931ec5a..56761dcf2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/AllEqualsExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/AllEqualsExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -122,14 +123,11 @@ class AllEqualsExpression extends NonPrepareExpression { } @Override - public int queryBindHash() { - - int hc = 92821; + public void queryBindKey(BindValuesKey key) { + key.add(propMap.size()); for (Object value : propMap.values()) { - hc = hc * 92821 + (value == null ? 0 : value.hashCode()); + key.add(value); } - - return hc; } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/ArrayContainsExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/ArrayContainsExpression.java index 8c12bec60..7a9e903f6 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/ArrayContainsExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/ArrayContainsExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -49,12 +50,11 @@ public class ArrayContainsExpression extends AbstractExpression { } @Override - public int queryBindHash() { - int hc = values[0].hashCode(); - for (int i = 1; i < values.length; i++) { - hc = hc * 92821 + values[i].hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(values.length); + for (Object value : values) { + key.add(value); } - return hc; } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/ArrayIsEmptyExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/ArrayIsEmptyExpression.java index 815223dfe..f9cd530c8 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/ArrayIsEmptyExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/ArrayIsEmptyExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -33,8 +34,8 @@ public class ArrayIsEmptyExpression extends AbstractExpression { } @Override - public int queryBindHash() { - return empty ? 0 : 92821; + public void queryBindKey(BindValuesKey key) { + key.add(empty); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/BetweenExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/BetweenExpression.java index bfdfa1d52..ed3d58d8b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/BetweenExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/BetweenExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -49,10 +50,8 @@ class BetweenExpression extends AbstractExpression { } @Override - public int queryBindHash() { - int hc = low().hashCode(); - hc = hc * 92821 + high().hashCode(); - return hc; + public void queryBindKey(BindValuesKey key) { + key.add(low()).add(high()); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/BetweenPropertyExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/BetweenPropertyExpression.java index a50ec888f..55b08380d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/BetweenPropertyExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/BetweenPropertyExpression.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.expression; import io.ebean.util.SplitName; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -95,8 +96,8 @@ class BetweenPropertyExpression extends NonPrepareExpression { } @Override - public int queryBindHash() { - return val().hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(val()); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/BitwiseExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/BitwiseExpression.java index 073ee51df..460af37ec 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/BitwiseExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/BitwiseExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -39,8 +40,8 @@ class BitwiseExpression extends AbstractExpression { } @Override - public int queryBindHash() { - return Long.hashCode(flags); + public void queryBindKey(BindValuesKey key) { + key.add(flags).add(match); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java index 52e39fbfa..3a6224d9c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; import io.ebeaninternal.server.el.ElPropertyValue; @@ -69,8 +70,8 @@ class CaseInsensitiveEqualExpression extends AbstractValueExpression { } @Override - public int queryBindHash() { - return val().hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(val()); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExampleExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExampleExpression.java index aade99f5a..cd8a59857 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExampleExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExampleExpression.java @@ -5,6 +5,7 @@ import io.ebean.LikeType; import io.ebean.bean.EntityBean; import io.ebean.event.BeanQueryRequest; import io.ebean.util.SplitName; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiExpression; @@ -136,10 +137,8 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins whereManyJoins) { list = buildExpressions(desc); - if (list != null) { - for (SpiExpression aList : list) { - aList.containsMany(desc, whereManyJoins); - } + for (SpiExpression expr : list) { + expr.containsMany(desc, whereManyJoins); } } @@ -186,8 +185,8 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio @Override public void validate(SpiExpressionValidation validation) { - for (SpiExpression aList : list) { - aList.validate(validation); + for (SpiExpression expr : list) { + expr.validate(validation); } } @@ -228,25 +227,20 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio */ @Override public void queryPlanHash(StringBuilder builder) { - builder.append("Example["); - for (SpiExpression aList : list) { - aList.queryPlanHash(builder); + for (SpiExpression expr : list) { + expr.queryPlanHash(builder); builder.append(","); } builder.append("]"); } - /** - * Return a hash for the actual bind values used. - */ @Override - public int queryBindHash() { - int hc = DefaultExampleExpression.class.getName().hashCode(); - for (SpiExpression aList : list) { - hc = hc * 92821 + aList.queryBindHash(); + public void queryBindKey(BindValuesKey key) { + key.add(list.size()); + for (SpiExpression expr : list) { + expr.queryBindKey(key); } - return hc; } @Override @@ -267,7 +261,6 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio * Build the List of expressions. */ private ArrayList buildExpressions(BeanDescriptor beanDescriptor) { - ArrayList list = new ArrayList<>(); addExpressions(list, beanDescriptor, entity, null); return list; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java index ab80ae9e5..ed3f57e14 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java @@ -26,6 +26,7 @@ import io.ebean.search.MultiMatch; import io.ebean.search.TextCommonTerms; import io.ebean.search.TextQueryString; import io.ebean.search.TextSimple; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiExpression; @@ -109,10 +110,8 @@ public class DefaultExpressionList implements SpiExpressionList { * @return A single SpiExpression that has the nestedPath set */ SpiExpression wrap(List list, String nestedPath, Junction.Type type) { - DefaultExpressionList wrapper = new DefaultExpressionList<>(query, expr, null, list, false); wrapper.setAllDocNested(nestedPath); - if (type != null) { return new JunctionExpression<>(type, wrapper); } else { @@ -121,15 +120,15 @@ public class DefaultExpressionList implements SpiExpressionList { } void simplifyEntries() { - for (SpiExpression element : list) { - element.simplify(); + for (SpiExpression expr : list) { + expr.simplify(); } } @Override public void prefixProperty(String path) { - for (SpiExpression exp : list) { - exp.prefixProperty(path); + for (SpiExpression expr : list) { + expr.prefixProperty(path); } } @@ -174,7 +173,6 @@ public class DefaultExpressionList implements SpiExpressionList { context.startNested(allDocNestedPath); } int size = list.size(); - SpiExpression first = list.get(0); boolean explicitBool = first instanceof SpiJunction; boolean implicitBool = !explicitBool && size > 1; @@ -210,7 +208,6 @@ public class DefaultExpressionList implements SpiExpressionList { @Override public void writeDocQuery(DocQueryContext context, SpiExpression idEquals) throws IOException { - if (allDocNestedPath != null) { context.startNested(allDocNestedPath); } @@ -227,8 +224,8 @@ public class DefaultExpressionList implements SpiExpressionList { if (idEquals != null) { idEquals.writeDocQuery(context); } - for (SpiExpression aList : list) { - aList.writeDocQuery(context); + for (SpiExpression expr : list) { + expr.writeDocQuery(context); } context.endBool(); } @@ -278,16 +275,15 @@ public class DefaultExpressionList implements SpiExpressionList { */ @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins whereManyJoins) { - - for (SpiExpression aList : list) { - aList.containsMany(desc, whereManyJoins); + for (SpiExpression expr : list) { + expr.containsMany(desc, whereManyJoins); } } @Override public void validate(SpiExpressionValidation validation) { - for (SpiExpression aList : list) { - aList.validate(validation); + for (SpiExpression expr : list) { + expr.validate(validation); } } @@ -630,7 +626,6 @@ public class DefaultExpressionList implements SpiExpressionList { @Override public void addSql(SpiExpressionRequest request) { - for (int i = 0, size = list.size(); i < size; i++) { SpiExpression expression = list.get(i); if (i > 0) { @@ -642,15 +637,15 @@ public class DefaultExpressionList implements SpiExpressionList { @Override public void addBindValues(SpiExpressionRequest request) { - for (SpiExpression aList : list) { - aList.addBindValues(request); + for (SpiExpression expr : list) { + expr.addBindValues(request); } } @Override public void prepareExpression(BeanQueryRequest request) { - for (SpiExpression aList : list) { - aList.prepareExpression(request); + for (SpiExpression expr : list) { + expr.prepareExpression(request); } } @@ -667,23 +662,19 @@ public class DefaultExpressionList implements SpiExpressionList { if (allDocNestedPath != null) { builder.append("path:").append(allDocNestedPath).append(" "); } - for (SpiExpression aList : list) { - aList.queryPlanHash(builder); + for (SpiExpression expr : list) { + expr.queryPlanHash(builder); builder.append(","); } builder.append("]"); } - /** - * Calculate a hash based on the expressions. - */ @Override - public int queryBindHash() { - int hash = DefaultExpressionList.class.getName().hashCode(); - for (SpiExpression aList : list) { - hash = hash * 92821 + aList.queryBindHash(); + public void queryBindKey(BindValuesKey key) { + key.add(list.size()); + for (SpiExpression expr : list) { + expr.queryBindKey(key); } - return hash; } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/ExistsQueryExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/ExistsQueryExpression.java index 32ae2bccc..71ca665fe 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/ExistsQueryExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/ExistsQueryExpression.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.expression; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiEbeanServer; @@ -8,6 +9,7 @@ import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; import io.ebeaninternal.api.SpiExpressionValidation; import io.ebeaninternal.api.SpiQuery; +import io.ebeaninternal.api.SpiQuery.Type; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.query.CQuery; @@ -81,7 +83,7 @@ class ExistsQueryExpression implements SpiExpression, UnsupportedDocStoreExpress */ protected CQuery compileSubQuery(BeanQueryRequest queryRequest) { SpiEbeanServer ebeanServer = (SpiEbeanServer) queryRequest.getEbeanServer(); - return ebeanServer.compileQuery(subQuery, queryRequest.getTransaction()); + return ebeanServer.compileQuery(Type.SQ_EXISTS, subQuery, queryRequest.getTransaction()); } @Override @@ -91,8 +93,8 @@ class ExistsQueryExpression implements SpiExpression, UnsupportedDocStoreExpress } @Override - public int queryBindHash() { - return subQuery.queryBindHash(); + public void queryBindKey(BindValuesKey key) { + subQuery.queryBindKey(key); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/IdExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/IdExpression.java index b5280b868..94cab6de7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/IdExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/IdExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -77,8 +78,8 @@ class IdExpression extends NonPrepareExpression implements SpiExpression { } @Override - public int queryBindHash() { - return value.hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(value); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/IdInExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/IdInExpression.java index 4c08b24d2..edf56b443 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/IdInExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/IdInExpression.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.expression; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -133,8 +134,11 @@ public class IdInExpression extends NonPrepareExpression { } @Override - public int queryBindHash() { - return idCollection.hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(idCollection.size()); + for (Object elem : idCollection) { + key.add(elem); + } } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InExpression.java index 7009a8b44..0bc5a4588 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InExpression.java @@ -2,6 +2,7 @@ package io.ebeaninternal.server.expression; import io.ebean.bean.EntityBean; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -176,12 +177,11 @@ class InExpression extends AbstractExpression { } @Override - public int queryBindHash() { - int hc = 92821; + public void queryBindKey(BindValuesKey key) { + key.add(bindValues.size()); for (Object bindValue : bindValues) { - hc = 92821 * hc + bindValue.hashCode(); + key.add(bindValue); } - return hc; } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InPairsExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InPairsExpression.java index 96cc7c182..e5bd27413 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InPairsExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InPairsExpression.java @@ -3,6 +3,7 @@ package io.ebeaninternal.server.expression; import io.ebean.Pairs; import io.ebean.Pairs.Entry; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -124,12 +125,11 @@ class InPairsExpression extends AbstractExpression { } @Override - public int queryBindHash() { - int hc = 92821; + public void queryBindKey(BindValuesKey key) { + key.add(entries.size()); for (Pairs.Entry entry : entries) { - hc = 92821 * hc + entry.hashCode(); + key.add(entry.getA()).add(entry.getB()); } - return hc; } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InQueryExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InQueryExpression.java index 83507af4b..a2d6d787c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InQueryExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InQueryExpression.java @@ -1,10 +1,12 @@ package io.ebeaninternal.server.expression; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; import io.ebeaninternal.api.SpiQuery; +import io.ebeaninternal.api.SpiQuery.Type; import io.ebeaninternal.server.query.CQuery; import java.io.IOException; @@ -68,12 +70,12 @@ class InQueryExpression extends AbstractExpression implements UnsupportedDocStor private CQuery compileSubQuery(BeanQueryRequest queryRequest) { SpiEbeanServer ebeanServer = (SpiEbeanServer) queryRequest.getEbeanServer(); - return ebeanServer.compileQuery(subQuery, queryRequest.getTransaction()); + return ebeanServer.compileQuery(Type.SQ_IN, subQuery, queryRequest.getTransaction()); } @Override - public int queryBindHash() { - return subQuery.queryBindHash(); + public void queryBindKey(BindValuesKey key) { + subQuery.queryBindKey(key); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InRangeExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InRangeExpression.java index e1d170b43..04b242ba8 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InRangeExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InRangeExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -47,10 +48,8 @@ class InRangeExpression extends AbstractExpression { } @Override - public int queryBindHash() { - int hc = low().hashCode(); - hc = hc * 92821 + high().hashCode(); - return hc; + public void queryBindKey(BindValuesKey key) { + key.add(low()).add(high()); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/IsEmptyExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/IsEmptyExpression.java index d3a3a1da6..d6b878bfa 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/IsEmptyExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/IsEmptyExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -103,8 +104,8 @@ class IsEmptyExpression extends AbstractExpression { } @Override - public int queryBindHash() { - return 1; + public void queryBindKey(BindValuesKey key) { + // no bind values } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/JsonPathExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/JsonPathExpression.java index f60b125cb..1d954dcc1 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/JsonPathExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/JsonPathExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -83,10 +84,8 @@ class JsonPathExpression extends AbstractExpression { } @Override - public int queryBindHash() { - int hc = (value == null) ? 0 : value.hashCode(); - hc = (upperValue == null) ? hc : hc * 92821 + upperValue.hashCode(); - return hc; + public void queryBindKey(BindValuesKey key) { + key.add(value).add(upperValue); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java index 0aae30886..5637fba7f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java @@ -25,6 +25,7 @@ import io.ebean.search.MultiMatch; import io.ebean.search.TextCommonTerms; import io.ebean.search.TextQueryString; import io.ebean.search.TextSimple; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiExpression; @@ -113,9 +114,8 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression @Override public void writeDocQuery(DocQueryContext context) throws IOException { context.startBool(type); - List list = exprList.internalList(); - for (SpiExpression aList : list) { - aList.writeDocQuery(context); + for (SpiExpression expr : exprList.internalList()) { + expr.writeDocQuery(context); } context.endBool(); } @@ -123,9 +123,8 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression @Override public void writeDocQueryJunction(DocQueryContext context) throws IOException { context.startBoolGroupList(type); - List list = exprList.internalList(); - for (SpiExpression aList : list) { - aList.writeDocQuery(context); + for (SpiExpression expr : exprList.internalList()) { + expr.writeDocQuery(context); } context.endBoolGroupList(); } @@ -138,18 +137,15 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { - List list = exprList.internalList(); - // get the current state for 'require outer joins' boolean parentOuterJoins = manyWhereJoin.isRequireOuterJoins(); if (type == Type.OR) { // turn on outer joins required for disjunction expressions manyWhereJoin.setRequireOuterJoins(true); } - - for (SpiExpression aList : list) { - aList.containsMany(desc, manyWhereJoin); + for (SpiExpression expr : list) { + expr.containsMany(desc, manyWhereJoin); } if (type == Type.OR && !parentOuterJoins) { // restore state to not forcing outer joins @@ -176,18 +172,14 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression @Override public void addBindValues(SpiExpressionRequest request) { - - List list = exprList.internalList(); - for (SpiExpression aList : list) { - aList.addBindValues(request); + for (SpiExpression expr : exprList.internalList()) { + expr.addBindValues(request); } } @Override public void addSql(SpiExpressionRequest request) { - List list = exprList.internalList(); - if (!list.isEmpty()) { request.append(type.prefix()); request.append("("); @@ -204,9 +196,8 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression @Override public void prepareExpression(BeanQueryRequest request) { - List list = exprList.internalList(); - for (SpiExpression aList : list) { - aList.prepareExpression(request); + for (SpiExpression expr : exprList.internalList()) { + expr.prepareExpression(request); } } @@ -216,22 +207,18 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression @Override public void queryPlanHash(StringBuilder builder) { builder.append(type).append("["); - List list = exprList.internalList(); - for (SpiExpression aList : list) { - aList.queryPlanHash(builder); + for (SpiExpression expr : exprList.internalList()) { + expr.queryPlanHash(builder); builder.append(","); } builder.append("]"); } @Override - public int queryBindHash() { - int hc = JunctionExpression.class.getName().hashCode(); - List list = exprList.internalList(); - for (SpiExpression aList : list) { - hc = hc * 92821 + aList.queryBindHash(); + public void queryBindKey(BindValuesKey key) { + for (SpiExpression expr : exprList.internalList()) { + expr.queryBindKey(key); } - return hc; } @Override @@ -275,7 +262,6 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression return exprList.textCommonTerms(search, options); } - @Override public ExpressionList allEq(Map propertyMap) { return exprList.allEq(propertyMap); @@ -1025,7 +1011,6 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression @Override public String nestedPath(BeanDescriptor desc) { - PrepareDocNested.prepare(exprList, desc, type); String nestedPath = exprList.allDocNestedPath; if (nestedPath != null) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/LikeExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/LikeExpression.java index 6886203c4..f40501929 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/LikeExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/LikeExpression.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.expression; import io.ebean.LikeType; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; import io.ebeaninternal.server.el.ElPropertyValue; @@ -70,8 +71,8 @@ class LikeExpression extends AbstractValueExpression { } @Override - public int queryBindHash() { - return strValue().hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(strValue()); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/LogicExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/LogicExpression.java index 482aac6e8..a66e2e88a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/LogicExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/LogicExpression.java @@ -3,6 +3,7 @@ package io.ebeaninternal.server.expression; import io.ebean.Expression; import io.ebean.Junction; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiExpression; @@ -168,10 +169,8 @@ abstract class LogicExpression implements SpiExpression { } @Override - public int queryBindHash() { - int hc = expOne.queryBindHash(); - hc = hc * 92821 + expTwo.queryBindHash(); - return hc; + public void queryBindKey(BindValuesKey key) { + key.add(expOne).add(expTwo); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NativeILikeExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NativeILikeExpression.java index c3376f656..f64933df5 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NativeILikeExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NativeILikeExpression.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.expression; import io.ebean.LikeType; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; import io.ebeaninternal.server.el.ElPropertyValue; @@ -54,8 +55,8 @@ class NativeILikeExpression extends AbstractExpression { } @Override - public int queryBindHash() { - return val.hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(val); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NestedPathWrapperExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NestedPathWrapperExpression.java index 77f46828b..b76bfef4d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NestedPathWrapperExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NestedPathWrapperExpression.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.expression; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -79,8 +80,8 @@ class NestedPathWrapperExpression implements SpiExpression { } @Override - public int queryBindHash() { - return delegate.queryBindHash(); + public void queryBindKey(BindValuesKey key) { + delegate.queryBindKey(key); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NoopExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NoopExpression.java index 28c9e3c78..6c8d709e7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NoopExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NoopExpression.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.expression; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiExpression; @@ -74,9 +75,8 @@ class NoopExpression implements SpiExpression { } @Override - public int queryBindHash() { + public void queryBindKey(BindValuesKey key) { // no bind values - return 0; } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NotExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NotExpression.java index 60ade47b0..462e60d9e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NotExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NotExpression.java @@ -2,6 +2,7 @@ package io.ebeaninternal.server.expression; import io.ebean.Expression; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiExpression; @@ -99,8 +100,8 @@ final class NotExpression implements SpiExpression { } @Override - public int queryBindHash() { - return exp.queryBindHash(); + public void queryBindKey(BindValuesKey key) { + exp.queryBindKey(key); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NullExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NullExpression.java index 90894dbde..2808acce7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NullExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NullExpression.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.expression; import io.ebean.util.SplitName; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -94,7 +95,7 @@ class NullExpression extends AbstractExpression { } @Override - public int queryBindHash() { - return (notNull ? 1 : 0); + public void queryBindKey(BindValuesKey key) { + key.add(notNull); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/RawExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/RawExpression.java index 295ddc75b..9e1c3e8dc 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/RawExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/RawExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -73,12 +74,11 @@ class RawExpression extends NonPrepareExpression { } @Override - public int queryBindHash() { - int hc = sql.hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(values.length); for (Object value : values) { - hc = hc * 92821 + value.hashCode(); + key.add(value); } - return hc; } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/SimpleExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/SimpleExpression.java index 0f8de90ec..bc23b84f2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/SimpleExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/SimpleExpression.java @@ -5,6 +5,7 @@ import io.ebean.plugin.ExpressionPath; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; import io.ebeaninternal.server.el.ElPropertyValue; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.NaturalKeyQueryData; import java.io.IOException; @@ -121,8 +122,8 @@ public class SimpleExpression extends AbstractValueExpression { } @Override - public int queryBindHash() { - return value().hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(value()); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/BindValues.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/BindValues.java deleted file mode 100644 index 6d71e8d79..000000000 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/BindValues.java +++ /dev/null @@ -1,81 +0,0 @@ -package io.ebeaninternal.server.persist; - -import java.util.ArrayList; - -/** - * Holds a list of bind values for binding to a PreparedStatement. - */ -class BindValues { - - private final ArrayList list = new ArrayList<>(); - - /** - * Create with a Binder. - */ - public BindValues() { - } - - /** - * Add a bind value with its JDBC datatype. - * - * @param value the bind value - * @param dbType the type as per java.sql.Types - */ - public void add(Object value, int dbType, String name) { - list.add(new Value(value, dbType, name)); - } - - /** - * List of bind values. - */ - public ArrayList values() { - return list; - } - - /** - * A Value has additionally the JDBC data type. - */ - public static class Value { - - private final Object value; - - private final int dbType; - - private final String name; - - /** - * Create the value. - */ - Value(Object value, int dbType, String name) { - this.value = value; - this.dbType = dbType; - this.name = name; - } - - /** - * Return the type as per java.sql.Types. - */ - public int getDbType() { - return dbType; - } - - /** - * Return the value. - */ - public Object getValue() { - return value; - } - - /** - * Return the property name. - */ - public String getName() { - return name; - } - - @Override - public String toString() { - return String.valueOf(value); - } - } -} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/Binder.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/Binder.java index 1d94d7a81..600a7e21a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/Binder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/Binder.java @@ -77,33 +77,6 @@ public class Binder { return asOfStandardsBased; } - /** - * Bind the values to the Prepared Statement. - */ - public void bind(BindValues bindValues, DataBind dataBind, StringBuilder bindBuf) throws SQLException { - String logPrefix = ""; - ArrayList list = bindValues.values(); - for (BindValues.Value bindValue : list) { - Object val = bindValue.getValue(); - int dt = bindValue.getDbType(); - bindObject(dataBind, val, dt); - - if (bindBuf != null) { - bindBuf.append(logPrefix); - if (logPrefix.isEmpty()) { - logPrefix = ", "; - } - bindBuf.append(bindValue.getName()); - bindBuf.append("="); - if (isLob(dt)) { - bindBuf.append("[LOB]"); - } else { - bindBuf.append(val); - } - } - } - } - /** * Bind the parameters to the preparedStatement returning the bind log. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java index 41439e4b6..9a4b1d32e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java @@ -46,9 +46,7 @@ class CQueryBuilder { private final SqlLimiter sqlLimiter; private final CQueryBuilderRawSql rawSqlHandler; private final Binder binder; - private final boolean selectCountWithAlias; - private final CQueryHistorySupport historySupport; private final CQueryDraftSupport draftSupport; private final DatabasePlatform dbPlatform; @@ -79,7 +77,6 @@ class CQueryBuilder { if (sb.length() > 0) { sb.append(", "); } - sb.append(name); sb.append("."); sb.append(token.trim()); @@ -91,7 +88,6 @@ class CQueryBuilder { * Build the delete query. */ CQueryUpdate buildUpdateQuery(boolean deleteRequest, OrmQueryRequest request) { - SpiQuery query = request.getQuery(); String rootTableAlias = query.getAlias(); query.setupForDeleteOrUpdate(); @@ -105,7 +101,6 @@ class CQueryBuilder { } predicates.prepare(true); - SqlTree sqlTree = createSqlTree(request, predicates); String sql; @@ -114,7 +109,6 @@ class CQueryBuilder { } else { sql = buildUpdateSql(request, rootTableAlias, predicates, sqlTree); } - // cache the query plan queryPlan = new CQueryPlan(request, sql, sqlTree, predicates.getLogWhereSql()); request.putQueryPlan(queryPlan); @@ -122,7 +116,6 @@ class CQueryBuilder { } private String buildDeleteSql(OrmQueryRequest request, String rootTableAlias, CQueryPredicates predicates, SqlTree sqlTree) { - String alias = alias(rootTableAlias); if (sqlTree.noJoins() && !request.getQuery().hasMaxRowsOrFirstRow()) { if (dbPlatform.isSupportsDeleteTableAlias()) { @@ -151,7 +144,6 @@ class CQueryBuilder { } private String buildUpdateSql(OrmQueryRequest request, String rootTableAlias, CQueryPredicates predicates, SqlTree sqlTree) { - StringBuilder sb = new StringBuilder(200); sb.append("update ").append(request.getBeanDescriptor().getBaseTable()); if (rootTableAlias != null) { @@ -159,7 +151,6 @@ class CQueryBuilder { } sb.append(" set ").append(predicates.getDbUpdateClause()); String updateClause = sb.toString(); - if (sqlTree.noJoins() && request.isInlineSqlUpdateLimit()) { // simple - update table set ... where ... return aliasStrip(buildSqlUpdate(updateClause, request, predicates, sqlTree).getSql()); @@ -186,7 +177,6 @@ class CQueryBuilder { } CQueryFetchSingleAttribute buildFetchAttributeQuery(OrmQueryRequest request) { - SpiQuery query = request.getQuery(); query.setSingleAttribute(); if (!query.isIncludeSoftDeletes()) { @@ -218,7 +208,6 @@ class CQueryBuilder { * Build the find ids query. */ CQueryFetchSingleAttribute buildFetchIdsQuery(OrmQueryRequest request) { - SpiQuery query = request.getQuery(); query.setSelectId(); BeanDescriptor desc = request.getBeanDescriptor(); @@ -246,9 +235,7 @@ class CQueryBuilder { * Build the row count query. */ CQueryRowCount buildRowCountQuery(OrmQueryRequest request) { - SpiQuery query = request.getQuery(); - // always set the order by to null for row count query query.setOrder(null); query.setFirstRow(0); @@ -310,7 +297,6 @@ class CQueryBuilder { // cache the query plan queryPlan = new CQueryPlan(request, sql, sqlTree, predicates.getLogWhereSql()); request.putQueryPlan(queryPlan); - return new CQueryRowCount(queryPlan, request, predicates); } @@ -334,9 +320,7 @@ class CQueryBuilder { * names to physical deployment column names. */ CQuery buildQuery(OrmQueryRequest request) { - CQueryPredicates predicates = new CQueryPredicates(binder, request); - CQueryPlan queryPlan = request.getQueryPlan(); if (queryPlan != null) { // Reuse the query plan so skip generating SqlTree and SQL. @@ -370,7 +354,6 @@ class CQueryBuilder { boolean rawSql = request.isRawSql(); if (rawSql) { queryPlan = new CQueryPlanRawSql(request, res, sqlTree, predicates.getLogWhereSql()); - } else { queryPlan = new CQueryPlan(request, res, sqlTree, false, predicates.getLogWhereSql()); } @@ -380,11 +363,9 @@ class CQueryBuilder { // log the query plan based bean type (i.e. ignoring query disabling for logging the sql/plan) desc.getReadAuditLogger().queryPlan(new ReadAuditQueryPlan(desc.getFullName(), queryPlan.getAuditQueryKey(), queryPlan.getSql())); } - // cache the query plan because we can reuse it and also // gather query performance statistics based on it. request.putQueryPlan(queryPlan); - return new CQuery<>(request, predicates, queryPlan); } @@ -393,18 +374,15 @@ class CQueryBuilder { *

* The SqlTree is immutable after construction and so is safe to use by * concurrent threads. - *

*

* The predicates is used to add additional joins that come from the where or * order by clauses that are not already included for the select clause. - *

*/ private SqlTree createSqlTree(OrmQueryRequest request, CQueryPredicates predicates) { return createSqlTree(request, predicates, false); } private SqlTree createSqlTree(OrmQueryRequest request, CQueryPredicates predicates, boolean forceColumnAlias) { - if (request.isNativeSql()) { return createNativeSqlTree(request, predicates); } @@ -423,9 +401,7 @@ class CQueryBuilder { * Create the SqlTree by reading the ResultSetMetaData and mapping table/columns to bean property paths. */ private SqlTree createNativeSqlTree(OrmQueryRequest request, CQueryPredicates predicates) { - SpiQuery query = request.getQuery(); - // parse named parameters returning the final sql to execute String sql = predicates.parseBindParams(query.getNativeSql()); if (query.hasMaxRowsOrFirstRow()) { @@ -469,10 +445,8 @@ class CQueryBuilder { } private SqlTree createRawSqlSqlTree(OrmQueryRequest request, CQueryPredicates predicates) { - BeanDescriptor descriptor = request.getBeanDescriptor(); ColumnMapping columnMapping = request.getQuery().getRawSql().getColumnMapping(); - PathProperties pathProps = new PathProperties(); // convert list of columns into (tree like) PathProperties @@ -498,12 +472,10 @@ class CQueryBuilder { } OrmQueryDetail detail = new OrmQueryDetail(); - // transfer PathProperties into OrmQueryDetail for (PathProperties.Props props : pathProps.getPathProps()) { detail.fetch(props.getPath(), props.getProperties()); } - // check if @Id property included in RawSql boolean rawNoId = true; BeanProperty idProperty = descriptor.getIdProperty(); @@ -511,7 +483,6 @@ class CQueryBuilder { // contains the @Id property for the root level bean rawNoId = false; } - // build SqlTree based on OrmQueryDetail of the RawSql return new SqlTreeBuilder(request, predicates, detail, rawNoId).build(); } @@ -603,7 +574,6 @@ class CQueryBuilder { private void appendSelect() { if (selectClause != null) { sb.append(selectClause); - } else { useSqlLimiter = (query.hasMaxRowsOrFirstRow() && select.getManyProperty() == null); if (!useSqlLimiter) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTree.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTree.java index 1befa650b..5f548fd59 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTree.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTree.java @@ -101,6 +101,7 @@ class SqlTree { * Return the String for the actual SQL. */ String getSelectSql() { + assert selectSql != null : "selectSql was null"; return selectSql; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java index 239e8a58e..65fa4f7f8 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java @@ -102,7 +102,11 @@ public final class SqlTreeBuilder { this.query = request.getQuery(); this.temporalMode = SpiQuery.TemporalMode.of(query); this.disableLazyLoad = query.isDisableLazyLoading(); - this.subQuery = Type.SUBQUERY == query.getType() || Type.ID_LIST == query.getType() || Type.DELETE == query.getType() || query.isCountDistinct(); + this.subQuery = Type.SQ_EXISTS == query.getType() + || Type.SQ_IN == query.getType() + || Type.ID_LIST == query.getType() + || Type.DELETE == query.getType() + || query.isCountDistinct(); this.includeJoin = query.getM2mIncludeJoin(); this.manyWhereJoins = query.getManyWhereJoins(); this.queryDetail = query.getDetail(); @@ -146,6 +150,10 @@ public final class SqlTreeBuilder { if (rawSql) { return "Not Used"; } + if (query.getType() == Type.SQ_EXISTS) { + // effective query is "where exists (select 1 from ...)" + return "1"; + } rootNode.appendSelect(ctx, subQuery); return trimComma(ctx.getContent()); } @@ -311,7 +319,7 @@ public final class SqlTreeBuilder { } else { // do not read Id on child beans (e.g. when used with fetch()) - boolean withId = isNotSingleAttribute(); + boolean withId = isNotSingleAttribute() && !subQuery; return new SqlTreeNodeBean(prefix, prop, props, myList, withId, temporalMode, disableLazyLoad); } } @@ -365,11 +373,11 @@ public final class SqlTreeBuilder { * This means it can included individual properties of an embedded bean. *

*/ - private void addPropertyToSubQuery(SqlTreeProperties selectProps, STreeType desc, String propName) { - STreeProperty p = desc.findProperty(propName); + private void addPropertyToSubQuery(SqlTreeProperties selectProps, STreeType desc, String propName, String path) { + STreeProperty p = desc.findPropertyWithDynamic(propName, path); if (p == null) { logger.error("property [" + propName + "]not found on " + desc + " for query - excluding it."); - + return; } else if (p instanceof STreePropertyAssoc && p.isEmbedded()) { // if the property is embedded we need to lookup the real column name int pos = propName.indexOf('.'); @@ -383,7 +391,7 @@ public final class SqlTreeBuilder { private void addProperty(SqlTreeProperties selectProps, STreeType desc, OrmQueryProperties queryProps, String propName) { if (subQuery) { - addPropertyToSubQuery(selectProps, desc, propName); + addPropertyToSubQuery(selectProps, desc, propName, queryProps.getPath()); return; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 96a923637..b5aeec323 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -1,30 +1,7 @@ package io.ebeaninternal.server.querydefn; -import io.ebean.CacheMode; -import io.ebean.CountDistinctOrder; -import io.ebean.Database; -import io.ebean.DtoQuery; -import io.ebean.Expression; -import io.ebean.ExpressionFactory; -import io.ebean.ExpressionList; -import io.ebean.FetchConfig; -import io.ebean.FetchGroup; -import io.ebean.FetchPath; -import io.ebean.FutureIds; -import io.ebean.FutureList; -import io.ebean.FutureRowCount; -import io.ebean.OrderBy; +import io.ebean.*; import io.ebean.OrderBy.Property; -import io.ebean.PagedList; -import io.ebean.PersistenceContextScope; -import io.ebean.ProfileLocation; -import io.ebean.Query; -import io.ebean.QueryIterator; -import io.ebean.QueryType; -import io.ebean.RawSql; -import io.ebean.Transaction; -import io.ebean.UpdateQuery; -import io.ebean.Version; import io.ebean.bean.CallOrigin; import io.ebean.bean.ObjectGraphNode; import io.ebean.bean.ObjectGraphOrigin; @@ -32,29 +9,10 @@ import io.ebean.bean.PersistenceContext; import io.ebean.event.BeanQueryRequest; import io.ebean.event.readaudit.ReadEvent; import io.ebean.plugin.BeanType; -import io.ebeaninternal.api.BindParams; -import io.ebeaninternal.api.CQueryPlanKey; -import io.ebeaninternal.api.CacheIdLookup; -import io.ebeaninternal.api.CacheIdLookupMany; -import io.ebeaninternal.api.CacheIdLookupSingle; -import io.ebeaninternal.api.HashQuery; -import io.ebeaninternal.api.ManyWhereJoins; -import io.ebeaninternal.api.NaturalKeyQueryData; -import io.ebeaninternal.api.SpiEbeanServer; -import io.ebeaninternal.api.SpiExpression; -import io.ebeaninternal.api.SpiExpressionList; -import io.ebeaninternal.api.SpiExpressionValidation; -import io.ebeaninternal.api.SpiNamedParam; -import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.api.SpiQuerySecondary; -import io.ebeaninternal.api.SpiTransaction; +import io.ebeaninternal.api.*; import io.ebeaninternal.server.autotune.ProfilingListener; import io.ebeaninternal.server.core.SpiOrmQueryRequest; -import io.ebeaninternal.server.deploy.BeanDescriptor; -import io.ebeaninternal.server.deploy.BeanNaturalKey; -import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; -import io.ebeaninternal.server.deploy.InheritInfo; -import io.ebeaninternal.server.deploy.TableJoin; +import io.ebeaninternal.server.deploy.*; import io.ebeaninternal.server.el.ElPropertyDeploy; import io.ebeaninternal.server.expression.DefaultExpressionList; import io.ebeaninternal.server.expression.IdInExpression; @@ -66,14 +24,7 @@ import io.ebeaninternal.server.transaction.ExternalJdbcTransaction; import javax.persistence.PersistenceException; import java.sql.Connection; import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.locks.ReentrantLock; +import java.util.*; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Stream; @@ -91,8 +42,6 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery { private static final FetchConfig FETCH_LAZY = FetchConfig.ofLazy(); - private final ReentrantLock lock = new ReentrantLock(); - private final Class beanType; private final ExpressionFactory expressionFactory; @@ -616,7 +565,7 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery { * Limit the number of fetch joins to Many properties, mark as query joins as needed. */ private void markQueryJoins() { - detail.markQueryJoins(beanDescriptor, lazyLoadManyPath, isAllowOneManyFetch(), type != Type.ATTRIBUTE); + detail.markQueryJoins(beanDescriptor, lazyLoadManyPath, isAllowOneManyFetch(), type.defaultSelect()); } private boolean isAllowOneManyFetch() { @@ -629,7 +578,7 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery { @Override public void setDefaultSelectClause() { - if (type != Type.ATTRIBUTE) { + if (type.defaultSelect()) { detail.setDefaultSelectClause(beanDescriptor); } else if (!detail.hasSelectClause()) { // explicit empty select when single attribute query on non-root fetch path @@ -1269,22 +1218,13 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery { } } - /** - * Calculate a hash based on the bind values used in the query. - *

- * Used with queryPlanHash() to get a unique hash for a query. - *

- */ @Override - public int queryBindHash() { - int hc = (id == null ? 0 : id.hashCode()); - hc = hc * 92821 + (whereExpressions == null ? 0 : whereExpressions.queryBindHash()); - hc = hc * 92821 + (havingExpressions == null ? 0 : havingExpressions.queryBindHash()); - hc = hc * 92821 + (bindParams == null ? 0 : bindParams.queryBindHash()); - hc = hc * 92821 + (asOf == null ? 0 : asOf.hashCode()); - hc = hc * 92821 + (versionsStart == null ? 0 : versionsStart.hashCode()); - hc = hc * 92821 + (versionsEnd == null ? 0 : versionsEnd.hashCode()); - return hc; + public void queryBindKey(BindValuesKey key) { + key.add(id); + if (whereExpressions != null) whereExpressions.queryBindKey(key); + if (havingExpressions != null) havingExpressions.queryBindKey(key); + if (bindParams != null) bindParams.queryBindHash(key); + key.add(asOf).add(versionsStart).add(versionsEnd); } /** @@ -1298,8 +1238,9 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery { public HashQuery queryHash() { // calculateQueryPlanHash is called just after potential AutoTune tuning // so queryPlanHash is calculated well before this method is called - int hc = queryBindHash(); - return new HashQuery(queryPlanKey, hc); + BindValuesKey bindKey = new BindValuesKey(); + queryBindKey(bindKey); + return new HashQuery(queryPlanKey, bindKey); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java index 7ae24420f..41cb28d49 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java @@ -80,11 +80,11 @@ class ScalarTypeJsonObjectMapper { @Override public Object read(DataReader reader) throws SQLException { String json = reader.getString(); + // pushJson such that we MD5 and store on EntityBeanIntercept later + reader.pushJson(json); if (json == null || json.isEmpty()) { return null; } - // pushJson such that we MD5 and store on EntityBeanIntercept later - reader.pushJson(json); try { return objectReader.readValue(json, deserType); } catch (IOException e) { diff --git a/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java b/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java index b09a0f54e..cb7e613a3 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java +++ b/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java @@ -45,6 +45,7 @@ import io.ebean.plugin.Property; import io.ebean.plugin.SpiServer; import io.ebean.text.csv.CsvReader; import io.ebean.text.json.JsonContext; +import io.ebeaninternal.api.SpiQuery.Type; import io.ebeaninternal.server.core.SpiResultSet; import io.ebeaninternal.server.core.timezone.DataTimeZone; import io.ebeaninternal.server.deploy.BeanDescriptor; @@ -54,7 +55,6 @@ import io.ebeaninternal.server.transaction.RemoteTransactionEvent; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; import javax.sql.DataSource; -import java.lang.reflect.Type; import java.time.Clock; import java.util.Collection; import java.util.Collections; @@ -256,7 +256,7 @@ public class TDSpiEbeanServer implements SpiEbeanServer { } @Override - public CQuery compileQuery(Query query, Transaction t) { + public CQuery compileQuery(Type type, Query query, Transaction t) { return null; } @@ -316,7 +316,7 @@ public class TDSpiEbeanServer implements SpiEbeanServer { } @Override - public boolean isSupportedType(Type genericType) { + public boolean isSupportedType(java.lang.reflect.Type genericType) { return false; } diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/expression/RawExpressionTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/expression/RawExpressionTest.java index 1953f2959..eb12ff460 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/expression/RawExpressionTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/expression/RawExpressionTest.java @@ -3,6 +3,7 @@ package io.ebeaninternal.server.expression; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; +import io.ebeaninternal.api.BindValuesKey; public class RawExpressionTest extends BaseExpressionTest { @@ -61,11 +62,16 @@ public class RawExpressionTest extends BaseExpressionTest { } public void assert_queryBindHash_isDifferent(RawExpression exp0, RawExpression exp1) { - assertThat(exp0.queryBindHash()).isNotEqualTo(exp1.queryBindHash()); + assertThat(bindKey(exp0)).isNotEqualTo(bindKey(exp1)); } public void assert_queryBindHash_isSame(RawExpression exp0, RawExpression exp1) { - assertThat(exp0.queryBindHash()).isEqualTo(exp1.queryBindHash()); + assertThat(bindKey(exp0)).isEqualTo(bindKey(exp1)); } + private BindValuesKey bindKey(RawExpression query) { + BindValuesKey bindValuesKey = new BindValuesKey(); + query.queryBindKey(bindValuesKey); + return bindValuesKey; + } } diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/profile/BasicProfileLocationTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/profile/BasicProfileLocationTest.java index 84a53442a..9d99b17ce 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/profile/BasicProfileLocationTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/profile/BasicProfileLocationTest.java @@ -14,7 +14,11 @@ public class BasicProfileLocationTest { assertThat(loc.obtain()).isTrue(); assertThat(loc.fullLocation()).endsWith(":12)"); - assertThat(loc.location()).isEqualTo("java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0"); + if (System.getProperty("java.version").startsWith("1.8")) { + assertThat(loc.location()).isEqualTo("sun.reflect.NativeMethodAccessorImpl.invoke0"); + } else { + assertThat(loc.location()).isEqualTo("java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0"); + } assertThat(loc.label()).isEqualTo("NativeMethodAccessorImpl.invoke0"); } diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/BindValuesKeyTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/BindValuesKeyTest.java new file mode 100644 index 000000000..1a7d3b39f --- /dev/null +++ b/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/BindValuesKeyTest.java @@ -0,0 +1,38 @@ +package io.ebeaninternal.server.querydefn; + +import io.ebeaninternal.api.BindValuesKey; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BindValuesKeyTest { + + @Test + public void update_with_null() { + + BindValuesKey hash = new BindValuesKey(); + hash.add(1).add(null).add("hello"); + + BindValuesKey hash2 = new BindValuesKey(); + hash2.add(1).add(null).add("hello"); + + assertThat(hash).isEqualTo(hash2); + } + + @Test + public void notEqual() { + + BindValuesKey hash = new BindValuesKey(); + hash.add(1).add(null).add("hello"); + + BindValuesKey hash2 = new BindValuesKey(); + hash2.add(1).add("hello"); + + BindValuesKey hash3 = new BindValuesKey(); + hash2.add(1).add(null); + + assertThat(hash).isNotEqualTo(hash2); + assertThat(hash).isNotEqualTo(hash3); + assertThat(hash2).isNotEqualTo(hash3); + } +} diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java index d9e621221..b2b4902ad 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java @@ -4,6 +4,7 @@ package io.ebeaninternal.server.querydefn; import io.ebean.BaseTestCase; import io.ebean.CacheMode; import io.ebean.Ebean; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.server.core.OrmQueryRequest; import org.junit.Test; @@ -62,7 +63,7 @@ public class DefaultOrmQueryTest extends BaseTestCase { prepare(q1, q2); assertThat(q1.createQueryPlanKey()).isNotEqualTo(q2.createQueryPlanKey()); - assertThat(q1.queryBindHash()).isNotEqualTo(q2.queryBindHash()); + assertThat(bindKey(q1)).isNotEqualTo(bindKey(q2)); } @Test @@ -73,7 +74,7 @@ public class DefaultOrmQueryTest extends BaseTestCase { prepare(q1, q2); assertThat(q1.createQueryPlanKey()).isEqualTo(q2.createQueryPlanKey()); - assertThat(q1.queryBindHash()).isNotEqualTo(q2.queryBindHash()); + assertThat(bindKey(q1)).isNotEqualTo(bindKey(q2)); } @Test @@ -84,7 +85,7 @@ public class DefaultOrmQueryTest extends BaseTestCase { prepare(q1, q2); assertThat(q1.createQueryPlanKey()).isEqualTo(q2.createQueryPlanKey()); - assertThat(q1.queryBindHash()).isEqualTo(q2.queryBindHash()); + assertThat(bindKey(q1)).isEqualTo(bindKey(q2)); } @Test @@ -110,4 +111,10 @@ public class DefaultOrmQueryTest extends BaseTestCase { OrmQueryRequest r2 = createQueryRequest(SpiQuery.Type.LIST, q2, null); q2.prepare(r2); } + + private BindValuesKey bindKey(DefaultOrmQuery query) { + BindValuesKey key = new BindValuesKey(); + query.queryBindKey(key); + return key; + } } diff --git a/ebean-core/src/test/java/org/tests/cache/TestQueryCache.java b/ebean-core/src/test/java/org/tests/cache/TestQueryCache.java index f9e732220..4c4d56109 100644 --- a/ebean-core/src/test/java/org/tests/cache/TestQueryCache.java +++ b/ebean-core/src/test/java/org/tests/cache/TestQueryCache.java @@ -3,7 +3,7 @@ package org.tests.cache; import io.ebean.BaseTestCase; import io.ebean.CacheMode; import io.ebean.DB; -import io.ebean.Ebean; +import io.ebean.ExpressionList; import io.ebean.bean.BeanCollection; import io.ebean.cache.ServerCache; import org.ebeantest.LoggedSqlCollector; @@ -14,6 +14,7 @@ import org.tests.model.basic.ResetBasicData; import org.tests.model.cache.EColAB; import java.util.List; +import java.util.function.Consumer; import static org.assertj.core.api.Assertions.assertThat; @@ -26,8 +27,7 @@ public class TestQueryCache extends BaseTestCase { new EColAB("02", "10").save(); List list1 = - Ebean.getServer(null) - .find(EColAB.class) + DB.find(EColAB.class) .setUseQueryCache(true) .where() .eq("columnA", "01") @@ -35,8 +35,7 @@ public class TestQueryCache extends BaseTestCase { .findList(); List list2 = - Ebean.getServer(null) - .find(EColAB.class) + DB.find(EColAB.class) .setUseQueryCache(true) .where() .eq("columnA", "02") @@ -57,7 +56,7 @@ public class TestQueryCache extends BaseTestCase { new EColAB("03", "SingleAttribute").save(); new EColAB("03", "SingleAttribute").save(); - List colA_first = Ebean.getServer(null) + List colA_first = DB .find(EColAB.class) .setUseQueryCache(true) .setDistinct(true) @@ -66,7 +65,7 @@ public class TestQueryCache extends BaseTestCase { .eq("columnB", "SingleAttribute") .findSingleAttributeList(); - List colA_Second = Ebean.getServer(null) + List colA_Second = DB .find(EColAB.class) .setUseQueryCache(true) .setDistinct(true) @@ -77,7 +76,7 @@ public class TestQueryCache extends BaseTestCase { assertThat(colA_Second).isSameAs(colA_first); - List colA_NotDistinct = Ebean.getServer(null) + List colA_NotDistinct = DB .find(EColAB.class) .setUseQueryCache(true) .select("columnA") @@ -89,7 +88,7 @@ public class TestQueryCache extends BaseTestCase { // ensure that findCount & findSingleAttribute use different // slots in cache. If not a "Cannot cast List to int" should happen. - int count = Ebean.getServer(null) + int count = DB .find(EColAB.class) .setUseQueryCache(true) .select("columnA") @@ -107,13 +106,13 @@ public class TestQueryCache extends BaseTestCase { LoggedSqlCollector.start(); - int count0 = Ebean.find(EColAB.class) + int count0 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.ON) .where() .eq("columnB", "count") .findCount(); - int count1 = Ebean.find(EColAB.class) + int count1 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.ON) .where() .eq("columnB", "count") @@ -126,7 +125,7 @@ public class TestQueryCache extends BaseTestCase { // and now, ensure that we hit the database LoggedSqlCollector.start(); - int count2 = Ebean.find(EColAB.class) + int count2 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.OFF) .where() .eq("columnB", "count") @@ -142,13 +141,13 @@ public class TestQueryCache extends BaseTestCase { LoggedSqlCollector.start(); - int count0 = Ebean.find(EColAB.class) + int count0 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.ON) .where() .eq("columnB", "abc") .findCount(); - int count1 = Ebean.find(EColAB.class) + int count1 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.ON) .where() .eq("columnB", "def") @@ -167,13 +166,13 @@ public class TestQueryCache extends BaseTestCase { LoggedSqlCollector.start(); - int count0 = Ebean.find(EColAB.class) + int count0 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.ON) .where() .eq("columnB", "uvw") .findCount(); - int count1 = Ebean.find(EColAB.class) + int count1 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.PUT) .where() .eq("columnB", "uvw") @@ -193,13 +192,13 @@ public class TestQueryCache extends BaseTestCase { LoggedSqlCollector.start(); - int count0 = Ebean.find(EColAB.class) + int count0 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.PUT) .where() .eq("columnB", "xyz") .findCount(); - int count1 = Ebean.find(EColAB.class) + int count1 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.ON) .where() .eq("columnB", "xyz") @@ -214,26 +213,26 @@ public class TestQueryCache extends BaseTestCase { @Test @SuppressWarnings("unchecked") - public void test() { + public void testReadOnlyFind() { ResetBasicData.reset(); - ServerCache customerCache = Ebean.getServerCacheManager().getQueryCache(Customer.class); + ServerCache customerCache = DB.getServerCacheManager().getQueryCache(Customer.class); customerCache.clear(); - List list = Ebean.find(Customer.class).setUseQueryCache(true).setReadOnly(true).where() + List list = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true).where() .ilike("name", "Rob").findList(); BeanCollection bc = (BeanCollection) list; Assert.assertTrue(bc.isReadOnly()); Assert.assertFalse(bc.isEmpty()); Assert.assertTrue(!list.isEmpty()); - Assert.assertTrue(Ebean.getBeanState(list.get(0)).isReadOnly()); + Assert.assertTrue(DB.getBeanState(list.get(0)).isReadOnly()); - List list2 = Ebean.find(Customer.class).setUseQueryCache(true).setReadOnly(true).where() + List list2 = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true).where() .ilike("name", "Rob").findList(); - List list2B = Ebean.find(Customer.class).setUseQueryCache(true) + List list2B = DB.find(Customer.class).setUseQueryCache(true) // .setReadOnly(true) .where().ilike("name", "Rob").findList(); @@ -245,7 +244,7 @@ public class TestQueryCache extends BaseTestCase { - List list3 = Ebean.find(Customer.class).setUseQueryCache(true).setReadOnly(false).where() + List list3 = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(false).where() .ilike("name", "Rob").findList(); Assert.assertNotSame(list, list3); @@ -269,13 +268,13 @@ public class TestQueryCache extends BaseTestCase { LoggedSqlCollector.start(); - List colA_first = Ebean.find(EColAB.class) + List colA_first = DB.find(EColAB.class) .setUseQueryCache(CacheMode.ON) .where() .eq("columnB", "someId") .findIds(); - List colA_second = Ebean.find(EColAB.class) + List colA_second = DB.find(EColAB.class) .setUseQueryCache(CacheMode.ON) .where() .eq("columnB", "someId") @@ -289,7 +288,7 @@ public class TestQueryCache extends BaseTestCase { // and now, ensure that we hit the database LoggedSqlCollector.start(); - colA_second = Ebean.find(EColAB.class) + colA_second = DB.find(EColAB.class) .setUseQueryCache(CacheMode.PUT) .where() .eq("columnB", "someId") @@ -299,4 +298,35 @@ public class TestQueryCache extends BaseTestCase { assertThat(sql).hasSize(1); } + @Test + public void findCountDifferentQueriesBit() { + DB.getDefault().getPluginApi().getServerCacheManager().clearAll(); + differentFindCount(q->q.bitwiseAny("id",1), q->q.bitwiseAny("id",0)); + differentFindCount(q->q.bitwiseAll("id",1), q->q.bitwiseAll("id",0)); + // differentFindCount(q->q.bitwiseNot("id",1), q->q.bitwiseNot("id",0)); NOT 1 == AND 1 = 0 + differentFindCount(q->q.bitwiseAnd("id",1, 0), q->q.bitwiseAnd("id",1, 1)); + + differentFindCount(q->q.bitwiseAnd("id",2, 0), q->q.bitwiseAnd("id",4, 0)); + differentFindCount(q->q.bitwiseAnd("id",2, 1), q->q.bitwiseAnd("id",4, 1)); + // Will produce hash collision + differentFindCount(q->q.bitwiseAnd("id",10, 0), q->q.bitwiseAnd("id",0, 928210)); + + } + + void differentFindCount(Consumer> q0, Consumer> q1) { + LoggedSqlCollector.start(); + + ExpressionList el0 = DB.find(EColAB.class).setUseQueryCache(CacheMode.ON).where(); + q0.accept(el0); + el0.findCount(); + + ExpressionList el1 = DB.find(EColAB.class).setUseQueryCache(CacheMode.ON).where(); + q1.accept(el1); + el1.findCount(); + + List sql = LoggedSqlCollector.stop(); + + assertThat(sql).hasSize(2); // different queries + } + } diff --git a/ebean-core/src/test/java/org/tests/changelog/TestChangeLog.java b/ebean-core/src/test/java/org/tests/changelog/TestChangeLog.java index 2adbbfc4b..a10a41276 100644 --- a/ebean-core/src/test/java/org/tests/changelog/TestChangeLog.java +++ b/ebean-core/src/test/java/org/tests/changelog/TestChangeLog.java @@ -16,10 +16,13 @@ import io.ebean.event.changelog.ChangeLogRegister; import io.ebean.event.changelog.ChangeSet; import io.ebean.event.changelog.ChangeType; import io.ebean.event.changelog.TxnState; +import io.ebeantest.LoggedSql; + import org.junit.After; import org.junit.Before; import org.junit.Test; import org.tests.model.basic.EBasicChangeLog; +import org.tests.model.json.PlainBean; import java.util.ArrayList; import java.util.List; @@ -130,7 +133,31 @@ public class TestChangeLog extends BaseTestCase { assertThat(change.getEvent()).isEqualTo(ChangeType.DELETE); assertThat(change.getData()).isNull(); } + + @Test + public void testWithJsonMutationDetection() { + EBasicChangeLog bean = new EBasicChangeLog(); + bean.setName(null); + bean.setShortDescription("hello"); + PlainBean jsonBean = new PlainBean(); + bean.setPlainBean(jsonBean); + jsonBean.setName("A"); + server.save(bean); + + BeanChange change = firstChange(); + assertThat(change.getEvent()).isEqualTo(ChangeType.INSERT); + + jsonBean.setName("B"); + LoggedSql.start(); + server.save(bean); + assertThat(LoggedSql.stop()).isNotEmpty(); + + change = firstChange(); + assertThat(change.getEvent()).isEqualTo(ChangeType.UPDATE); + assertThat(change.getData()).contains("\"plainBean\":{\"name\":\"B\""); + assertThat(change.getOldData()).contains("\"plainBean\":{\"name\":\"A\""); + } private Database createServer() { DatabaseConfig config = new DatabaseConfig(); diff --git a/ebean-core/src/test/java/org/tests/json/TestDbJson_Jackson3.java b/ebean-core/src/test/java/org/tests/json/TestDbJson_Jackson3.java index c1cc3c40d..5b58b3108 100644 --- a/ebean-core/src/test/java/org/tests/json/TestDbJson_Jackson3.java +++ b/ebean-core/src/test/java/org/tests/json/TestDbJson_Jackson3.java @@ -10,6 +10,7 @@ import io.ebeantest.LoggedSql; import org.junit.Test; import org.tests.model.json.EBasicJsonJackson3; import org.tests.model.json.EBasicJsonList; +import org.tests.model.json.EBasicJsonMulti; import org.tests.model.json.PlainBean; import org.tests.model.json.PlainBeanDirtyAware; @@ -221,6 +222,21 @@ public class TestDbJson_Jackson3 extends BaseTestCase { LoggedSql.stop(); } + + @Test + public void push_pop_test() { + + EBasicJsonMulti bean = new EBasicJsonMulti(); + bean.setPlainValue2(new PlainBeanDirtyAware("x", 42)); + bean.save(); + + bean = DB.find(EBasicJsonMulti.class, bean.getId()); + bean.setPlainValue1(null); // already null + bean.setPlainValue2(null); + bean.setPlainValue3(null); // already null + BeanState state = DB.getBeanState(bean); + assertThat(state.getDirtyValues()).hasSize(1).containsKey("plainValue2"); + } private void expectedSql(int i, String s) { assertThat(LoggedSql.collect().get(i)).contains(s); diff --git a/ebean-core/src/test/java/org/tests/model/basic/EBasicChangeLog.java b/ebean-core/src/test/java/org/tests/model/basic/EBasicChangeLog.java index 88e3e4f10..0ac02e749 100644 --- a/ebean-core/src/test/java/org/tests/model/basic/EBasicChangeLog.java +++ b/ebean-core/src/test/java/org/tests/model/basic/EBasicChangeLog.java @@ -2,6 +2,7 @@ package org.tests.model.basic; import io.ebean.annotation.Cache; import io.ebean.annotation.ChangeLog; +import io.ebean.annotation.DbJson; import io.ebean.annotation.ReadAudit; import io.ebean.annotation.WhenCreated; import io.ebean.annotation.WhenModified; @@ -12,11 +13,16 @@ import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Version; import javax.validation.constraints.Size; + +import org.tests.model.json.PlainBean; + +import static io.ebean.annotation.MutationDetection.SOURCE; + import java.sql.Timestamp; @Cache(enableQueryCache = true) @ReadAudit -@ChangeLog(updatesThatInclude = {"name", "shortDescription"}) +@ChangeLog(updatesThatInclude = {"name", "shortDescription", "plainBean"}) @Entity public class EBasicChangeLog { @@ -46,6 +52,9 @@ public class EBasicChangeLog { @Version Long version; + + @DbJson(length = 500, mutationDetection = SOURCE) // such that we can rebuild old values + PlainBean plainBean; public Long getId() { return id; @@ -118,4 +127,12 @@ public class EBasicChangeLog { public void setVersion(Long version) { this.version = version; } + + public PlainBean getPlainBean() { + return plainBean; + } + + public void setPlainBean(PlainBean plainBean) { + this.plainBean = plainBean; + } } diff --git a/ebean-core/src/test/java/org/tests/model/json/EBasicJsonMulti.java b/ebean-core/src/test/java/org/tests/model/json/EBasicJsonMulti.java new file mode 100644 index 000000000..23dcd33d9 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/json/EBasicJsonMulti.java @@ -0,0 +1,81 @@ +package org.tests.model.json; + +import io.ebean.Model; +import io.ebean.annotation.DbJson; +import io.ebean.annotation.MutationDetection; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Version; + +import static io.ebean.annotation.MutationDetection.NONE; +import static io.ebean.annotation.MutationDetection.SOURCE; + +@Entity +public class EBasicJsonMulti extends Model { + + @Id + Long id; + + String name; + + @DbJson(length = 500, mutationDetection = SOURCE) + PlainBeanDirtyAware plainValue1; + + @DbJson(length = 500, mutationDetection = SOURCE) + PlainBeanDirtyAware plainValue2; + + @DbJson(length = 500, mutationDetection = SOURCE) + PlainBeanDirtyAware plainValue3; + + @Version + long version; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public PlainBeanDirtyAware getPlainValue1() { + return plainValue1; + } + + public void setPlainValue1(PlainBeanDirtyAware plainValue1) { + this.plainValue1 = plainValue1; + } + + public PlainBeanDirtyAware getPlainValue2() { + return plainValue2; + } + + public void setPlainValue2(PlainBeanDirtyAware plainValue2) { + this.plainValue2 = plainValue2; + } + + public PlainBeanDirtyAware getPlainValue3() { + return plainValue3; + } + + public void setPlainValue3(PlainBeanDirtyAware plainValue3) { + this.plainValue3 = plainValue3; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } +} diff --git a/ebean-core/src/test/java/org/tests/query/TestQueryAlias.java b/ebean-core/src/test/java/org/tests/query/TestQueryAlias.java index de378586c..4e77d49d6 100644 --- a/ebean-core/src/test/java/org/tests/query/TestQueryAlias.java +++ b/ebean-core/src/test/java/org/tests/query/TestQueryAlias.java @@ -1,7 +1,7 @@ package org.tests.query; import io.ebean.BaseTestCase; -import io.ebean.Ebean; +import io.ebean.DB; import io.ebean.Query; import org.junit.Test; import org.tests.model.basic.CKeyParent; @@ -16,11 +16,11 @@ public class TestQueryAlias extends BaseTestCase { ResetBasicData.reset(); - Query sq = Ebean.createQuery(CKeyParent.class) + Query sq = DB.createQuery(CKeyParent.class) .select("id.oneKey").alias("st0") .setAutoTune(false).where().query(); - Query pq = Ebean.find(CKeyParent.class).alias("myt0").where().in("id.oneKey", sq).query(); + Query pq = DB.find(CKeyParent.class).alias("myt0").where().in("id.oneKey", sq).query(); pq.findList(); @@ -36,17 +36,36 @@ public class TestQueryAlias extends BaseTestCase { assertThat(sql).contains("ckey_parent myt0"); assertThat(sql).contains("(myt0.one_key) in (select st0.one_key from ckey_parent st0)"); } + + @Test + public void testExistsWithConcat() { + + ResetBasicData.reset(); + + Query sq = DB.createQuery(CKeyParent.class) + .select("concat(id.oneKey,id.twoKey)").alias("st0") + .setAutoTune(false).where().query(); + + Query pq = DB.find(CKeyParent.class).alias("myt0").where().in("concat(id.oneKey,id.twoKey)", sq).query(); + + pq.findList(); + + String sql = pq.getGeneratedSql(); + + assertThat(sql).contains("ckey_parent myt0"); + assertThat(sql).contains("(concat(myt0.one_key,myt0.two_key)) in (select concat(st0.one_key,st0.two_key) from ckey_parent st0)"); + } @Test public void testNotExists() { ResetBasicData.reset(); - Query sq = Ebean.createQuery(CKeyParent.class) + Query sq = DB.createQuery(CKeyParent.class) .select("id.oneKey").alias("st0") .setAutoTune(false).where().query(); - Query pq = Ebean.find(CKeyParent.class).alias("myt0").where().notIn("id.oneKey", sq).query(); + Query pq = DB.find(CKeyParent.class).alias("myt0").where().notIn("id.oneKey", sq).query(); pq.findList(); diff --git a/ebean-core/src/test/java/org/tests/query/TestQueryExists.java b/ebean-core/src/test/java/org/tests/query/TestQueryExists.java index 93155bf9a..f885852b7 100644 --- a/ebean-core/src/test/java/org/tests/query/TestQueryExists.java +++ b/ebean-core/src/test/java/org/tests/query/TestQueryExists.java @@ -1,7 +1,7 @@ package org.tests.query; import io.ebean.BaseTestCase; -import io.ebean.Ebean; +import io.ebean.DB; import io.ebean.Query; import io.ebeantest.LoggedSql; @@ -21,7 +21,7 @@ public class TestQueryExists extends BaseTestCase { ResetBasicData.reset(); - Query query = Ebean.find(Order.class) + Query query = DB.find(Order.class) .where().gt("id", 1) .query(); @@ -34,8 +34,8 @@ public class TestQueryExists extends BaseTestCase { assertThat(sql).contains("select t0.id from o_order t0 where t0.id > ? limit 1"); } - assertThat(Ebean.find(Order.class).where().gt("id", 1).exists()).isTrue(); - assertThat(Ebean.find(Order.class).where().or().gt("id", 1).isNull("shipDate").exists()).isTrue(); + assertThat(DB.find(Order.class).where().gt("id", 1).exists()).isTrue(); + assertThat(DB.find(Order.class).where().or().gt("id", 1).isNull("shipDate").exists()).isTrue(); } @Test @@ -43,13 +43,13 @@ public class TestQueryExists extends BaseTestCase { ResetBasicData.reset(); - Query query = Ebean.find(Order.class) + Query query = DB.find(Order.class) .where().raw("exists (select 1 from o_order_detail where order_id = t0.id)") .query(); List ordersThatHave = query.findList(); - Query query2 = Ebean.find(Order.class) + Query query2 = DB.find(Order.class) .where().raw("not exists (select 1 from o_order_detail where order_id = t0.id)") .query(); @@ -67,13 +67,13 @@ public class TestQueryExists extends BaseTestCase { ResetBasicData.reset(); - Query query = Ebean.find(Customer.class) + Query query = DB.find(Customer.class) .where().raw("exists (select 1 from contact where customer_id = t0.id)") .query(); List customersWithContacts = query.findList(); - Query query2 = Ebean.find(Customer.class) + Query query2 = DB.find(Customer.class) .where().raw("not exists (select 1 FROM contact where customer_id = t0.id)") .query(); @@ -89,26 +89,26 @@ public class TestQueryExists extends BaseTestCase { public void testExists() { ResetBasicData.reset(); - Query subQuery = Ebean.find(Order.class).alias("sq").select("id").where().raw("sq.kcustomer_id = qt.id").query(); + Query subQuery = DB.find(Order.class).alias("sq").select("id").where().raw("sq.kcustomer_id = qt.id").query(); - Query query = Ebean.find(Customer.class).alias("qt").where().exists(subQuery).query(); + Query query = DB.find(Customer.class).alias("qt").where().exists(subQuery).query(); query.findList(); String sql = query.getGeneratedSql(); - assertThat(sql).contains("exists ("); + assertThat(sql).contains("exists (select 1 from"); } @Test public void testNotExists() { ResetBasicData.reset(); - Query subQuery = Ebean.find(Order.class).alias("sq").select("id").where().raw("sq.kcustomer_id = qt.id").query(); - Query query = Ebean.find(Customer.class).alias("qt").where().notExists(subQuery).query(); + Query subQuery = DB.find(Order.class).alias("sq").where().raw("sq.kcustomer_id = qt.id").query(); + Query query = DB.find(Customer.class).alias("qt").where().notExists(subQuery).query(); query.findList(); String sql = query.getGeneratedSql(); - assertThat(sql).contains("not exists ("); + assertThat(sql).contains("not exists (select 1 from"); } } diff --git a/ebean-core/src/test/java/org/tests/query/TestSubQuery.java b/ebean-core/src/test/java/org/tests/query/TestSubQuery.java index db9eb7cbd..61ffacf56 100644 --- a/ebean-core/src/test/java/org/tests/query/TestSubQuery.java +++ b/ebean-core/src/test/java/org/tests/query/TestSubQuery.java @@ -6,6 +6,8 @@ import io.ebean.Query; import org.junit.Test; import org.tests.model.basic.CKeyParent; import org.tests.model.basic.Order; +import org.tests.model.basic.OrderDetail; +import org.tests.model.basic.OrderShipment; import org.tests.model.basic.ResetBasicData; import org.tests.model.basic.Vehicle; import org.tests.model.basic.VehicleDriver; @@ -47,6 +49,75 @@ public class TestSubQuery extends BaseTestCase { DB.find(Order.class).where().isIn("id", sq).findList(); } + /** + * Testcase, that discovered, that DefaultOrmQuery.setDefaultSelectClause is set on subQueries with fetch path. + * Also checks, that SqlTreeBuilder does not read id on Many2One props. + */ + @Test + public void test_IsInWithFetchSubQuery1() { + + List productIds = new ArrayList<>(); + productIds.add(3); + + Query sq = DB.createQuery(OrderDetail.class).fetch("order", "id").where() + .isIn("product.id", productIds).query(); + + // execute the subQuery as copy (generatedSQL must be part of original query) + Query debugSq = sq.copy(); + debugSq.findSingleAttribute(); + assertThat(debugSq.getGeneratedSql()).isEqualTo( + "select t1.id from o_order_detail t0 join o_order t1 on t1.id = t0.order_id where t0.product_id in (?)"); + + Query query = DB.find(Order.class).select("shipDate").where().isIn("id", sq).query(); + query.findSingleAttribute(); + + assertThat(query.getGeneratedSql()) + .isEqualTo("select t0.ship_date from o_order t0 where (t0.id) in (" + debugSq.getGeneratedSql() + ")"); + } + + /** + * Test checks, that DefaultOrmQuery.markQueryJoins handles subQuery correct. + */ + @Test + public void test_IsInWithFetchSubQuery2() { + + Query sq = DB.createQuery(OrderDetail.class).fetch("order.customer", "anniversary").where() + .eq("order.customer.name", "Roland") + .query().setDistinct(true); + + // execute the subQuery as copy (generatedSQL must be part of original query) + Query debugSq = sq.copy(); + debugSq.findSingleAttribute(); + + Query query = DB.find(Order.class).select("status").where().isIn("shipDate", sq).query(); + query.findSingleAttribute(); + assertThat(query.getGeneratedSql()) + .isEqualTo("select t0.status from o_order t0 where (t0.ship_date) in (" + debugSq.getGeneratedSql() + ")"); + } + + /** + * Checks, that SqlTreeBuilder does not read id on One2Many props. + */ + @Test + public void test_IsInWithFetchSubQuery3() { + + List productIds = new ArrayList<>(); + productIds.add(3); + + Query sq = DB.createQuery(OrderDetail.class).fetch("order.shipments", "id").where() + .isIn("product.id", productIds).query(); + + // execute the subQuery as copy (generatedSQL must be part of original query) + Query debugSq = sq.copy(); + debugSq.findSingleAttribute(); + + Query query = DB.find(OrderShipment.class).select("shipTime").where().isIn("id", sq).query(); + query.findSingleAttribute(); + + assertThat(query.getGeneratedSql()) + .isEqualTo("select t0.ship_time from or_order_ship t0 where (t0.id) in (" + debugSq.getGeneratedSql() + ")"); + } + public void testCompositeKey() { ResetBasicData.reset(); diff --git a/ebean-core/src/test/java/org/tests/query/cancel/SqlQueryCancelTest.java b/ebean-core/src/test/java/org/tests/query/cancel/SqlQueryCancelTest.java index 9fc3d6b57..5fedc53e0 100644 --- a/ebean-core/src/test/java/org/tests/query/cancel/SqlQueryCancelTest.java +++ b/ebean-core/src/test/java/org/tests/query/cancel/SqlQueryCancelTest.java @@ -113,8 +113,8 @@ public class SqlQueryCancelTest extends BaseTestCase { doCancelOrmAtBegin(Query::findOne); doCancelOrmAtBegin(q -> q.setMaxRows(1000).findPagedList().getList()); // untested doCancelOrmAtBegin(Query::findSet); - doCancelOrmAtBegin(Query::findSingleAttribute); - doCancelOrmAtBegin(Query::findSingleAttributeList); + doCancelOrmAtBegin(q -> q.select("name").findSingleAttribute()); + doCancelOrmAtBegin(q -> q.select("name").findSingleAttributeList()); doCancelOrmAtBegin(Query::findStream); // testDuringRun(Query::findVersions); // EBasic has no history support, but it should work if @History is added @@ -138,8 +138,8 @@ public class SqlQueryCancelTest extends BaseTestCase { // findOne cannot be tested, as H2 does the cancel check every 128 rows only doCancelOrmDuringRun(q -> q.setMaxRows(1000).findPagedList().getList()); // untested doCancelOrmDuringRun(Query::findSet); - doCancelOrmDuringRun(Query::findSingleAttribute); - doCancelOrmDuringRun(Query::findSingleAttributeList); + doCancelOrmDuringRun(q -> q.select("name").findSingleAttribute()); + doCancelOrmDuringRun(q -> q.select("name").findSingleAttributeList()); doCancelOrmDuringRun(Query::findStream); // testDuringRun(Query::findVersions); // EBasic has no history support, but it should work if @History is added