Merge remote-tracking branch 'ebean/master' into pr/enh/softreference_in_cache

This commit is contained in:
Jonas Pöhler
2021-08-11 15:26:42 +02:00
63 changed files with 658 additions and 510 deletions
@@ -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();
}
}
@@ -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
+5 -14
View File
@@ -20,22 +20,13 @@
<profiles>
<profile>
<!-- Note: to use this profile, you need to download manually the db2jcc4 driver.
After that, install it into your local maven repository:
mvn install:install-file \
-Dfile=db2jcc4.jar \
-DgroupId=com.ibm.jdbc \
-DartifactId=db2jcc4 \
-Dversion=4.23.42 \
-Dpackaging=jar
-->
<id>db2</id>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.ibm.db2/jcc -->
<dependency>
<groupId>com.ibm.jdbc</groupId>
<artifactId>db2jcc4</artifactId>
<version>4.23.42</version>
<groupId>com.ibm.db2</groupId>
<artifactId>jcc</artifactId>
<version>11.5.5.0</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -72,7 +63,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.9.4-RC1</version>
<version>12.11.0</version>
<scope>test</scope>
</dependency>
@@ -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);
}
/**
@@ -0,0 +1,35 @@
package io.ebeaninternal.api;
import java.util.ArrayList;
import java.util.List;
/**
* BindValues used for L2 query cache key matching.
* <p>
* 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<Object> 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();
}
}
@@ -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);
}
}
@@ -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<Object> getIdList() {
List<Object> 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<Object> 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<Object> 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
@@ -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.
*/
<T> CQuery<T> compileQuery(Query<T> query, Transaction t);
<T> CQuery<T> compileQuery(Type type, Query<T> query, Transaction t);
/**
* Execute the findId's query but without copying the query.
@@ -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.
@@ -84,7 +84,7 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
/**
* Find single attribute.
*/
ATTRIBUTE(FIND_ATTRIBUTE, "findAttribute"),
ATTRIBUTE(FIND_ATTRIBUTE, "findAttribute", false, false),
/**
* Find rowCount.
@@ -92,9 +92,14 @@ public interface SpiQuery<T> extends Query<T>, 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<T> extends Query<T>, 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<T> extends Query<T>, 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<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
CQueryPlanKey prepare(SpiOrmQueryRequest<T> 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).
* <p>
* Combined with queryPlanHash() to return getQueryHash (a unique hash for a
* query).
* </p>
* 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.
@@ -526,8 +526,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
* Compile a query. Only valid for ORM queries.
*/
@Override
public <T> CQuery<T> compileQuery(Query<T> query, Transaction t) {
SpiOrmQueryRequest<T> qr = createQueryRequest(Type.SUBQUERY, query, t);
public <T> CQuery<T> compileQuery(Type type, Query<T> query, Transaction t) {
SpiOrmQueryRequest<T> qr = createQueryRequest(type, query, t);
OrmQueryRequest<T> orm = (OrmQueryRequest<T>) qr;
return cqueryEngine.buildQuery(orm);
}
@@ -2055,17 +2055,15 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return transactionManager;
}
public void register(BeanPersistController c) {
List<BeanDescriptor<?>> 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<BeanDescriptor<?>> list = beanDescriptorManager.getBeanDescriptorList();
for (BeanDescriptor<?> aList : list) {
aList.deregister(c);
for (BeanDescriptor<?> desc : beanDescriptorManager.getBeanDescriptorList()) {
desc.deregister(c);
}
}
@@ -2519,7 +2519,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
if (propName.indexOf('(') > -1) {
return findSqlTreeFormula(propName, path);
}
return _findBeanProperty(propName);
return findProperty(propName);
}
/**
@@ -702,13 +702,11 @@ public class DeployBeanDescriptor<T> {
}
public void sortProperties() {
ArrayList<DeployBeanProperty> 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);
}
}
@@ -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) {
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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<SpiExpression> buildExpressions(BeanDescriptor<?> beanDescriptor) {
ArrayList<SpiExpression> list = new ArrayList<>();
addExpressions(list, beanDescriptor, entity, null);
return list;
@@ -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<T> implements SpiExpressionList<T> {
* @return A single SpiExpression that has the nestedPath set
*/
SpiExpression wrap(List<SpiExpression> list, String nestedPath, Junction.Type type) {
DefaultExpressionList<T> 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<T> implements SpiExpressionList<T> {
}
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<T> implements SpiExpressionList<T> {
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<T> implements SpiExpressionList<T> {
@Override
public void writeDocQuery(DocQueryContext context, SpiExpression idEquals) throws IOException {
if (allDocNestedPath != null) {
context.startNested(allDocNestedPath);
}
@@ -227,8 +224,8 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
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<T> implements SpiExpressionList<T> {
*/
@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<T> implements SpiExpressionList<T> {
@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<T> implements SpiExpressionList<T> {
@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<T> implements SpiExpressionList<T> {
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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<T> implements SpiJunction<T>, SpiExpression, Expression
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.startBool(type);
List<SpiExpression> 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<T> implements SpiJunction<T>, SpiExpression, Expression
@Override
public void writeDocQueryJunction(DocQueryContext context) throws IOException {
context.startBoolGroupList(type);
List<SpiExpression> 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<T> implements SpiJunction<T>, SpiExpression, Expression
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
List<SpiExpression> 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<T> implements SpiJunction<T>, SpiExpression, Expression
@Override
public void addBindValues(SpiExpressionRequest request) {
List<SpiExpression> 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<SpiExpression> list = exprList.internalList();
if (!list.isEmpty()) {
request.append(type.prefix());
request.append("(");
@@ -204,9 +196,8 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
List<SpiExpression> list = exprList.internalList();
for (SpiExpression aList : list) {
aList.prepareExpression(request);
for (SpiExpression expr : exprList.internalList()) {
expr.prepareExpression(request);
}
}
@@ -216,22 +207,18 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
@Override
public void queryPlanHash(StringBuilder builder) {
builder.append(type).append("[");
List<SpiExpression> 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<SpiExpression> 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<T> implements SpiJunction<T>, SpiExpression, Expression
return exprList.textCommonTerms(search, options);
}
@Override
public ExpressionList<T> allEq(Map<String, Object> propertyMap) {
return exprList.allEq(propertyMap);
@@ -1025,7 +1011,6 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
@Override
public String nestedPath(BeanDescriptor<?> desc) {
PrepareDocNested.prepare(exprList, desc, type);
String nestedPath = exprList.allDocNestedPath;
if (nestedPath != null) {
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
}
}
@@ -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
@@ -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
@@ -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<Value> 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<Value> 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);
}
}
}
@@ -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<BindValues.Value> 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.
*/
@@ -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.
*/
<T> CQueryUpdate buildUpdateQuery(boolean deleteRequest, OrmQueryRequest<T> request) {
SpiQuery<T> 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 <T> String buildDeleteSql(OrmQueryRequest<T> 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 <T> String buildUpdateSql(OrmQueryRequest<T> 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.
*/
<T> CQueryFetchSingleAttribute buildFetchIdsQuery(OrmQueryRequest<T> request) {
SpiQuery<T> query = request.getQuery();
query.setSelectId();
BeanDescriptor<T> desc = request.getBeanDescriptor();
@@ -246,9 +235,7 @@ class CQueryBuilder {
* Build the row count query.
*/
<T> CQueryRowCount buildRowCountQuery(OrmQueryRequest<T> request) {
SpiQuery<T> 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.
*/
<T> CQuery<T> buildQuery(OrmQueryRequest<T> 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 {
* <p>
* The SqlTree is immutable after construction and so is safe to use by
* concurrent threads.
* </p>
* <p>
* 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.
* </p>
*/
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) {
@@ -101,6 +101,7 @@ class SqlTree {
* Return the String for the actual SQL.
*/
String getSelectSql() {
assert selectSql != null : "selectSql was null";
return selectSql;
}
@@ -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.
* </p>
*/
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;
}
@@ -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<T> extends AbstractQuery implements SpiQuery<T> {
private static final FetchConfig FETCH_LAZY = FetchConfig.ofLazy();
private final ReentrantLock lock = new ReentrantLock();
private final Class<T> beanType;
private final ExpressionFactory expressionFactory;
@@ -616,7 +565,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
* 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<T> extends AbstractQuery implements SpiQuery<T> {
@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<T> extends AbstractQuery implements SpiQuery<T> {
}
}
/**
* Calculate a hash based on the bind values used in the query.
* <p>
* Used with queryPlanHash() to get a unique hash for a query.
* </p>
*/
@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<T> extends AbstractQuery implements SpiQuery<T> {
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
@@ -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) {
@@ -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 <T> CQuery<T> compileQuery(Query<T> query, Transaction t) {
public <T> CQuery<T> compileQuery(Type type, Query<T> 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;
}
@@ -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;
}
}
@@ -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");
}
@@ -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);
}
}
@@ -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<T> r2 = createQueryRequest(SpiQuery.Type.LIST, q2, null);
q2.prepare(r2);
}
private BindValuesKey bindKey(DefaultOrmQuery<Order> query) {
BindValuesKey key = new BindValuesKey();
query.queryBindKey(key);
return key;
}
}
+58 -28
View File
@@ -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<EColAB> 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<EColAB> 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<String> colA_first = Ebean.getServer(null)
List<String> colA_first = DB
.find(EColAB.class)
.setUseQueryCache(true)
.setDistinct(true)
@@ -66,7 +65,7 @@ public class TestQueryCache extends BaseTestCase {
.eq("columnB", "SingleAttribute")
.findSingleAttributeList();
List<String> colA_Second = Ebean.getServer(null)
List<String> 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<String> colA_NotDistinct = Ebean.getServer(null)
List<String> 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<Customer> list = Ebean.find(Customer.class).setUseQueryCache(true).setReadOnly(true).where()
List<Customer> list = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true).where()
.ilike("name", "Rob").findList();
BeanCollection<Customer> bc = (BeanCollection<Customer>) 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<Customer> list2 = Ebean.find(Customer.class).setUseQueryCache(true).setReadOnly(true).where()
List<Customer> list2 = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true).where()
.ilike("name", "Rob").findList();
List<Customer> list2B = Ebean.find(Customer.class).setUseQueryCache(true)
List<Customer> list2B = DB.find(Customer.class).setUseQueryCache(true)
// .setReadOnly(true)
.where().ilike("name", "Rob").findList();
@@ -245,7 +244,7 @@ public class TestQueryCache extends BaseTestCase {
List<Customer> list3 = Ebean.find(Customer.class).setUseQueryCache(true).setReadOnly(false).where()
List<Customer> 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<Integer> colA_first = Ebean.find(EColAB.class)
List<Integer> colA_first = DB.find(EColAB.class)
.setUseQueryCache(CacheMode.ON)
.where()
.eq("columnB", "someId")
.findIds();
List<Integer> colA_second = Ebean.find(EColAB.class)
List<Integer> 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<ExpressionList<EColAB>> q0, Consumer<ExpressionList<EColAB>> q1) {
LoggedSqlCollector.start();
ExpressionList<EColAB> el0 = DB.find(EColAB.class).setUseQueryCache(CacheMode.ON).where();
q0.accept(el0);
el0.findCount();
ExpressionList<EColAB> el1 = DB.find(EColAB.class).setUseQueryCache(CacheMode.ON).where();
q1.accept(el1);
el1.findCount();
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(2); // different queries
}
}
@@ -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();
@@ -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);
@@ -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;
}
}
@@ -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;
}
}
@@ -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<CKeyParent> sq = Ebean.createQuery(CKeyParent.class)
Query<CKeyParent> sq = DB.createQuery(CKeyParent.class)
.select("id.oneKey").alias("st0")
.setAutoTune(false).where().query();
Query<CKeyParent> pq = Ebean.find(CKeyParent.class).alias("myt0").where().in("id.oneKey", sq).query();
Query<CKeyParent> 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<CKeyParent> sq = DB.createQuery(CKeyParent.class)
.select("concat(id.oneKey,id.twoKey)").alias("st0")
.setAutoTune(false).where().query();
Query<CKeyParent> 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<CKeyParent> sq = Ebean.createQuery(CKeyParent.class)
Query<CKeyParent> sq = DB.createQuery(CKeyParent.class)
.select("id.oneKey").alias("st0")
.setAutoTune(false).where().query();
Query<CKeyParent> pq = Ebean.find(CKeyParent.class).alias("myt0").where().notIn("id.oneKey", sq).query();
Query<CKeyParent> pq = DB.find(CKeyParent.class).alias("myt0").where().notIn("id.oneKey", sq).query();
pq.findList();
@@ -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<Order> query = Ebean.find(Order.class)
Query<Order> 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<Order> query = Ebean.find(Order.class)
Query<Order> query = DB.find(Order.class)
.where().raw("exists (select 1 from o_order_detail where order_id = t0.id)")
.query();
List<Order> ordersThatHave = query.findList();
Query<Order> query2 = Ebean.find(Order.class)
Query<Order> 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<Customer> query = Ebean.find(Customer.class)
Query<Customer> query = DB.find(Customer.class)
.where().raw("exists (select 1 from contact where customer_id = t0.id)")
.query();
List<Customer> customersWithContacts = query.findList();
Query<Customer> query2 = Ebean.find(Customer.class)
Query<Customer> 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<Order> subQuery = Ebean.find(Order.class).alias("sq").select("id").where().raw("sq.kcustomer_id = qt.id").query();
Query<Order> subQuery = DB.find(Order.class).alias("sq").select("id").where().raw("sq.kcustomer_id = qt.id").query();
Query<Customer> query = Ebean.find(Customer.class).alias("qt").where().exists(subQuery).query();
Query<Customer> 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<Order> subQuery = Ebean.find(Order.class).alias("sq").select("id").where().raw("sq.kcustomer_id = qt.id").query();
Query<Customer> query = Ebean.find(Customer.class).alias("qt").where().notExists(subQuery).query();
Query<Order> subQuery = DB.find(Order.class).alias("sq").where().raw("sq.kcustomer_id = qt.id").query();
Query<Customer> 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");
}
}
@@ -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<Integer> productIds = new ArrayList<>();
productIds.add(3);
Query<OrderDetail> 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<OrderDetail> 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<Order> 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<OrderDetail> 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<OrderDetail> debugSq = sq.copy();
debugSq.findSingleAttribute();
Query<Order> 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<Integer> productIds = new ArrayList<>();
productIds.add(3);
Query<OrderDetail> 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<OrderDetail> debugSq = sq.copy();
debugSq.findSingleAttribute();
Query<OrderShipment> 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();
@@ -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