From 7e265f7bde97120cf66e59e43abf044e0b047764 Mon Sep 17 00:00:00 2001
From: Roland Praml
Date: Tue, 13 Jul 2021 09:09:30 +0200
Subject: [PATCH 01/22] FIX: SubQuery with fetch-path does not work correctly
---
.../server/query/SqlTreeBuilder.java | 2 +-
.../server/querydefn/DefaultOrmQuery.java | 4 +-
.../java/org/tests/query/TestSubQuery.java | 71 +++++++++++++++++++
3 files changed, 74 insertions(+), 3 deletions(-)
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..e4c936e45 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
@@ -311,7 +311,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);
}
}
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..3a81183e9 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
@@ -616,7 +616,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 != Type.ATTRIBUTE && type != Type.SUBQUERY);
}
private boolean isAllowOneManyFetch() {
@@ -629,7 +629,7 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery {
@Override
public void setDefaultSelectClause() {
- if (type != Type.ATTRIBUTE) {
+ if (type != Type.ATTRIBUTE && type != Type.SUBQUERY) {
detail.setDefaultSelectClause(beanDescriptor);
} else if (!detail.hasSelectClause()) {
// explicit empty select when single attribute query on non-root fetch path
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();
From 181dc73d39f5285fb7d02ffbc923d01ff794c4d9 Mon Sep 17 00:00:00 2001
From: Roland Praml
Date: Mon, 9 Aug 2021 09:19:53 +0200
Subject: [PATCH 02/22] Use MD5 for QueryBindHash
---
.../java/io/ebeaninternal/api/BindHash.java | 37 +++++
.../java/io/ebeaninternal/api/BindParams.java | 11 +-
.../java/io/ebeaninternal/api/HashQuery.java | 8 +-
.../io/ebeaninternal/api/SpiExpression.java | 2 +-
.../java/io/ebeaninternal/api/SpiQuery.java | 2 +-
.../expression/AbstractTextExpression.java | 7 +-
.../expression/AllEqualsExpression.java | 8 +-
.../expression/ArrayContainsExpression.java | 10 +-
.../expression/ArrayIsEmptyExpression.java | 5 +-
.../server/expression/BetweenExpression.java | 7 +-
.../expression/BetweenPropertyExpression.java | 5 +-
.../server/expression/BitwiseExpression.java | 5 +-
.../CaseInsensitiveEqualExpression.java | 5 +-
.../expression/DefaultExampleExpression.java | 8 +-
.../expression/DefaultExpressionList.java | 8 +-
.../expression/ExistsQueryExpression.java | 5 +-
.../server/expression/IdExpression.java | 5 +-
.../server/expression/IdInExpression.java | 8 +-
.../server/expression/InExpression.java | 8 +-
.../server/expression/InPairsExpression.java | 8 +-
.../server/expression/InQueryExpression.java | 5 +-
.../server/expression/InRangeExpression.java | 7 +-
.../server/expression/IsEmptyExpression.java | 5 +-
.../server/expression/JsonPathExpression.java | 7 +-
.../server/expression/JunctionExpression.java | 6 +-
.../server/expression/LikeExpression.java | 5 +-
.../server/expression/LogicExpression.java | 7 +-
.../expression/NativeILikeExpression.java | 5 +-
.../NestedPathWrapperExpression.java | 5 +-
.../server/expression/NoopExpression.java | 4 +-
.../server/expression/NotExpression.java | 5 +-
.../server/expression/NullExpression.java | 5 +-
.../server/expression/RawExpression.java | 8 +-
.../server/expression/SimpleExpression.java | 5 +-
.../server/querydefn/DefaultOrmQuery.java | 26 ++--
.../server/querydefn/HashCodeBindHash.java | 56 +++++++
.../server/querydefn/MdBindHash.java | 138 ++++++++++++++++++
.../server/expression/RawExpressionTest.java | 12 +-
.../server/querydefn/DefaultOrmQueryTest.java | 14 +-
.../java/org/tests/cache/TestQueryCache.java | 86 +++++++----
40 files changed, 436 insertions(+), 137 deletions(-)
create mode 100644 ebean-core/src/main/java/io/ebeaninternal/api/BindHash.java
create mode 100644 ebean-core/src/main/java/io/ebeaninternal/server/querydefn/HashCodeBindHash.java
create mode 100644 ebean-core/src/main/java/io/ebeaninternal/server/querydefn/MdBindHash.java
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/BindHash.java b/ebean-core/src/main/java/io/ebeaninternal/api/BindHash.java
new file mode 100644
index 000000000..d70c5f09d
--- /dev/null
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/BindHash.java
@@ -0,0 +1,37 @@
+package io.ebeaninternal.api;
+
+/**
+ * BindHash implementation.
+ *
+ * @author Roland Praml, FOCONIS AG
+ *
+ */
+public interface BindHash {
+
+ /**
+ * Update with boolean value.
+ */
+ BindHash update(boolean boolValue);
+
+ /**
+ * Update with int value.
+ */
+ BindHash update(int intValue);
+
+ /**
+ * Update with long value.
+ */
+ BindHash update(long longValue);
+
+ /**
+ * Update with object value.
+ */
+ BindHash update(Object value);
+
+ /**
+ * finishes the hash. May be used to compute internal state. After finish, no
+ * update method must be called
+ */
+ void finish();
+
+}
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..2817c4ba1 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/BindParams.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/BindParams.java
@@ -54,12 +54,11 @@ public class BindParams implements Serializable {
positionedParameters.clear();
}
- public int queryBindHash() {
- int hc = namedParameters.hashCode();
+ public void queryBindHash(BindHash hash) {
+ hash.update(positionedParameters.size());
for (Param positionedParameter : positionedParameters) {
- hc = hc * 92821 + positionedParameter.hashCode();
+ positionedParameter.queryBindHash(hash);
}
- return hc;
}
/**
@@ -422,6 +421,10 @@ public class BindParams implements Serializable {
return hc;
}
+ void queryBindHash(BindHash hash) {
+ hash.update(isInParam).update(isOutParam).update(type).update(inValue);
+ }
+
@Override
public boolean equals(Object o) {
return o != null && (o == this || (o instanceof Param) && hashCode() == o.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..87241aedc 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/HashQuery.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/HashQuery.java
@@ -7,12 +7,12 @@ public class HashQuery {
private final CQueryPlanKey planHash;
- private final int bindHash;
+ private final BindHash bindHash;
/**
* Create the HashQuery.
*/
- public HashQuery(CQueryPlanKey planHash, int bindHash) {
+ public HashQuery(CQueryPlanKey planHash, BindHash bindHash) {
this.planHash = planHash;
this.bindHash = bindHash;
}
@@ -25,7 +25,7 @@ public class HashQuery {
@Override
public int hashCode() {
int hc = 92821 * planHash.hashCode();
- hc = 92821 * hc + bindHash;
+ hc = 92821 * hc + bindHash.hashCode();
return hc;
}
@@ -39,6 +39,6 @@ public class HashQuery {
}
HashQuery e = (HashQuery) obj;
- return e.bindHash == bindHash && e.planHash.equals(planHash);
+ return e.bindHash.equals(bindHash) && e.planHash.equals(planHash);
}
}
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..32c3a3518 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiExpression.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiExpression.java
@@ -56,7 +56,7 @@ public interface SpiExpression extends Expression {
/**
* Return the hash value for the values that will be bound.
*/
- int queryBindHash();
+ void queryBindHash(BindHash hash);
/**
* 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..d8bae7503 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java
@@ -635,7 +635,7 @@ public interface SpiQuery extends Query, SpiQueryFetch, TxnProfileEventCod
* query).
*
*/
- int queryBindHash();
+ void queryBindHash(BindHash hash);
/**
* Identifies queries that are exactly the same including bind variables.
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..d377e48dd 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.BindHash;
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 queryBindHash(BindHash hash) {
+ // 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..df9e6095b 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.BindHash;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
@@ -122,14 +123,13 @@ class AllEqualsExpression extends NonPrepareExpression {
}
@Override
- public int queryBindHash() {
+ public void queryBindHash(BindHash hash) {
- int hc = 92821;
+ hash.update(propMap.size());
for (Object value : propMap.values()) {
- hc = hc * 92821 + (value == null ? 0 : value.hashCode());
+ hash.update(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..ac6d27d0e 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(values.length);
+ for (int i = 0; i < values.length; i++) {
+ hash.update(values[i]);
}
- 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..0249acabb 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(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..37a3aab8f 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(low()).update(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..440dd5f1f 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(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..db1fa686d 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(flags).update(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..485d1b04f 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(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..9149923e0 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.BindHash;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.NaturalKeyQueryData;
import io.ebeaninternal.api.SpiExpression;
@@ -241,12 +242,11 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio
* Return a hash for the actual bind values used.
*/
@Override
- public int queryBindHash() {
- int hc = DefaultExampleExpression.class.getName().hashCode();
+ public void queryBindHash(BindHash hash) {
+ hash.update(list.size());
for (SpiExpression aList : list) {
- hc = hc * 92821 + aList.queryBindHash();
+ aList.queryBindHash(hash);
}
- return hc;
}
@Override
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..b5f9c6ec1 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.BindHash;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.NaturalKeyQueryData;
import io.ebeaninternal.api.SpiExpression;
@@ -678,12 +679,11 @@ public class DefaultExpressionList implements SpiExpressionList {
* Calculate a hash based on the expressions.
*/
@Override
- public int queryBindHash() {
- int hash = DefaultExpressionList.class.getName().hashCode();
+ public void queryBindHash(BindHash hash) {
+ hash.update(list.size());
for (SpiExpression aList : list) {
- hash = hash * 92821 + aList.queryBindHash();
+ aList.queryBindHash(hash);
}
- 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..b3fcf8ce0 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.BindHash;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.NaturalKeyQueryData;
import io.ebeaninternal.api.SpiEbeanServer;
@@ -91,8 +92,8 @@ class ExistsQueryExpression implements SpiExpression, UnsupportedDocStoreExpress
}
@Override
- public int queryBindHash() {
- return subQuery.queryBindHash();
+ public void queryBindHash(BindHash hash) {
+ subQuery.queryBindHash(hash);
}
@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..904fbe9be 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(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..390d0bebb 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(idCollection.size());
+ for (Object elem : idCollection) {
+ hash.update(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..a5719f71a 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(bindValues.size());
for (Object bindValue : bindValues) {
- hc = 92821 * hc + bindValue.hashCode();
+ hash.update(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..a7079ae55 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(entries.size());
for (Pairs.Entry entry : entries) {
- hc = 92821 * hc + entry.hashCode();
+ hash.update(entry.getA()).update(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..619f7b715 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,6 +1,7 @@
package io.ebeaninternal.server.expression;
import io.ebean.event.BeanQueryRequest;
+import io.ebeaninternal.api.BindHash;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
@@ -72,8 +73,8 @@ class InQueryExpression extends AbstractExpression implements UnsupportedDocStor
}
@Override
- public int queryBindHash() {
- return subQuery.queryBindHash();
+ public void queryBindHash(BindHash hash) {
+ subQuery.queryBindHash(hash);
}
@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..abe0b7338 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(low()).update(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..bb65962b3 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.BindHash;
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 queryBindHash(BindHash hash) {
+ // 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..18e0aa924 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(value).update(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..51eba8f39 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.BindHash;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.NaturalKeyQueryData;
import io.ebeaninternal.api.SpiExpression;
@@ -225,13 +226,12 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression
}
@Override
- public int queryBindHash() {
+ public void queryBindHash(BindHash hash) {
int hc = JunctionExpression.class.getName().hashCode();
List list = exprList.internalList();
for (SpiExpression aList : list) {
- hc = hc * 92821 + aList.queryBindHash();
+ aList.queryBindHash(hash);
}
- return hc;
}
@Override
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..587e6064c 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(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..cbd487b3e 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(expOne).update(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..8c51d2f5d 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(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..f3ed1badd 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.BindHash;
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 queryBindHash(BindHash hash) {
+ delegate.queryBindHash(hash);
}
@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..92180cea1 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.BindHash;
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 queryBindHash(BindHash hash) {
// 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..e9df6d856 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.BindHash;
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 queryBindHash(BindHash hash) {
+ exp.queryBindHash(hash);
}
@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..daded43cb 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(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..721d69b25 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(values.length);
for (Object value : values) {
- hc = hc * 92821 + value.hashCode();
+ hash.update(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..e50d9856c 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.BindHash;
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 queryBindHash(BindHash hash) {
+ hash.update(value());
}
@Override
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..6f3334ede 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
@@ -32,6 +32,8 @@ import io.ebean.bean.PersistenceContext;
import io.ebean.event.BeanQueryRequest;
import io.ebean.event.readaudit.ReadEvent;
import io.ebean.plugin.BeanType;
+import io.ebean.plugin.LoadErrorHandler;
+import io.ebeaninternal.api.BindHash;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.CQueryPlanKey;
import io.ebeaninternal.api.CacheIdLookup;
@@ -283,6 +285,8 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery {
private boolean orderById;
+ private final String bindHashAlgorithm;
+
private ProfileLocation profileLocation;
public DefaultOrmQuery(BeanDescriptor desc, SpiEbeanServer server, ExpressionFactory expressionFactory) {
@@ -291,6 +295,7 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery {
this.beanType = desc.getBeanType();
this.server = server;
this.orderById = server.getServerConfig().isDefaultOrderById();
+ this.bindHashAlgorithm = "MD5"; // TODO: server.getServerConfig().isUseMd5BindHash();
this.disableLazyLoading = server.getServerConfig().isDisableLazyLoading();
this.expressionFactory = expressionFactory;
this.detail = new OrmQueryDetail();
@@ -1276,15 +1281,12 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery {
*
*/
@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 queryBindHash(BindHash hash) {
+ hash.update(id);
+ if (whereExpressions != null) whereExpressions.queryBindHash(hash);
+ if (havingExpressions != null) havingExpressions.queryBindHash(hash);
+ if (bindParams != null) bindParams.queryBindHash(hash);
+ hash.update(asOf).update(versionsStart).update(versionsEnd);
}
/**
@@ -1298,8 +1300,10 @@ 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);
+ BindHash hash = bindHashAlgorithm == null ? new HashCodeBindHash() : new MdBindHash(bindHashAlgorithm);
+ queryBindHash(hash);
+ hash.finish();
+ return new HashQuery(queryPlanKey, hash);
}
@Override
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/HashCodeBindHash.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/HashCodeBindHash.java
new file mode 100644
index 000000000..cc6cd5f13
--- /dev/null
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/HashCodeBindHash.java
@@ -0,0 +1,56 @@
+package io.ebeaninternal.server.querydefn;
+
+import java.util.Objects;
+
+import io.ebeaninternal.api.BindHash;
+
+/**
+ * HashCode builder that uses Object.hashCode for computing bind-hashes.
+ * This is a fast and lightweight implementation, but may produce collisions.
+ *
+ * @author Roland Praml, FOCONIS AG
+ */
+public class HashCodeBindHash implements BindHash {
+
+ int hashCode;
+
+ @Override
+ public BindHash update(int intValue) {
+ hashCode = hashCode * 92821 + intValue;
+ return this;
+ }
+
+ @Override
+ public BindHash update(long longValue) {
+ hashCode = hashCode * 92821 + Long.hashCode(longValue);
+ return this;
+ }
+
+ @Override
+ public BindHash update(boolean boolValue) {
+ hashCode = hashCode * 92821 + Boolean.hashCode(boolValue);
+ return this;
+ }
+
+ @Override
+ public BindHash update(Object value) {
+ hashCode = hashCode * 92821 + Objects.hashCode(value);
+ return this;
+ }
+
+ @Override
+ public void finish() {
+ // nothing to do
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof HashCodeBindHash && ((HashCodeBindHash) obj).hashCode == hashCode;
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/MdBindHash.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/MdBindHash.java
new file mode 100644
index 000000000..4791a9c07
--- /dev/null
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/MdBindHash.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed Materials - Property of FOCONIS AG
+ * (C) Copyright FOCONIS AG.
+ */
+
+package io.ebeaninternal.server.querydefn;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.UUID;
+
+import io.ebeaninternal.api.BindHash;
+
+/**
+ * Bind hash that uses a MessageDigest to compute a collision resistent hash.
+ *
+ * @author Roland Praml, FOCONIS AG
+ *
+ */
+public class MdBindHash implements BindHash {
+ private MessageDigest md;
+ private byte[] buffer;
+ private int hashCode;
+
+ public MdBindHash(String algorithm) {
+ try {
+ md = MessageDigest.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException nsae) {
+ throw new InternalError(algorithm + " not supported", nsae);
+ }
+ }
+
+ @Override
+ public BindHash update(int v) {
+ md.update((byte) (v >>> 24));
+ md.update((byte) (v >>> 16));
+ md.update((byte) (v >>> 8));
+ md.update((byte) (v >>> 0));
+ return this;
+ }
+
+ @Override
+ public BindHash update(long v) {
+ md.update((byte) (v >>> 56));
+ md.update((byte) (v >>> 48));
+ md.update((byte) (v >>> 40));
+ md.update((byte) (v >>> 32));
+ md.update((byte) (v >>> 24));
+ md.update((byte) (v >>> 16));
+ md.update((byte) (v >>> 8));
+ md.update((byte) (v >>> 0));
+ return this;
+ }
+
+ @Override
+ public BindHash update(boolean boolValue) {
+ md.update(boolValue ? (byte) 1 : (byte) 0);
+ return this;
+ }
+
+ @Override
+ public BindHash update(Object value) {
+ if (value == null) {
+ md.update((byte) 0);
+
+ // do some special handling for known object types
+ } else if (value instanceof String) {
+ md.update(((String) value).getBytes());
+
+ } else if (value instanceof Long) {
+ update(((Long) value).longValue());
+
+ } else if (value instanceof Double) {
+ double d = ((Double) value).doubleValue();
+ update(Double.doubleToLongBits(d));
+
+ } else if (value instanceof UUID) {
+ UUID uuid = (UUID) value;
+ update(uuid.getLeastSignificantBits());
+ update(uuid.getMostSignificantBits());
+
+ } else if (value instanceof Date) {
+ update(((Date) value).getTime());
+
+ } else if (value instanceof Instant) {
+ update(((Instant) value).getEpochSecond());
+ update(((Instant) value).getNano());
+
+ } else if (value instanceof LocalDate) {
+ update(((LocalDate) value).toEpochDay());
+
+ } else if (value instanceof LocalTime) {
+ update(((LocalTime) value).toSecondOfDay());
+ update(((LocalTime) value).toNanoOfDay());
+
+ } else if (value instanceof LocalDateTime) {
+ update(((LocalDateTime) value).toLocalDate().toEpochDay());
+ update(((LocalDateTime) value).toLocalTime().toSecondOfDay());
+ update(((LocalDateTime) value).toLocalTime().toNanoOfDay());
+
+ } else {
+ // Fall back to hashCode for all other types
+ updateOther(value);
+ }
+ return this;
+ }
+
+ /**
+ * Update all other object. May be overridden to handle joda dates.
+ */
+ protected void updateOther(Object value) {
+ // Fall back to hashCode for all other types
+ update(value.hashCode());
+ }
+
+ @Override
+ public void finish() {
+ buffer = md.digest();
+ hashCode = Arrays.hashCode(buffer);
+ md = null; // clear memory
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof MdBindHash && Arrays.equals(buffer, ((MdBindHash) obj).buffer);
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+}
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..6c608472f 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,8 @@ package io.ebeaninternal.server.expression;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
+import io.ebeaninternal.api.BindHash;
+import io.ebeaninternal.server.querydefn.HashCodeBindHash;
public class RawExpressionTest extends BaseExpressionTest {
@@ -61,11 +63,17 @@ public class RawExpressionTest extends BaseExpressionTest {
}
public void assert_queryBindHash_isDifferent(RawExpression exp0, RawExpression exp1) {
- assertThat(exp0.queryBindHash()).isNotEqualTo(exp1.queryBindHash());
+ assertThat(getHash(exp0)).isNotEqualTo(getHash(exp1));
}
public void assert_queryBindHash_isSame(RawExpression exp0, RawExpression exp1) {
- assertThat(exp0.queryBindHash()).isEqualTo(exp1.queryBindHash());
+ assertThat(getHash(exp0)).isEqualTo(getHash(exp1));
}
+ private int getHash(RawExpression query) {
+ BindHash hash = new HashCodeBindHash();
+ query.queryBindHash(hash);
+ hash.finish();
+ return hash.hashCode();
+ }
}
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..cb1738ba8 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.BindHash;
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(getHash(q1)).isNotEqualTo(getHash(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(getHash(q1)).isNotEqualTo(getHash(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(getHash(q1)).isEqualTo(getHash(q2));
}
@Test
@@ -110,4 +111,11 @@ public class DefaultOrmQueryTest extends BaseTestCase {
OrmQueryRequest r2 = createQueryRequest(SpiQuery.Type.LIST, q2, null);
q2.prepare(r2);
}
+
+ private int getHash(DefaultOrmQuery query) {
+ BindHash hash = new HashCodeBindHash();
+ query.queryBindHash(hash);
+ hash.finish();
+ return hash.hashCode();
+ }
}
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
+ }
+
}
From e18c190905fa8a2a2051e6bba3447ad1a942408d Mon Sep 17 00:00:00 2001
From: Roland Praml
Date: Mon, 9 Aug 2021 09:46:49 +0200
Subject: [PATCH 03/22] Fix the test to run with java 8
---
.../server/profile/BasicProfileLocationTest.java | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
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");
}
From c8ae3c5ef65da0ad043f9a0c302720abd298fda9 Mon Sep 17 00:00:00 2001
From: Roland Praml
Date: Tue, 10 Aug 2021 09:30:25 +0200
Subject: [PATCH 04/22] FIX: Changelog-oldValue did not work for Json mutable
properties
---
.../io/ebean/bean/EntityBeanIntercept.java | 13 ++++++---
.../org/tests/changelog/TestChangeLog.java | 27 +++++++++++++++++++
.../tests/model/basic/EBasicChangeLog.java | 19 ++++++++++++-
3 files changed, 55 insertions(+), 4 deletions(-)
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-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/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;
+ }
}
From 3e8f76cd6d054cbc9bb88392c4e3048d8857841b Mon Sep 17 00:00:00 2001
From: rbygrave
Date: Tue, 10 Aug 2021 21:49:31 +1200
Subject: [PATCH 05/22] Update HashCodeBindHash to use values for equals()
---
.../server/querydefn/DefaultOrmQuery.java | 3 ++-
.../server/querydefn/HashCodeBindHash.java | 11 +++++++++--
2 files changed, 11 insertions(+), 3 deletions(-)
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 6f3334ede..f29889a88 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
@@ -1300,7 +1300,8 @@ 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
- BindHash hash = bindHashAlgorithm == null ? new HashCodeBindHash() : new MdBindHash(bindHashAlgorithm);
+ //BindHash hash = bindHashAlgorithm == null ? new HashCodeBindHash() : new MdBindHash(bindHashAlgorithm);
+ BindHash hash = new HashCodeBindHash();// : new MdBindHash(bindHashAlgorithm);
queryBindHash(hash);
hash.finish();
return new HashQuery(queryPlanKey, hash);
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/HashCodeBindHash.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/HashCodeBindHash.java
index cc6cd5f13..9bc9c60fc 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/HashCodeBindHash.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/HashCodeBindHash.java
@@ -1,5 +1,7 @@
package io.ebeaninternal.server.querydefn;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
import io.ebeaninternal.api.BindHash;
@@ -12,28 +14,33 @@ import io.ebeaninternal.api.BindHash;
*/
public class HashCodeBindHash implements BindHash {
- int hashCode;
+ private final List