From ef1143bb702822654d40b4e05312e4ada477c7a0 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Tue, 2 Mar 2021 15:26:29 +0100 Subject: [PATCH 01/72] FIX: orderBy does not work when used on formula property or in conjunction with "exists" query --- .../server/query/CQueryBuilder.java | 12 +++++-- .../server/querydefn/DefaultOrmQuery.java | 9 +++++ .../java/org/tests/basic/TestFetchId.java | 36 +++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java index ab008a5cf..1162e914d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java @@ -616,11 +616,19 @@ class CQueryBuilder { if (request.isInlineCountDistinct()) { sb.append(")"); } - if (distinct && dbOrderBy != null && !query.isSingleAttribute()) { + if (distinct && dbOrderBy != null) { // add the orderBy columns to the select clause (due to distinct) final OrderBy orderBy = query.getOrderBy(); if (orderBy != null && orderBy.supportsSelect()) { - sb.append(", ").append(DbOrderByTrim.trim(dbOrderBy)); + String trimmed = DbOrderByTrim.trim(dbOrderBy); + if (query.isSingleAttribute() && trimmed.equals(select.getSelectSql())) { + // NOP, already in SQL + // TODO: what to do if we select("id").orderBy("prop,id")? + // Can we live with a query like "select t0.id, t0.prop, t0.id from" + // or should we elliminate the second "t0.id" from select + } else { + sb.append(", ").append(trimmed); + } } } } 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 6f72febb9..e6eafc0b8 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 @@ -53,6 +53,7 @@ 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.el.ElPropertyDeploy; import io.ebeaninternal.server.expression.DefaultExpressionList; import io.ebeaninternal.server.expression.IdInExpression; import io.ebeaninternal.server.expression.SimpleExpression; @@ -537,6 +538,14 @@ public class DefaultOrmQuery implements SpiQuery { if (havingExpressions != null) { havingExpressions.containsMany(beanDescriptor, manyWhereJoins); } + if (orderBy != null) { + for (Property orderProperty : orderBy.getProperties()) { + ElPropertyDeploy elProp = beanDescriptor.getElPropertyDeploy(orderProperty.getProperty()); + if (elProp != null && elProp.containsFormulaWithJoin()) { + manyWhereJoins.addFormulaWithJoin(orderProperty.getProperty()); + } + } + } } /** diff --git a/ebean-core/src/test/java/org/tests/basic/TestFetchId.java b/ebean-core/src/test/java/org/tests/basic/TestFetchId.java index cf207995c..c830208d5 100644 --- a/ebean-core/src/test/java/org/tests/basic/TestFetchId.java +++ b/ebean-core/src/test/java/org/tests/basic/TestFetchId.java @@ -1,10 +1,14 @@ package org.tests.basic; import io.ebean.BaseTestCase; +import io.ebean.DB; import io.ebean.Ebean; import io.ebean.FutureIds; import io.ebean.Query; +import io.ebeantest.LoggedSql; + import org.tests.model.basic.Order; +import org.tests.model.basic.OrderDetail; import org.tests.model.basic.ResetBasicData; import org.junit.Test; @@ -36,4 +40,36 @@ public class TestFetchId extends BaseTestCase { List idList = futureIds.get(); assertThat(idList).isNotEmpty(); } + + @Test + public void testFetchIdWithExists() throws InterruptedException, ExecutionException { + + ResetBasicData.reset(); + + Query subQuery = Ebean.find(OrderDetail.class) + .alias("sq") + .where().raw("details.id = sq.id").query(); + Query query = Ebean.find(Order.class) + .where().exists(subQuery) + .orderBy("orderDate").query(); + + List ids = query.findIds(); + // TODO: assert(query.getGeneratedSql()) + assertThat(ids).isNotEmpty(); + FutureIds futureIds = query.findFutureIds(); + + // wait for all the id's to be fetched + List idList = futureIds.get(); + assertThat(idList).isNotEmpty(); + } + + @Test + public void testFetchIdWithOrderFormula() throws InterruptedException, ExecutionException { + + ResetBasicData.reset(); + + Query query = DB.find(Order.class).orderBy("totalItems"); + query.findIds(); + // TODO: assert(query.getGeneratedSql()) + } } From 74c7268d2fc73e439cc661beb6d9312d7758bea2 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Wed, 21 Apr 2021 18:57:03 +1200 Subject: [PATCH 02/72] Change such that filterMany can be included in the main query. That is, currently a filterMany automatically marks that path as a fetchQuery path. This change turns that off and the filterMany path and predicates can then be included in the main query. This is better for performance (to include the filterMany path in with the main query) when the query is a findEach and looking to return a large number of results. --- .../io/ebeaninternal/api/SpiExpression.java | 5 ++ .../ebeaninternal/api/SpiExpressionList.java | 5 ++ .../server/core/OrmQueryRequest.java | 4 +- .../server/expression/AbstractExpression.java | 7 ++- .../expression/BetweenPropertyExpression.java | 10 +++- .../expression/DefaultExampleExpression.java | 7 +++ .../expression/DefaultExpressionList.java | 7 +++ .../expression/ExistsQueryExpression.java | 5 ++ .../server/expression/IdExpression.java | 5 ++ .../server/expression/JunctionExpression.java | 5 ++ .../server/expression/LogicExpression.java | 6 +++ .../NestedPathWrapperExpression.java | 5 ++ .../expression/NonPrepareExpression.java | 5 ++ .../server/expression/NoopExpression.java | 5 ++ .../server/expression/NotExpression.java | 5 ++ .../server/querydefn/DefaultOrmQuery.java | 1 + .../server/querydefn/OrmQueryDetail.java | 45 +++++------------ .../server/querydefn/OrmQueryProperties.java | 22 ++++++-- ...faultServer_createOrmQueryRequestTest.java | 36 ++++++++++++- .../org/tests/query/TestQueryFilterMany.java | 50 +++++++++++-------- 20 files changed, 179 insertions(+), 61 deletions(-) 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 cad22c50a..007e9f29f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiExpression.java @@ -105,4 +105,9 @@ public interface SpiExpression extends Expression { * Check for match to a natural key query returning false if it doesn't match. */ boolean naturalKey(NaturalKeyQueryData data); + + /** + * Apply property prefix when filterMany expressions included into main query. + */ + void prefixProperty(String path); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiExpressionList.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiExpressionList.java index 3cf5c2e64..c4764b30f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiExpressionList.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiExpressionList.java @@ -43,4 +43,9 @@ public interface SpiExpressionList extends ExpressionList, SpiExpression { default void applyRowLimits(SpiQuery query) { // do nothing by default } + + /** + * Apply property prefix when filterMany expressions included in main query. + */ + void prefixProperty(String path); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java index 3ec5deac7..5548c88e3 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java @@ -219,10 +219,10 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery */ @Override public void prepareQuery() { + secondaryQueries = query.convertJoins(); beanDescriptor.prepareQuery(query); adapterPreQuery(); - this.secondaryQueries = query.convertJoins(); - this.queryPlanKey = query.prepare(this); + queryPlanKey = query.prepare(this); } public boolean isNativeSql() { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/AbstractExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/AbstractExpression.java index 0ae5cd6fc..c3090e38f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/AbstractExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/AbstractExpression.java @@ -16,12 +16,17 @@ import io.ebeaninternal.api.NaturalKeyQueryData; */ public abstract class AbstractExpression implements SpiExpression { - protected final String propName; + protected String propName; protected AbstractExpression(String propName) { this.propName = propName; } + @Override + public void prefixProperty(String path) { + this.propName = path + "." + propName; + } + @Override public boolean naturalKey(NaturalKeyQueryData data) { // by default can't use naturalKey cache 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 798908335..a50ec888f 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 @@ -17,8 +17,8 @@ class BetweenPropertyExpression extends NonPrepareExpression { private static final String BETWEEN = " between "; - private final String lowProperty; - private final String highProperty; + private String lowProperty; + private String highProperty; private final Object value; BetweenPropertyExpression(String lowProperty, String highProperty, Object value) { @@ -27,6 +27,12 @@ class BetweenPropertyExpression extends NonPrepareExpression { this.value = value; } + @Override + public void prefixProperty(String path) { + this.lowProperty = path + "." + lowProperty; + this.highProperty = path + "." + highProperty; + } + protected String name(String propName) { return propName; } 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 c6fa5d087..aade99f5a 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 @@ -88,6 +88,13 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio } } + @Override + public void prefixProperty(String path) { + for (SpiExpression exp : list) { + exp.prefixProperty(path); + } + } + @Override public boolean naturalKey(NaturalKeyQueryData data) { // can't use naturalKey cache 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 c034b2992..ab80ae9e5 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 @@ -126,6 +126,13 @@ public class DefaultExpressionList implements SpiExpressionList { } } + @Override + public void prefixProperty(String path) { + for (SpiExpression exp : list) { + exp.prefixProperty(path); + } + } + @Override public Junction toJunction() { return new JunctionExpression<>(Junction.Type.FILTER, this); 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 8918bd05d..32ae2bccc 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 @@ -36,6 +36,11 @@ class ExistsQueryExpression implements SpiExpression, UnsupportedDocStoreExpress this.subQuery = null; } + @Override + public void prefixProperty(String path) { + // do nothing + } + @Override public boolean naturalKey(NaturalKeyQueryData data) { // can't use naturalKey cache 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 12910fb37..b5280b868 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 @@ -19,6 +19,11 @@ class IdExpression extends NonPrepareExpression implements SpiExpression { this.value = value; } + @Override + public void prefixProperty(String path) { + throw new IllegalStateException("Not allowed?"); + } + @Override public void writeDocQuery(DocQueryContext context) throws IOException { context.writeId(value); 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 f048f1163..0aae30886 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 @@ -66,6 +66,11 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression this.exprList = exprList; } + @Override + public void prefixProperty(String path) { + exprList.prefixProperty(path); + } + @Override public boolean naturalKey(NaturalKeyQueryData data) { // can't use naturalKey cache 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 0fa0d9b37..482aac6e8 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 @@ -57,6 +57,12 @@ abstract class LogicExpression implements SpiExpression { this.expTwo = (SpiExpression) expTwo; } + @Override + public void prefixProperty(String path) { + expOne.prefixProperty(path); + expTwo.prefixProperty(path); + } + @Override public boolean naturalKey(NaturalKeyQueryData data) { // can't use naturalKey cache 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 c7f5d193e..77f46828b 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 @@ -24,6 +24,11 @@ class NestedPathWrapperExpression implements SpiExpression { this.delegate = delegate; } + @Override + public void prefixProperty(String path) { + // do nothing + } + @Override public boolean naturalKey(NaturalKeyQueryData data) { // can't use naturalKey cache diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NonPrepareExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NonPrepareExpression.java index 675e641ee..e384bbd1e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NonPrepareExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NonPrepareExpression.java @@ -9,6 +9,11 @@ import io.ebeaninternal.api.NaturalKeyQueryData; */ abstract class NonPrepareExpression implements SpiExpression { + @Override + public void prefixProperty(String path) { + // do nothing + } + @Override public boolean naturalKey(NaturalKeyQueryData data) { // can't use naturalKey cache 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 c95c8585b..28c9e3c78 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 @@ -17,6 +17,11 @@ class NoopExpression implements SpiExpression { protected static final NoopExpression INSTANCE = new NoopExpression(); + @Override + public void prefixProperty(String path) { + // do nothing + } + @Override public boolean naturalKey(NaturalKeyQueryData data) { // can't use naturalKey cache 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 29535cd95..60ade47b0 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 @@ -22,6 +22,11 @@ final class NotExpression implements SpiExpression { this.exp = (SpiExpression) exp; } + @Override + public void prefixProperty(String path) { + exp.prefixProperty(path); + } + @Override public boolean naturalKey(NaturalKeyQueryData data) { // can't use naturalKey cache 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 867e16dfa..71fe9ff68 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 @@ -1238,6 +1238,7 @@ public class DefaultOrmQuery implements SpiQuery { * Prepare the expressions (compile sub-queries etc). */ private void prepareExpressions(BeanQueryRequest request) { + detail.prepareExpressions(request); if (whereExpressions != null) { whereExpressions.prepareExpression(request); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java index 6f96fbbea..6646746eb 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.querydefn; import io.ebean.FetchConfig; +import io.ebean.event.BeanQueryRequest; import io.ebean.util.SplitName; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.deploy.BeanPropertyAssoc; @@ -9,25 +10,16 @@ import io.ebeaninternal.server.el.ElPropertyValue; import javax.persistence.PersistenceException; import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * Represents the internal structure of an Object Relational query. *

* Holds the select() and join() details of a ORM query. - *

*

* It is worth noting that for AutoTune a "tuned fetch info" builds an instance of OrmQueryDetail. * Tuning a query is a matter of replacing an instance of this class with one that has been tuned * with select() and join() set. - *

*/ public class OrmQueryDetail implements Serializable { @@ -81,7 +73,6 @@ public class OrmQueryDetail implements Serializable { * Return true if equal in terms of autoTune (select and fetch without property ordering). */ public boolean isAutoTuneEqual(OrmQueryDetail otherDetail) { - if (!isSameByAutoTune(baseProps, otherDetail.baseProps)) { return false; } @@ -175,9 +166,7 @@ public class OrmQueryDetail implements Serializable { } private List removeSecondaryQueries(boolean lazyQuery) { - ArrayList matchingPaths = new ArrayList<>(2); - for (OrmQueryProperties chunk : fetchPaths.values()) { boolean match = lazyQuery ? chunk.isLazyFetch() : chunk.isQueryFetch(); if (match) { @@ -191,7 +180,6 @@ public class OrmQueryDetail implements Serializable { // sort into depth order to remove Collections.sort(matchingPaths); - // the list of secondary queries ArrayList props = new ArrayList<>(); @@ -225,14 +213,11 @@ public class OrmQueryDetail implements Serializable { OrmQueryProperties chunk = getChunk(split[0], true); chunk.addSecondaryQueryJoin(split[1]); } - return props; } boolean tuneFetchProperties(OrmQueryDetail tunedDetail) { - boolean tuned = false; - OrmQueryProperties tunedRoot = tunedDetail.getChunk(null, false); if (tunedRoot != null) { tuned = true; @@ -257,10 +242,7 @@ public class OrmQueryDetail implements Serializable { } /** - * Remove all joins and properties. - *

- * Typically for the row count query. - *

+ * Remove all joins and properties. Typically for the row count query. */ public void clear() { fetchPaths.clear(); @@ -306,7 +288,6 @@ public class OrmQueryDetail implements Serializable { } private void sortFetchPaths(BeanDescriptor d, boolean addIds) { - if (!fetchPaths.isEmpty()) { LinkedHashMap sorted = new LinkedHashMap<>(); for (OrmQueryProperties p : fetchPaths.values()) { @@ -317,7 +298,6 @@ public class OrmQueryDetail implements Serializable { } private void sortFetchPaths(BeanDescriptor d, OrmQueryProperties p, LinkedHashMap sorted, boolean addId) { - String path = p.getPath(); if (!sorted.containsKey(path)) { String parentPath = p.getParentPath(); @@ -339,7 +319,6 @@ public class OrmQueryDetail implements Serializable { parentProp = new OrmQueryProperties(parentPath, Collections.emptySet()); } } - sortFetchPaths(d, parentProp, sorted, addId); sorted.put(path, p); } @@ -350,14 +329,12 @@ public class OrmQueryDetail implements Serializable { * Mark 'fetch joins' to 'many' properties over to 'query joins' where needed. */ void markQueryJoins(BeanDescriptor beanDescriptor, String lazyLoadManyPath, boolean allowOne, boolean addIds) { - if (fetchPaths.isEmpty()) { return; } // the name of the many fetch property if there is one String manyFetchProperty = null; - // flag that is set once the many fetch property is chosen boolean fetchJoinFirstMany = allowOne; @@ -375,6 +352,7 @@ public class OrmQueryDetail implements Serializable { // letting the first one remain a 'fetch join' fetchJoinFirstMany = false; manyFetchProperty = pair.getPath(); + chunk.filterManyInline(); } else { // convert this one over to a 'query join' chunk.markForQueryJoin(); @@ -388,7 +366,6 @@ public class OrmQueryDetail implements Serializable { * Sort the fetch entries taking into account fetchPreference on the path. */ private List sortByFetchPreference(BeanDescriptor desc) { - List entries = new ArrayList<>(fetchPaths.size()); int idx = 0; for (Map.Entry entry : fetchPaths.entrySet()) { @@ -452,11 +429,9 @@ public class OrmQueryDetail implements Serializable { *

*/ public void setDefaultSelectClause(BeanDescriptor desc) { - if (desc.hasDefaultSelectClause() && !hasSelectClause()) { baseProps = new OrmQueryProperties(null, desc.getDefaultSelectClause()); } - for (OrmQueryProperties joinProps : fetchPaths.values()) { if (!joinProps.hasSelectClause()) { BeanDescriptor assocDesc = desc.getBeanDescriptor(joinProps.getPath()); @@ -506,7 +481,6 @@ public class OrmQueryDetail implements Serializable { props = new OrmQueryProperties(path); fetch(props); return props; - } else { return props; } @@ -516,9 +490,7 @@ public class OrmQueryDetail implements Serializable { * Return true if the fetch path is included. */ public boolean includesPath(String path) { - OrmQueryProperties chunk = fetchPaths.get(path); - // may not have fetch properties if just +cache etc return chunk != null && !chunk.isCache(); } @@ -537,6 +509,15 @@ public class OrmQueryDetail implements Serializable { return fetchPaths.entrySet(); } + /** + * Prepare filterMany expressions that are being included into the main query. + */ + public void prepareExpressions(BeanQueryRequest request) { + for (OrmQueryProperties value : fetchPaths.values()) { + value.prepareExpressions(request); + } + } + private static class FetchEntry implements Comparable { private final int index; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryProperties.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryProperties.java index 466bb3eb9..0a8b51465 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -4,6 +4,7 @@ import io.ebean.ExpressionFactory; import io.ebean.FetchConfig; import io.ebean.OrderBy; import io.ebean.Query; +import io.ebean.event.BeanQueryRequest; import io.ebean.util.SplitName; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionFactory; @@ -178,8 +179,6 @@ public class OrmQueryProperties implements Serializable { SpiExpressionFactory queryEf = (SpiExpressionFactory) rootQuery.getExpressionFactory(); ExpressionFactory filterEf = queryEf.createExpressionFactory();// exprPath); filterMany = new FilterExpressionList(exprPath, filterEf, rootQuery); - // by default we need to make this a 'query join' now - markForQueryJoin = true; } return filterMany; } @@ -194,6 +193,24 @@ public class OrmQueryProperties implements Serializable { return filterMany.trimPath(trimPath); } + /** + * Adjust filterMany expressions for inclusion in main query. + */ + public void filterManyInline() { + if (filterMany != null){ + filterMany.prefixProperty(path); + } + } + + /** + * Prepare filterMany expressions for query plan key. + */ + public void prepareExpressions(BeanQueryRequest request) { + if (filterMany != null) { + filterMany.prepareExpression(request); + } + } + /** * Return the filterMany expression list (can be null). */ @@ -206,7 +223,6 @@ public class OrmQueryProperties implements Serializable { */ public void setFilterMany(SpiExpressionList filterMany) { this.filterMany = filterMany; - this.markForQueryJoin = true; } /** diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java index 12c731115..1a5484f16 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java @@ -288,7 +288,7 @@ public class DefaultServer_createOrmQueryRequestTest extends BaseTestCase { } @Test - public void test_removeJoinToMany_when_filterMany() { + public void test_filterMany_included() { Query query = Ebean.find(Order.class) .fetch("details") @@ -301,6 +301,40 @@ public class DefaultServer_createOrmQueryRequestTest extends BaseTestCase { OrmQueryRequest queryRequest = queryRequest(query); OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + assertThat(detail.getFetchPaths()).containsExactly("details", "details.product", "customer"); + } + + @Test + public void test_filterMany_excludedByOrdering() { + + Query query = Ebean.find(Order.class) + .fetch("customer") + .fetch("customer.contacts") + .fetch("details") + .fetch("details.product") + .filterMany("details").eq("orderQuantity", 10) + .query(); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("customer", "customer.contacts"); + } + + @Test + public void test_filterMany_excludedExplicitly() { + + Query query = Ebean.find(Order.class) + .fetchQuery("details") + .fetch("details.product") + .fetch("customer") + .fetch("customer.contacts") + .filterMany("details").eq("orderQuantity", 10) + .query(); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + assertThat(detail.getFetchPaths()).containsExactly("customer", "customer.contacts"); } diff --git a/ebean-core/src/test/java/org/tests/query/TestQueryFilterMany.java b/ebean-core/src/test/java/org/tests/query/TestQueryFilterMany.java index 976dac580..be58fb8c4 100644 --- a/ebean-core/src/test/java/org/tests/query/TestQueryFilterMany.java +++ b/ebean-core/src/test/java/org/tests/query/TestQueryFilterMany.java @@ -172,7 +172,6 @@ public class TestQueryFilterMany extends BaseTestCase { LoggedSqlCollector.start(); Query query = Ebean.find(Customer.class) - .fetch("orders") .filterMany("orders").raw("1=0") .where().isNotEmpty("orders") .query(); @@ -183,12 +182,10 @@ public class TestQueryFilterMany extends BaseTestCase { } List sqlList = LoggedSqlCollector.stop(); - assertEquals(2, sqlList.size()); - assertThat(sqlList.get(0)).contains("where exists (select 1 from o_order x where x.kcustomer_id = t0.id)"); - assertThat(sqlList.get(1)).contains("and 1=0"); + assertEquals(1, sqlList.size()); + assertThat(sqlList.get(0)).contains("from o_customer t0 left join o_order t1 on t1.kcustomer_id = t0.id and t1.order_date is not null left join o_customer t2 on t2.id = t1.kcustomer_id where exists (select 1 from o_order x where x.kcustomer_id = t0.id) and 1=0 order by t0.id"); } - @Test public void test_filterMany_in_findCount() { @@ -212,7 +209,6 @@ public class TestQueryFilterMany extends BaseTestCase { public void test_filterMany_copy_findList() { ResetBasicData.reset(); - LoggedSqlCollector.start(); Query query = Ebean.find(Customer.class) @@ -222,17 +218,35 @@ public class TestQueryFilterMany extends BaseTestCase { query.copy().findList(); + List sqlList = LoggedSqlCollector.stop(); + assertEquals(1, sqlList.size()); + assertThat(sqlList.get(0)).contains("from o_customer t0 left join o_order t1 on t1.kcustomer_id = t0.id and t1.order_date is not null left join o_customer t2 on t2.id = t1.kcustomer_id where t1.status in (?) order by t0.id"); + } + + @Test + public void test_filterMany_fetchQuery() { + + ResetBasicData.reset(); + LoggedSqlCollector.start(); + + Query query = Ebean.find(Customer.class) + .fetchQuery("orders") // explicitly fetch orders separately + .filterMany("orders").in("status", Order.Status.NEW) + .order().asc("id"); + + query.findList(); + List sqlList = LoggedSqlCollector.stop(); assertEquals(2, sqlList.size()); assertThat(sqlList.get(0)).contains("from o_customer t0"); - assertThat(sqlList.get(1)).contains("from o_order t0 join o_customer t1"); + assertThat(sqlList.get(1)).contains("from o_order t0 join o_customer t1 on t1.id = t0.kcustomer_id where t0.order_date is not null and (t0.kcustomer_id) in "); + assertThat(sqlList.get(1)).contains(" and t0.status in "); } @Test public void testDisjunction() { ResetBasicData.reset(); - LoggedSqlCollector.start(); Ebean.find(Customer.class) @@ -243,8 +257,8 @@ public class TestQueryFilterMany extends BaseTestCase { .findList(); List sql = LoggedSqlCollector.stop(); - assertEquals(2, sql.size()); - assertSql(sql.get(1)).contains("and (t0.status = ? or t0.order_date = ?"); + assertEquals(1, sql.size()); + assertSql(sql.get(0)).contains(" from o_customer t0 left join o_order t1 on t1.kcustomer_id = t0.id and t1.order_date is not null left join o_customer t2 on t2.id = t1.kcustomer_id where (t1.status = ? or t1.order_date = ?) order by t0.id"); } @Test @@ -259,12 +273,10 @@ public class TestQueryFilterMany extends BaseTestCase { List sql = LoggedSqlCollector.stop(); - assertThat(sql).hasSize(3); - assertSql(sql.get(0)).contains(" from o_customer t0; --bind()"); - platformAssertIn(sql.get(1), " from contact t0 where (t0.customer_id)"); - assertSql(sql.get(1)).contains(" and t0.first_name is not null"); - platformAssertIn(sql.get(2), " from contact_note t0 where (t0.contact_id)"); - assertSql(sql.get(2)).contains(" and lower(t0.title) like"); + assertThat(sql).hasSize(2); + assertSql(sql.get(0)).contains(" from o_customer t0 left join contact t1 on t1.customer_id = t0.id where t1.first_name is not null order by t0.id; --bind()"); + platformAssertIn(sql.get(1), " from contact_note t0 where (t0.contact_id)"); + assertSql(sql.get(1)).contains(" and lower(t0.title) like"); } @Test @@ -280,9 +292,7 @@ public class TestQueryFilterMany extends BaseTestCase { List sql = LoggedSqlCollector.stop(); - assertThat(sql).hasSize(2); - assertSql(sql.get(0)).contains(" from o_customer t0"); - assertSql(sql.get(1)).contains("from contact t0 where "); - assertSql(sql.get(1)).contains("and (t0.first_name is not null and lower(t0.email) like ?"); + assertThat(sql).hasSize(1); + assertSql(sql.get(0)).contains(" from o_customer t0 left join contact t1 on t1.customer_id = t0.id where (t1.first_name is not null and lower(t1.email) like ? escape'|' ) order by t0.id; --bind(rob%)"); } } From d6fdb4ee34129f4b98b35686b822c3c4154d77b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Apr 2021 20:37:55 +0000 Subject: [PATCH 03/72] Bump commons-io from 2.5 to 2.7 in /ebean-core Bumps commons-io from 2.5 to 2.7. Signed-off-by: dependabot[bot] --- ebean-core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index dbde9f167..13732d513 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -282,7 +282,7 @@ commons-io commons-io - 2.5 + 2.7 test From ad52711fe172e70d5ab2f649f163399f2dc4ce62 Mon Sep 17 00:00:00 2001 From: Brian Payne Date: Tue, 11 May 2021 08:52:08 -0700 Subject: [PATCH 04/72] Add test demonstrating soft-delete bug with optional relationship --- .../org/tests/model/softdelete/ESoftDelX.java | 54 +++++++++++++++++++ .../org/tests/model/softdelete/ESoftDelY.java | 53 ++++++++++++++++++ .../org/tests/model/softdelete/ESoftDelZ.java | 43 +++++++++++++++ .../TestSoftDeleteOptionalRelationship.java | 31 +++++++++++ 4 files changed, 181 insertions(+) create mode 100644 ebean-core/src/test/java/org/tests/model/softdelete/ESoftDelX.java create mode 100644 ebean-core/src/test/java/org/tests/model/softdelete/ESoftDelY.java create mode 100644 ebean-core/src/test/java/org/tests/model/softdelete/ESoftDelZ.java diff --git a/ebean-core/src/test/java/org/tests/model/softdelete/ESoftDelX.java b/ebean-core/src/test/java/org/tests/model/softdelete/ESoftDelX.java new file mode 100644 index 000000000..fbc3f9964 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/softdelete/ESoftDelX.java @@ -0,0 +1,54 @@ +package org.tests.model.softdelete; + +import io.ebean.annotation.SoftDelete; + +import javax.persistence.*; +import java.util.UUID; + +@Entity +public class ESoftDelX { + + @Id + private UUID id; + + @OneToOne + private ESoftDelY y; + + @ManyToOne + private ESoftDelZ organization; + + @SoftDelete + boolean deleted; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public ESoftDelY getY() { + return y; + } + + public void setY(ESoftDelY y) { + this.y = y; + } + + public ESoftDelZ getOrganization() { + return organization; + } + + public void setOrganization(ESoftDelZ organization) { + this.organization = organization; + } + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } +} diff --git a/ebean-core/src/test/java/org/tests/model/softdelete/ESoftDelY.java b/ebean-core/src/test/java/org/tests/model/softdelete/ESoftDelY.java new file mode 100644 index 000000000..a65cc75c1 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/softdelete/ESoftDelY.java @@ -0,0 +1,53 @@ +package org.tests.model.softdelete; + +import io.ebean.annotation.SoftDelete; + +import javax.persistence.*; + +@Entity +public class ESoftDelY { + + @Id + private Long id; + + @ManyToOne + private ESoftDelZ organization; + + @OneToOne(mappedBy = "y") + private ESoftDelX x; + + @SoftDelete + boolean deleted; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public ESoftDelZ getOrganization() { + return organization; + } + + public void setOrganization(ESoftDelZ organization) { + this.organization = organization; + } + + public ESoftDelX getX() { + return x; + } + + public void setX(ESoftDelX x) { + this.x = x; + } + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } +} diff --git a/ebean-core/src/test/java/org/tests/model/softdelete/ESoftDelZ.java b/ebean-core/src/test/java/org/tests/model/softdelete/ESoftDelZ.java new file mode 100644 index 000000000..5ce11c175 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/softdelete/ESoftDelZ.java @@ -0,0 +1,43 @@ +package org.tests.model.softdelete; + +import io.ebean.annotation.SoftDelete; + +import javax.persistence.Entity; +import javax.persistence.Id; +import java.util.UUID; + +@Entity +public class ESoftDelZ { + + @Id + private Long id; + + @SoftDelete + private boolean deleted; + + private UUID uuid; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } + + public UUID getUuid() { + return uuid; + } + + public void setUuid(UUID uuid) { + this.uuid = uuid; + } +} diff --git a/ebean-core/src/test/java/org/tests/softdelete/TestSoftDeleteOptionalRelationship.java b/ebean-core/src/test/java/org/tests/softdelete/TestSoftDeleteOptionalRelationship.java index fc925f62c..2488060d4 100644 --- a/ebean-core/src/test/java/org/tests/softdelete/TestSoftDeleteOptionalRelationship.java +++ b/ebean-core/src/test/java/org/tests/softdelete/TestSoftDeleteOptionalRelationship.java @@ -1,9 +1,15 @@ package org.tests.softdelete; import io.ebean.BaseTestCase; +import io.ebean.DB; import io.ebean.Ebean; import org.tests.model.softdelete.ESoftDelMid; +import io.ebean.Finder; import org.junit.Test; +import org.tests.model.softdelete.ESoftDelY; +import org.tests.model.softdelete.ESoftDelZ; + +import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; @@ -24,5 +30,30 @@ public class TestSoftDeleteOptionalRelationship extends BaseTestCase { assertThat(bean.getTop()).isNull(); } + @Test + public void testFindNullWhenMultiple() { + UUID uuid = UUID.randomUUID(); + { + ESoftDelZ z = new ESoftDelZ(); + z.setUuid(uuid); + DB.save(z); + + ESoftDelY y = new ESoftDelY(); + y.setOrganization(z); + y.setX(null); + DB.save(y); + } + + Finder finder = new Finder<>(ESoftDelY.class); + ESoftDelY bean = finder + .query() + .where() + .eq("organization.uuid", uuid) + .isNull("x") + .findOne(); + + assertThat(bean).isNotNull(); + } + } From 66f7f69ec70bc301d8effb18f01185b817064fdd Mon Sep 17 00:00:00 2001 From: Brian Payne Date: Wed, 12 May 2021 09:24:33 -0700 Subject: [PATCH 05/72] Fixed bug with soft-delete on one-to-one exported --- .../server/query/SqlTreeNodeExtraJoin.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java index 074457a30..7238224d7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java @@ -5,6 +5,7 @@ import io.ebean.bean.EntityBean; import io.ebean.core.type.ScalarType; import io.ebean.util.SplitName; import io.ebeaninternal.api.SpiQuery; +import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; import io.ebeaninternal.server.deploy.TableJoin; @@ -130,11 +131,19 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode { } } + boolean oneToOneExported = false; + if (assocBeanProperty instanceof BeanPropertyAssocOne) { + BeanPropertyAssocOne oneToOneProp = (BeanPropertyAssocOne) assocBeanProperty; + if (oneToOneProp.isOneToOneExported()) { + oneToOneExported = true; + } + } + if (pathContainsMany) { // "promote" to left join as the path contains a many joinType = SqlJoinType.OUTER; } - if (!manyToMany) { + if (!manyToMany && !oneToOneExported) { if (assocBeanProperty.isFormula()) { // add joins for formula beans assocBeanProperty.appendFrom(ctx, joinType); From eb00690b3034b970d4e7a1778d61eb0c305c82aa Mon Sep 17 00:00:00 2001 From: rbygrave Date: Thu, 20 May 2021 14:09:22 +1200 Subject: [PATCH 06/72] #2231 - Change OrderBy to not be final to support mocking via Mockito --- ebean-api/src/main/java/io/ebean/OrderBy.java | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/ebean-api/src/main/java/io/ebean/OrderBy.java b/ebean-api/src/main/java/io/ebean/OrderBy.java index 820a76314..8b39fc19e 100644 --- a/ebean-api/src/main/java/io/ebean/OrderBy.java +++ b/ebean-api/src/main/java/io/ebean/OrderBy.java @@ -16,7 +16,7 @@ import java.util.Objects; * on the Query object. *

*/ -public final class OrderBy implements Serializable { +public class OrderBy implements Serializable { private static final long serialVersionUID = 9157089257745730539L; @@ -69,7 +69,6 @@ public final class OrderBy implements Serializable { * Add a property with ascending order to this OrderBy. */ public Query asc(String propertyName) { - list.add(new Property(propertyName, true)); return query; } @@ -98,7 +97,6 @@ public final class OrderBy implements Serializable { return query; } - /** * Return true if the property is known to be contained in the order by clause. */ @@ -207,7 +205,6 @@ public final class OrderBy implements Serializable { if (!(obj instanceof OrderBy)) { return false; } - OrderBy e = (OrderBy) obj; return e.list.equals(list); } @@ -249,7 +246,7 @@ public final class OrderBy implements Serializable { /** * A property and its ascending descending order. */ - public static final class Property implements Serializable { + public static class Property implements Serializable { private static final long serialVersionUID = 1546009780322478077L; @@ -415,13 +412,10 @@ public final class OrderBy implements Serializable { } private void parse(String orderByClause) { - if (orderByClause == null) { return; } - - String[] chunks = orderByClause.split(","); - for (String chunk : chunks) { + for (String chunk : orderByClause.split(",")) { Property p = parseProperty(chunk); if (p != null) { list.add(p); @@ -467,8 +461,7 @@ public final class OrderBy implements Serializable { if (s.startsWith("desc")) { return false; } - String m = "Expecting [" + s + "] to be asc or desc?"; - throw new RuntimeException(m); + throw new RuntimeException("Expecting [" + s + "] to be asc or desc?"); } private boolean isEmptyString(String s) { From b17593ba3cbf4a2c70967fbf0a93568607d10961 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Thu, 20 May 2021 21:44:07 +1200 Subject: [PATCH 07/72] No effective change - tidy BeanList, BeanMap, BeanSet internals --- ebean-api/src/main/java/io/ebean/common/BeanList.java | 3 +-- ebean-api/src/main/java/io/ebean/common/BeanMap.java | 1 - ebean-api/src/main/java/io/ebean/common/BeanSet.java | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ebean-api/src/main/java/io/ebean/common/BeanList.java b/ebean-api/src/main/java/io/ebean/common/BeanList.java index 0aa774a81..ba0adca99 100644 --- a/ebean-api/src/main/java/io/ebean/common/BeanList.java +++ b/ebean-api/src/main/java/io/ebean/common/BeanList.java @@ -198,10 +198,9 @@ public final class BeanList extends AbstractBeanCollection implements List } if (list == null) { sb.append("deferred "); - } else { sb.append("size[").append(list.size()).append("] "); - sb.append("list").append(list).append(""); + sb.append("list").append(list); } return sb.toString(); } diff --git a/ebean-api/src/main/java/io/ebean/common/BeanMap.java b/ebean-api/src/main/java/io/ebean/common/BeanMap.java index 09b37d38a..2b9ef350d 100644 --- a/ebean-api/src/main/java/io/ebean/common/BeanMap.java +++ b/ebean-api/src/main/java/io/ebean/common/BeanMap.java @@ -194,7 +194,6 @@ public final class BeanMap extends AbstractBeanCollection implements Ma } if (map == null) { sb.append("deferred "); - } else { sb.append("size[").append(map.size()).append("]"); sb.append(" map").append(map); diff --git a/ebean-api/src/main/java/io/ebean/common/BeanSet.java b/ebean-api/src/main/java/io/ebean/common/BeanSet.java index 880ece106..0c6f0c508 100644 --- a/ebean-api/src/main/java/io/ebean/common/BeanSet.java +++ b/ebean-api/src/main/java/io/ebean/common/BeanSet.java @@ -176,7 +176,6 @@ public final class BeanSet extends AbstractBeanCollection implements Set Date: Fri, 21 May 2021 08:33:31 +1200 Subject: [PATCH 08/72] #2233 Failing test --- .../org/tests/lazyloadconf/AppConfig.java | 36 +++++++ .../tests/lazyloadconf/AppConfigControl.java | 42 ++++++++ .../java/org/tests/lazyloadconf/MyTest.java | 97 +++++++++++++++++++ .../org/tests/lazyloadconf/Relationship.java | 35 +++++++ 4 files changed, 210 insertions(+) create mode 100644 ebean-core/src/test/java/org/tests/lazyloadconf/AppConfig.java create mode 100644 ebean-core/src/test/java/org/tests/lazyloadconf/AppConfigControl.java create mode 100644 ebean-core/src/test/java/org/tests/lazyloadconf/MyTest.java create mode 100644 ebean-core/src/test/java/org/tests/lazyloadconf/Relationship.java diff --git a/ebean-core/src/test/java/org/tests/lazyloadconf/AppConfig.java b/ebean-core/src/test/java/org/tests/lazyloadconf/AppConfig.java new file mode 100644 index 000000000..999ef1aa0 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/lazyloadconf/AppConfig.java @@ -0,0 +1,36 @@ +package org.tests.lazyloadconf; + +import io.ebean.annotation.Cache; + +import javax.persistence.*; +import java.util.List; + +@Entity +@Cache +@Table(name = "app_config") +public class AppConfig { + + @Id + @Column(name = "id") + private Integer id; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "appConfig") + //@JoinColumn(name = "id", referencedColumnName = "id") + private List items; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } +} diff --git a/ebean-core/src/test/java/org/tests/lazyloadconf/AppConfigControl.java b/ebean-core/src/test/java/org/tests/lazyloadconf/AppConfigControl.java new file mode 100644 index 000000000..8da38cd59 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/lazyloadconf/AppConfigControl.java @@ -0,0 +1,42 @@ +package org.tests.lazyloadconf; + +import javax.persistence.*; + +@Entity +@Table(name = "app_config_control") +public class AppConfigControl { + + @Id + private Integer id; + + private String name; + + @ManyToOne + @JoinColumn(name = "config_id", referencedColumnName = "id") + private AppConfig appConfig; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public AppConfig getAppConfig() { + return appConfig; + } + + public void setAppConfig(AppConfig appConfig) { + this.appConfig = appConfig; + } +} + diff --git a/ebean-core/src/test/java/org/tests/lazyloadconf/MyTest.java b/ebean-core/src/test/java/org/tests/lazyloadconf/MyTest.java new file mode 100644 index 000000000..34ec36ea7 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/lazyloadconf/MyTest.java @@ -0,0 +1,97 @@ +package org.tests.lazyloadconf; + +import io.ebean.Ebean; +import io.ebean.Query; +import org.junit.Test; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MyTest { + + @Test + public void testRe() { + + AppConfig globalAppConfig = new AppConfig(); + globalAppConfig.setId(1); + globalAppConfig.setItems(new ArrayList<>()); + AppConfigControl global = new AppConfigControl(); + global.setId(1); + global.setName("global"); + global.setAppConfig(globalAppConfig); + globalAppConfig.getItems().add(global); + Ebean.save(globalAppConfig); + + AppConfig userAppConfig = new AppConfig(); + userAppConfig.setId(2); + userAppConfig.setItems(new ArrayList<>()); + AppConfigControl user = new AppConfigControl(); + user.setId(2); + user.setName("user"); + user.setAppConfig(userAppConfig); + userAppConfig.getItems().add(user); + Ebean.save(userAppConfig); + + AppConfig otherAppConfig = new AppConfig(); + otherAppConfig.setId(3); + otherAppConfig.setItems(new ArrayList<>()); + Ebean.save(otherAppConfig); + + Relationship globalRe = new Relationship(); + globalRe.setId(1); + globalRe.setAppConfig(globalAppConfig); + Ebean.save(globalRe); + + Relationship userRe = new Relationship(); + userRe.setId(2); + userRe.setAppConfig(userAppConfig); + Ebean.save(userRe); + + Relationship otherRe = new Relationship(); + otherRe.setId(3); + otherRe.setAppConfig(otherAppConfig); + Ebean.save(otherRe); + + + +// Start business processing + Query relationshipQuery = Ebean.find(Relationship.class); + List relationshipList = relationshipQuery.where().idIn(1, 2, 3).findList(); + + assertThat(relationshipList.size()).isEqualTo(3); + + Map map = relationshipList.stream() + .map(Relationship::getAppConfig) + .map((ac) -> new AbstractMap.SimpleImmutableEntry<>(ac.getId(), ac)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + +// final List items = DB.find(AppConfig.class) +// .fetch("items") +// .findList(); + + AppConfig g = map.get(1); + AppConfig u = map.get(2); + AppConfig o = map.get(3); + if(!u.getItems().isEmpty()){ + g.setItems(u.getItems()); + } + + assertThat(g.getItems().size()).isEqualTo(1); + +// If this line of code is commented out, this test case will be successfully passed + assertThat(o.getItems().size()).isEqualTo(0); + +// org.junit.ComparisonFailure: +// Expected :1 +// Actual :2 + assertThat(g.getItems().size()).isEqualTo(1); + + assertThat(g.getItems().get(0).getName()).isEqualTo("user"); + } + +} diff --git a/ebean-core/src/test/java/org/tests/lazyloadconf/Relationship.java b/ebean-core/src/test/java/org/tests/lazyloadconf/Relationship.java new file mode 100644 index 000000000..f59e7677a --- /dev/null +++ b/ebean-core/src/test/java/org/tests/lazyloadconf/Relationship.java @@ -0,0 +1,35 @@ +package org.tests.lazyloadconf; + +import io.ebean.annotation.Cache; + +import javax.persistence.*; + +@Entity +@Cache(enableBeanCache = true) +@Table(name="app_config_re") +public class Relationship { + + @Id + private Integer id; + + @ManyToOne + @JoinColumn(name="config_id",referencedColumnName="id") + private AppConfig appConfig; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public AppConfig getAppConfig() { + return appConfig; + } + + public void setAppConfig(AppConfig appConfig) { + this.appConfig = appConfig; + } +} + From 77c6f1cb9f97773abd41b13cc4eef950aef608b3 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 21 May 2021 08:44:04 +1200 Subject: [PATCH 09/72] #2233 Tidy failing test Ebean -> DB and rename test --- ...ava => BeanCollectionLazyLoadingTest.java} | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) rename ebean-core/src/test/java/org/tests/lazyloadconf/{MyTest.java => BeanCollectionLazyLoadingTest.java} (75%) diff --git a/ebean-core/src/test/java/org/tests/lazyloadconf/MyTest.java b/ebean-core/src/test/java/org/tests/lazyloadconf/BeanCollectionLazyLoadingTest.java similarity index 75% rename from ebean-core/src/test/java/org/tests/lazyloadconf/MyTest.java rename to ebean-core/src/test/java/org/tests/lazyloadconf/BeanCollectionLazyLoadingTest.java index 34ec36ea7..b0ef7592b 100644 --- a/ebean-core/src/test/java/org/tests/lazyloadconf/MyTest.java +++ b/ebean-core/src/test/java/org/tests/lazyloadconf/BeanCollectionLazyLoadingTest.java @@ -1,6 +1,6 @@ package org.tests.lazyloadconf; -import io.ebean.Ebean; +import io.ebean.DB; import io.ebean.Query; import org.junit.Test; @@ -12,10 +12,10 @@ import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; -public class MyTest { +public class BeanCollectionLazyLoadingTest { @Test - public void testRe() { + public void test() { AppConfig globalAppConfig = new AppConfig(); globalAppConfig.setId(1); @@ -25,7 +25,7 @@ public class MyTest { global.setName("global"); global.setAppConfig(globalAppConfig); globalAppConfig.getItems().add(global); - Ebean.save(globalAppConfig); + DB.save(globalAppConfig); AppConfig userAppConfig = new AppConfig(); userAppConfig.setId(2); @@ -35,32 +35,31 @@ public class MyTest { user.setName("user"); user.setAppConfig(userAppConfig); userAppConfig.getItems().add(user); - Ebean.save(userAppConfig); + DB.save(userAppConfig); AppConfig otherAppConfig = new AppConfig(); otherAppConfig.setId(3); otherAppConfig.setItems(new ArrayList<>()); - Ebean.save(otherAppConfig); + DB.save(otherAppConfig); Relationship globalRe = new Relationship(); globalRe.setId(1); globalRe.setAppConfig(globalAppConfig); - Ebean.save(globalRe); + DB.save(globalRe); Relationship userRe = new Relationship(); userRe.setId(2); userRe.setAppConfig(userAppConfig); - Ebean.save(userRe); + DB.save(userRe); Relationship otherRe = new Relationship(); otherRe.setId(3); otherRe.setAppConfig(otherAppConfig); - Ebean.save(otherRe); + DB.save(otherRe); - -// Start business processing - Query relationshipQuery = Ebean.find(Relationship.class); + // Start business processing + Query relationshipQuery = DB.find(Relationship.class); List relationshipList = relationshipQuery.where().idIn(1, 2, 3).findList(); assertThat(relationshipList.size()).isEqualTo(3); @@ -70,25 +69,21 @@ public class MyTest { .map((ac) -> new AbstractMap.SimpleImmutableEntry<>(ac.getId(), ac)) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); -// final List items = DB.find(AppConfig.class) -// .fetch("items") -// .findList(); - AppConfig g = map.get(1); AppConfig u = map.get(2); AppConfig o = map.get(3); - if(!u.getItems().isEmpty()){ + if (!u.getItems().isEmpty()) { + // a source of the problem came from invoking lazy loading here + // with the setItems call which is unnecessary due to being a ToMany g.setItems(u.getItems()); } assertThat(g.getItems().size()).isEqualTo(1); -// If this line of code is commented out, this test case will be successfully passed + // If this line of code is commented out, this test case will be successfully passed assertThat(o.getItems().size()).isEqualTo(0); -// org.junit.ComparisonFailure: -// Expected :1 -// Actual :2 + // org.junit.ComparisonFailure: Expected :1 Actual :2 assertThat(g.getItems().size()).isEqualTo(1); assertThat(g.getItems().get(0).getName()).isEqualTo("user"); From 79740accfa688d14988ee6ffe3f21276a8126e33 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 21 May 2021 08:48:42 +1200 Subject: [PATCH 10/72] #2233 Fix with setter on ToMany now not invoking lazy loading - ebean-agent changed to not invoke preGetter on ToMany (just like Id) - EntityBeanIntercept preSetterMany() changed to ensure loading flag set --- .../src/main/java/io/ebean/bean/EntityBeanIntercept.java | 8 +++++++- ebean-bom/pom.xml | 4 ++-- ebean-core/pom.xml | 2 +- ebean-ddl-generator/pom.xml | 2 +- kotlin-querybean-generator/pom.xml | 2 +- 5 files changed, 12 insertions(+), 6 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 12f3fda8f..0c2333026 100644 --- a/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java +++ b/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java @@ -85,6 +85,8 @@ public final class EntityBeanIntercept implements Serializable { */ private static final byte FLAG_CHANGED_PROP = 2; + private static final byte FLAG_CHANGEDLOADED_PROP = 3; + /** * Flags indicating if a property is a dirty embedded bean. Used to distinguish * between an embedded bean being completely overwritten and one of its @@ -569,6 +571,10 @@ public final class EntityBeanIntercept implements Serializable { flags[propertyIndex] |= FLAG_CHANGED_PROP; } + private void setChangeLoaded(int propertyIndex) { + flags[propertyIndex] |= FLAG_CHANGEDLOADED_PROP; + } + /** * Set that an embedded bean has had one of its properties changed. */ @@ -937,7 +943,7 @@ public final class EntityBeanIntercept implements Serializable { if (readOnly) { throw new IllegalStateException("This bean is readOnly"); } - setChangedProperty(propertyIndex); + setChangeLoaded(propertyIndex); } } diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index aaa2b5961..edc2708cf 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -18,8 +18,8 @@ 12.4.0 4.1 7.0 - 12.8.2 - 12.8.2 + 12.8.4 + 12.8.4 diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 13732d513..559d763ac 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -302,7 +302,7 @@ io.ebean ebean-maven-plugin - 12.8.2 + 12.8.4 test diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml index f94924830..0c5089130 100644 --- a/ebean-ddl-generator/pom.xml +++ b/ebean-ddl-generator/pom.xml @@ -76,7 +76,7 @@ io.ebean ebean-maven-plugin - 12.8.2 + 12.8.4 test diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml index a556671bb..99433a038 100644 --- a/kotlin-querybean-generator/pom.xml +++ b/kotlin-querybean-generator/pom.xml @@ -146,7 +146,7 @@ io.ebean ebean-maven-plugin - 12.8.2 + 12.8.4 test From 676bac390ea018b895903bfaee026e57808cf9ee Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 21 May 2021 09:01:45 +1200 Subject: [PATCH 11/72] #2233 Followup change that unsets load buffer from BeanCollection - Clears the buffer after it has been used to load BeanCollection's - Sets the BeanCollection to use the server rather than the Load Buffer just in case there are subsequent calls for loading (which there should not be) --- .../io/ebeaninternal/api/LoadManyRequest.java | 15 +++------------ .../server/loadcontext/DLoadManyContext.java | 9 ++++----- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/LoadManyRequest.java b/ebean-core/src/main/java/io/ebeaninternal/api/LoadManyRequest.java index d7dc2f56a..aeb9ff7dd 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/LoadManyRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/LoadManyRequest.java @@ -71,7 +71,6 @@ public class LoadManyRequest extends LoadRequest { * This for use when lazy loading is invoked on methods such as clear() and removeAll() where it * generally makes sense to only fetch the Id values as the other property information is not * used. - *

*/ private boolean isOnlyIds() { return onlyIds; @@ -91,18 +90,16 @@ public class LoadManyRequest extends LoadRequest { return loadContext.getBatchSize(); } - private List getParentIdList() { - + private List parentIdList(SpiEbeanServer server) { List idList = new ArrayList<>(); - BeanPropertyAssocMany many = getMany(); for (BeanCollection bc : batch) { idList.add(many.getParentId(bc.getOwnerBean())); + bc.setLoader(server); // don't use the load buffer again } if (many.getTargetDescriptor().isPadInExpression()) { BindPadding.padIds(idList); } - return idList; } @@ -111,9 +108,7 @@ public class LoadManyRequest extends LoadRequest { } public SpiQuery createQuery(SpiEbeanServer server) { - BeanPropertyAssocMany many = getMany(); - SpiQuery query = many.newQuery(server); String orderBy = many.getLazyFetchOrderBy(); if (orderBy != null) { @@ -128,7 +123,7 @@ public class LoadManyRequest extends LoadRequest { } query.setLazyLoadForParents(many); - many.addWhereParentIdIn(query, getParentIdList(), loadContext.isUseDocStore()); + many.addWhereParentIdIn(query, parentIdList(server), loadContext.isUseDocStore()); query.setPersistenceContext(loadContext.getPersistenceContext()); String mode = isLazy() ? "+lazy" : "+query"; @@ -146,7 +141,6 @@ public class LoadManyRequest extends LoadRequest { // override to just select the Id values query.select(many.getTargetIdProperty()); } - return query; } @@ -154,10 +148,8 @@ public class LoadManyRequest extends LoadRequest { * After the query execution check for empty collections and load L2 cache if desired. */ public void postLoad() { - BeanDescriptor desc = loadContext.getBeanDescriptor(); BeanPropertyAssocMany many = getMany(); - // check for BeanCollection's that where never processed // in the +query or +lazy load due to no rows (predicates) for (BeanCollection bc : batch) { @@ -172,6 +164,5 @@ public class LoadManyRequest extends LoadRequest { desc.cacheManyPropPut(many, bc, parentId); } } - } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java index 5035f0802..5d2b781c7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java @@ -201,7 +201,6 @@ class DLoadManyContext extends DLoadBaseContext implements LoadManyContext { @Override public void loadMany(BeanCollection bc, boolean onlyIds) { - lock.lock(); try { boolean useCache = !onlyIds && context.hitCache && context.property.isUseCache(); @@ -215,6 +214,7 @@ class DLoadManyContext extends DLoadBaseContext implements LoadManyContext { // find it using instance equality - avoiding equals() and potential deadlock issue if (list.get(i) == bc) { list.remove(i); + bc.setLoader(context.parent.getEbeanServer()); return; } } @@ -222,10 +222,9 @@ class DLoadManyContext extends DLoadBaseContext implements LoadManyContext { } } - // Should reduce the list by checking each beanCollection in the L2 first before executing the query - - LoadManyRequest req = new LoadManyRequest(this, onlyIds, useCache); - context.parent.getEbeanServer().loadMany(req); + context.parent.getEbeanServer().loadMany(new LoadManyRequest(this, onlyIds, useCache)); + // clear the buffer as all entries have been loaded + list.clear(); } finally { lock.unlock(); } From 643b0499c44c9ec9b2215fdb83349d262ead8208 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 21 May 2021 16:09:15 +1200 Subject: [PATCH 12/72] #2233 Add ebean-version.mf so ebean-agent can sniff the internal version This allows the ebean-agent to support the older behaviour. Devs using the latest IntelliJ ebean plugin will get the old behaviour unless they bump ebean-core to 12.9 or later. --- ebean-core/src/main/resources/META-INF/ebean-version.mf | 1 + 1 file changed, 1 insertion(+) create mode 100644 ebean-core/src/main/resources/META-INF/ebean-version.mf diff --git a/ebean-core/src/main/resources/META-INF/ebean-version.mf b/ebean-core/src/main/resources/META-INF/ebean-version.mf new file mode 100644 index 000000000..6bcacf1d6 --- /dev/null +++ b/ebean-core/src/main/resources/META-INF/ebean-version.mf @@ -0,0 +1 @@ +ebean-version: 129 From 0da86a82ae74dd2f90c045fb1c842cf1d18ecae4 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 21 May 2021 17:14:49 +1200 Subject: [PATCH 13/72] #2233 Bump to ebean-agent 12.9.0 --- ebean-bom/pom.xml | 4 ++-- ebean-core/pom.xml | 2 +- ebean-ddl-generator/pom.xml | 2 +- kotlin-querybean-generator/pom.xml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index edc2708cf..6109effa9 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -18,8 +18,8 @@ 12.4.0 4.1 7.0 - 12.8.4 - 12.8.4 + 12.9.0 + 12.9.0 diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 559d763ac..624e91c91 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -302,7 +302,7 @@ io.ebean ebean-maven-plugin - 12.8.4 + 12.9.0 test diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml index 0c5089130..d063c222c 100644 --- a/ebean-ddl-generator/pom.xml +++ b/ebean-ddl-generator/pom.xml @@ -76,7 +76,7 @@ io.ebean ebean-maven-plugin - 12.8.4 + 12.9.0 test diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml index 99433a038..e7967307e 100644 --- a/kotlin-querybean-generator/pom.xml +++ b/kotlin-querybean-generator/pom.xml @@ -146,7 +146,7 @@ io.ebean ebean-maven-plugin - 12.8.4 + 12.9.0 test From 2a6108087ca01e5ba8ae192c2912cfe1c43ead83 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 21 May 2021 17:18:30 +1200 Subject: [PATCH 14/72] Bump version to 12.9.0-SNAPSHOT --- ebean-api/pom.xml | 2 +- ebean-autotune/pom.xml | 4 ++-- ebean-bom/pom.xml | 30 +++++++++++++++--------------- ebean-core-type/pom.xml | 4 ++-- ebean-core/pom.xml | 8 ++++---- ebean-ddl-generator/pom.xml | 6 +++--- ebean-externalmapping-api/pom.xml | 2 +- ebean-externalmapping-xml/pom.xml | 8 ++++---- ebean-postgis/pom.xml | 6 +++--- ebean-querybean/pom.xml | 10 +++++----- ebean-redis/pom.xml | 12 ++++++------ ebean-test/pom.xml | 6 +++--- ebean/pom.xml | 8 ++++---- kotlin-querybean-generator/pom.xml | 8 ++++---- pom.xml | 2 +- querybean-generator/pom.xml | 2 +- 16 files changed, 59 insertions(+), 59 deletions(-) diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index 380438cc2..10e919049 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT ebean api diff --git a/ebean-autotune/pom.xml b/ebean-autotune/pom.xml index 590680c8a..1c7d48b4e 100644 --- a/ebean-autotune/pom.xml +++ b/ebean-autotune/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT @@ -26,7 +26,7 @@ io.ebean ebean-core - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT provided diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index 6109effa9..dc1478d34 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT ebean bom @@ -81,88 +81,88 @@ io.ebean ebean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT io.ebean ebean-api - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT io.ebean ebean-core - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT io.ebean ebean-core-type - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT io.ebean ebean-ddl-generator - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT io.ebean ebean-externalmapping-api - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT io.ebean ebean-externalmapping-xml - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT io.ebean ebean-autotune - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT io.ebean ebean-querybean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT io.ebean querybean-generator - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT provided io.ebean kotlin-querybean-generator - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT provided io.ebean ebean-test - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT test io.ebean ebean-postgis - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT io.ebean ebean-redis - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT diff --git a/ebean-core-type/pom.xml b/ebean-core-type/pom.xml index 04497471c..e6589d839 100644 --- a/ebean-core-type/pom.xml +++ b/ebean-core-type/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT ebean-core-type @@ -16,7 +16,7 @@ io.ebean ebean-api - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 624e91c91..16e400f91 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -3,7 +3,7 @@ ebean-parent io.ebean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT ebean-core @@ -87,19 +87,19 @@ io.ebean ebean-api - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT io.ebean ebean-core-type - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT io.ebean ebean-externalmapping-api - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml index d063c222c..6b16e9b2a 100644 --- a/ebean-ddl-generator/pom.xml +++ b/ebean-ddl-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT ebean ddl generation @@ -28,14 +28,14 @@ io.ebean ebean-core-type - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT provided io.ebean ebean-core - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT provided diff --git a/ebean-externalmapping-api/pom.xml b/ebean-externalmapping-api/pom.xml index 079eebec9..57372b097 100644 --- a/ebean-externalmapping-api/pom.xml +++ b/ebean-externalmapping-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT ebean external mapping api diff --git a/ebean-externalmapping-xml/pom.xml b/ebean-externalmapping-xml/pom.xml index bcf9f10ff..0fb961ac4 100644 --- a/ebean-externalmapping-xml/pom.xml +++ b/ebean-externalmapping-xml/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT @@ -33,7 +33,7 @@ io.ebean ebean-externalmapping-api - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT @@ -59,14 +59,14 @@ io.ebean ebean-core - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT test io.ebean ebean-ddl-generator - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT test diff --git a/ebean-postgis/pom.xml b/ebean-postgis/pom.xml index 4e1b6063b..e000db688 100644 --- a/ebean-postgis/pom.xml +++ b/ebean-postgis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT ebean postgis @@ -23,7 +23,7 @@ io.ebean ebean-core - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT provided @@ -74,7 +74,7 @@ io.ebean ebean-test - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT test diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml index ff6dd8754..57b01e349 100644 --- a/ebean-querybean/pom.xml +++ b/ebean-querybean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT ebean querybean @@ -17,7 +17,7 @@ io.ebean ebean-core - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT provided @@ -57,21 +57,21 @@ io.ebean ebean-ddl-generator - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT test io.ebean querybean-generator - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT test io.ebean ebean-test - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT test diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml index 573fbcf3b..c500e7508 100644 --- a/ebean-redis/pom.xml +++ b/ebean-redis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT ebean-redis @@ -22,35 +22,35 @@ io.ebean ebean-api - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT provided io.ebean ebean-core - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT provided io.ebean ebean-querybean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT test io.ebean querybean-generator - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT test io.ebean ebean-test - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT test diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml index c3ed1f57f..94cba52d6 100644 --- a/ebean-test/pom.xml +++ b/ebean-test/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT ebean test @@ -29,14 +29,14 @@ io.ebean ebean-core - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT provided io.ebean ebean-ddl-generator - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT diff --git a/ebean/pom.xml b/ebean/pom.xml index ba312953f..d284c0bda 100644 --- a/ebean/pom.xml +++ b/ebean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT ebean composite @@ -22,20 +22,20 @@ io.ebean ebean-api - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT io.ebean ebean-core - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT io.ebean ebean-querybean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml index e7967307e..a475fdcc4 100644 --- a/kotlin-querybean-generator/pom.xml +++ b/kotlin-querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT kotlin querybean generator @@ -29,7 +29,7 @@ io.ebean ebean-querybean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT test @@ -43,7 +43,7 @@ io.ebean ebean-core - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT test @@ -64,7 +64,7 @@ io.ebean ebean-ddl-generator - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT test diff --git a/pom.xml b/pom.xml index fb82113fa..14f2e3f08 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.ebean ebean-parent - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT pom ebean parent diff --git a/querybean-generator/pom.xml b/querybean-generator/pom.xml index 1ea632096..d5d769ac4 100644 --- a/querybean-generator/pom.xml +++ b/querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.8.4-SNAPSHOT + 12.9.0-SNAPSHOT querybean generator From 3c4d600187c2443016e56635d0b8afb7a6a1db39 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Sun, 23 May 2021 17:17:34 +1200 Subject: [PATCH 15/72] #2223 #2224 Support BeanMap modification via entrySet() and keySet() --- .../main/java/io/ebean/common/BeanMap.java | 20 +- .../io/ebean/common/ModifyCollection.java | 2 +- .../java/io/ebean/common/ModifyEntrySet.java | 131 +++++++++ .../java/io/ebean/common/ModifyKeySet.java | 126 +++++++++ .../main/java/io/ebean/common/ModifySet.java | 24 -- .../java/io/ebean/common/BeanMapTest.java | 265 +++++++++++++++++- .../model/map/BeanMapOrphanRemovalTest.java | 50 ++++ .../test/java/org/tests/model/map/MpUser.java | 12 +- 8 files changed, 572 insertions(+), 58 deletions(-) create mode 100644 ebean-api/src/main/java/io/ebean/common/ModifyEntrySet.java create mode 100644 ebean-api/src/main/java/io/ebean/common/ModifyKeySet.java delete mode 100644 ebean-api/src/main/java/io/ebean/common/ModifySet.java create mode 100644 ebean-core/src/test/java/org/tests/model/map/BeanMapOrphanRemovalTest.java diff --git a/ebean-api/src/main/java/io/ebean/common/BeanMap.java b/ebean-api/src/main/java/io/ebean/common/BeanMap.java index 2b9ef350d..643febc14 100644 --- a/ebean-api/src/main/java/io/ebean/common/BeanMap.java +++ b/ebean-api/src/main/java/io/ebean/common/BeanMap.java @@ -175,10 +175,6 @@ public final class BeanMap extends AbstractBeanCollection implements Ma /** * Returns the map entrySet. - *

- * This is because the key values may need to be set against the details (so - * they don't need to be set twice). - *

*/ @Override public Collection getActualEntries() { @@ -242,17 +238,12 @@ public final class BeanMap extends AbstractBeanCollection implements Ma } @Override - @SuppressWarnings({"unchecked"}) public Set> entrySet() { init(); if (isReadOnly()) { return Collections.unmodifiableSet(map.entrySet()); } - if (modifyListening) { - Set> s = map.entrySet(); - return new ModifySet(this, s); - } - return map.entrySet(); + return modifyListening ? new ModifyEntrySet<>(this, map.entrySet()) : map.entrySet(); } @Override @@ -273,8 +264,7 @@ public final class BeanMap extends AbstractBeanCollection implements Ma if (isReadOnly()) { return Collections.unmodifiableSet(map.keySet()); } - // we don't really care about modifications to the ketSet? - return map.keySet(); + return modifyListening ? new ModifyKeySet<>(this, map.keySet()) : map.keySet(); } @Override @@ -345,11 +335,7 @@ public final class BeanMap extends AbstractBeanCollection implements Ma if (isReadOnly()) { return Collections.unmodifiableCollection(map.values()); } - if (modifyListening) { - Collection c = map.values(); - return new ModifyCollection<>(this, c); - } - return map.values(); + return modifyListening ? new ModifyCollection<>(this, map.values()) : map.values(); } @Override diff --git a/ebean-api/src/main/java/io/ebean/common/ModifyCollection.java b/ebean-api/src/main/java/io/ebean/common/ModifyCollection.java index 05b203a2f..48f703ba8 100644 --- a/ebean-api/src/main/java/io/ebean/common/ModifyCollection.java +++ b/ebean-api/src/main/java/io/ebean/common/ModifyCollection.java @@ -25,7 +25,7 @@ class ModifyCollection implements Collection { * The owner is notified of the additions and removals. *

*/ - public ModifyCollection(BeanCollection owner, Collection c) { + ModifyCollection(BeanCollection owner, Collection c) { this.owner = owner; this.c = c; } diff --git a/ebean-api/src/main/java/io/ebean/common/ModifyEntrySet.java b/ebean-api/src/main/java/io/ebean/common/ModifyEntrySet.java new file mode 100644 index 000000000..174863a74 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/common/ModifyEntrySet.java @@ -0,0 +1,131 @@ +package io.ebean.common; + +import java.util.*; + +/** + * Handles the Entry Set for BeanMap. + */ +class ModifyEntrySet implements Set> { + + private final BeanMap owner; + private final Set> entrySet; + + ModifyEntrySet(BeanMap owner, Set> entrySet) { + this.owner = owner; + this.entrySet = entrySet; + } + + @Override + public int size() { + return entrySet.size(); + } + + @Override + public boolean isEmpty() { + return entrySet.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return entrySet.contains(o); + } + + @Override + public Object[] toArray() { + return entrySet.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return entrySet.toArray(a); + } + + @Override + public boolean containsAll(Collection entries) { + return entrySet.containsAll(entries); + } + + @Override + public void clear() { + owner.clear(); + } + + @Override + public boolean add(Map.Entry entry) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection> c) { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("rawtypes") + @Override + public boolean remove(Object o) { + if (o instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) o; + final E val = owner.get(entry.getKey()); + if (Objects.equals(val, entry.getValue())) { + owner.remove(entry.getKey()); + return true; + } + } + return false; + } + + @Override + public boolean retainAll(Collection entries) { + boolean modified = false; + final Iterator> it = iterator(); + while (it.hasNext()) { + if (!entries.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + @Override + public boolean removeAll(Collection entries) { + boolean modified = false; + for (Object entry : entries) { + modified |= remove(entry); + } + return modified; + } + + @Override + public Iterator> iterator() { + return new EntrySetIterator(new ArrayList<>(entrySet).iterator()); + } + + class EntrySetIterator implements Iterator> { + + private final Iterator> iterator; + private Map.Entry entry; + + EntrySetIterator(Iterator> iterator) { + this.iterator = iterator; + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public Map.Entry next() { + entry = iterator.next(); + return entry; + } + + @Override + public void remove() { + owner.remove(entry.getKey()); + iterator.remove(); + } + } + +} diff --git a/ebean-api/src/main/java/io/ebean/common/ModifyKeySet.java b/ebean-api/src/main/java/io/ebean/common/ModifyKeySet.java new file mode 100644 index 000000000..e9f829b9b --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/common/ModifyKeySet.java @@ -0,0 +1,126 @@ +package io.ebean.common; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +/** + * Handle the Key Set for BeanMap. + */ +class ModifyKeySet implements Set { + + private final Set keySet; + private final BeanMap owner; + + ModifyKeySet(BeanMap owner, Set keySet) { + this.owner = owner; + this.keySet = keySet; + } + + @Override + public int size() { + return keySet.size(); + } + + @Override + public boolean isEmpty() { + return keySet.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return keySet.contains(o); + } + + @Override + public Object[] toArray() { + return keySet.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return keySet.toArray(a); + } + + @Override + public boolean add(E key) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection keys) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + return owner.remove(o) != null; + } + + @Override + public boolean containsAll(Collection keys) { + return keySet.containsAll(keys); + } + + @Override + public void clear() { + owner.clear(); + } + + @Override + public Iterator iterator() { + return new KeySetIterator<>(new ArrayList<>(keySet).iterator()); + } + + @Override + public boolean retainAll(Collection keys) { + return keysMatch(keys, false); + } + + @Override + public boolean removeAll(Collection keys) { + return keysMatch(keys, true); + } + + private boolean keysMatch(Collection keys, boolean containsMatch) { + boolean changed = false; + final Iterator iterator = iterator(); + while (iterator.hasNext()) { + final E key = iterator.next(); + if (keys.contains(key) == containsMatch) { + iterator.remove(); + changed = true; + } + } + return changed; + } + + + class KeySetIterator implements Iterator { + + private final Iterator iterator; + private K key; + + KeySetIterator(Iterator iterator) { + this.iterator = iterator; + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public K next() { + key = iterator.next(); + return key; + } + + @Override + public void remove() { + owner.remove(key); + iterator.remove(); + } + } +} diff --git a/ebean-api/src/main/java/io/ebean/common/ModifySet.java b/ebean-api/src/main/java/io/ebean/common/ModifySet.java deleted file mode 100644 index 1a0a2e1bf..000000000 --- a/ebean-api/src/main/java/io/ebean/common/ModifySet.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.ebean.common; - -import io.ebean.bean.BeanCollection; - -import java.util.Set; - -/** - * Wraps a Set for the purposes of notifying removals and additions to the - * BeanCollection owner. - *

- * This is required for persisting ManyToMany objects. Additions and removals - * become inserts and deletes to the intersection table. - *

- */ -class ModifySet extends ModifyCollection implements Set { - - /** - * Create with an Owner that is notified of any additions or deletions. - */ - public ModifySet(BeanCollection owner, Set s) { - super(owner, s); - } - -} diff --git a/ebean-core/src/test/java/io/ebean/common/BeanMapTest.java b/ebean-core/src/test/java/io/ebean/common/BeanMapTest.java index 08a57e05a..003539a72 100644 --- a/ebean-core/src/test/java/io/ebean/common/BeanMapTest.java +++ b/ebean-core/src/test/java/io/ebean/common/BeanMapTest.java @@ -2,18 +2,21 @@ package io.ebean.common; import io.ebean.bean.BeanCollection; import org.junit.Test; +import org.tests.model.basic.EBasic; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.*; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; public class BeanMapTest { - Object object1 = new Object(); - Object object2 = new Object(); - Object object3 = new Object(); + private final EBasic object1 = new EBasic("o1"); + private final EBasic object2 = new EBasic("o2"); + private final EBasic object3 = new EBasic("o3"); + private final EBasic object4 = new EBasic("o4"); + private final EBasic object5 = new EBasic("o5"); private Map all() { Map all = new LinkedHashMap<>(); @@ -174,9 +177,7 @@ public class BeanMapTest { @Test public void testClear_given_someBeansInAdditions() throws Exception { - BeanMap map = new BeanMap<>(); - map.put("1", object1); - map.setModifyListening(BeanCollection.ModifyListenMode.ALL); + BeanMap map = newModifyListeningMap(); map.put("2", object2); map.put("3", object3); @@ -188,4 +189,252 @@ public class BeanMapTest { assertThat(map.getModifyAdditions()).isEmpty(); } + @Test(expected = UnsupportedOperationException.class) + public void keySet_add_whenModifyListening() { + BeanMap map = newModifyListeningMap(); + map.keySet().add("3"); + } + + @Test(expected = UnsupportedOperationException.class) + public void keySet_add() { + BeanMap map = new BeanMap<>(); + map.keySet().add("3"); + } + + @Test(expected = UnsupportedOperationException.class) + public void keySet_addAll_whenModifyListening() { + BeanMap map = newModifyListeningMap(); + map.keySet().addAll(asList("3", "4")); + } + + @Test(expected = UnsupportedOperationException.class) + public void keySet_addAll() { + BeanMap map = new BeanMap<>(); + map.keySet().addAll(asList("3", "4")); + } + + @Test + public void keySet_remove() { + + BeanMap map = new BeanMap<>(); + map.put("1", object1); + map.put("2", object2); + map.setModifyListening(BeanCollection.ModifyListenMode.ALL); + + final Set keySet = map.keySet(); + keySet.remove("1"); + + assertThat(keySet.contains("1")).isFalse(); + assertThat(map).doesNotContainKeys("1"); + assertThat(map.get("1")).isNull(); + + assertThat(map.getModifyRemovals()).containsExactly(object1); + } + + @Test + public void keySet_clear() { + + BeanMap map = new BeanMap<>(); + map.put("1", object1); + map.put("2", object2); + map.setModifyListening(BeanCollection.ModifyListenMode.ALL); + + final Set keySet = map.keySet(); + keySet.clear(); + + assertThat(map).isEmpty(); + assertThat(keySet).isEmpty(); + + assertThat(map.getModifyRemovals()).containsExactly(object1, object2); + } + + @Test + public void keySet_iterator_remove() { + + BeanMap map = new BeanMap<>(); + map.put("1", object1); + map.put("2", object2); + map.put("3", object3); + map.setModifyListening(BeanCollection.ModifyListenMode.ALL); + + final Set keySet = map.keySet(); + final Iterator iterator = keySet.iterator(); + while (iterator.hasNext()) { + final String key = iterator.next(); + if (key.equals("2")) { + iterator.remove(); + } + } + + assertThat(map).hasSize(2); + assertThat(keySet).hasSize(2); + assertThat(keySet).containsExactly("1", "3"); + assertThat(map).containsKeys("1", "3"); + + assertThat(map.getModifyRemovals()).containsExactly(object2); + } + + @Test + public void keySet_removeAll() { + + BeanMap map = new BeanMap<>(); + map.put("1", object1); + map.put("2", object2); + map.put("3", object3); + map.put("4", object4); + map.put("5", object5); + map.setModifyListening(BeanCollection.ModifyListenMode.ALL); + + final Set keySet = map.keySet(); + final boolean changed = keySet.removeAll(asList("2", "3", "5")); + + assertThat(changed).isTrue(); + assertThat(map).hasSize(2); + assertThat(keySet).hasSize(2); + assertThat(keySet).containsExactly("1", "4"); + assertThat(map).containsKeys("1", "4"); + + assertThat(map.getModifyRemovals()).containsExactly(object2, object3, object5); + } + + + @Test + public void keySet_retainAll() { + + BeanMap map = new BeanMap<>(); + map.put("1", object1); + map.put("2", object2); + map.put("3", object3); + map.put("4", object4); + map.put("5", object5); + map.setModifyListening(BeanCollection.ModifyListenMode.ALL); + + final Set keySet = map.keySet(); + final boolean changed = keySet.retainAll(asList("2", "3", "5")); + + assertThat(changed).isTrue(); + assertThat(map).hasSize(3); + assertThat(keySet).hasSize(3); + assertThat(keySet).containsExactly("2", "3", "5"); + assertThat(map).containsKeys("2", "3", "5"); + + assertThat(map.getModifyRemovals()).containsExactly(object1, object4); + } + + @Test(expected = UnsupportedOperationException.class) + public void values_add() { + BeanMap map = new BeanMap<>(); + map.values().add(object3); + } + + @Test(expected = UnsupportedOperationException.class) + public void values_addAll() { + BeanMap map = new BeanMap<>(); + map.values().addAll(asList(object3, object5)); + } + + @Test(expected = UnsupportedOperationException.class) + public void entrySet_add() { + newModifyListeningMap() + .entrySet() + .add(new AbstractMap.SimpleEntry<>("3", object3)); + } + + @Test + public void entrySet_clear() { + final BeanMap map = newModifyListeningMap(); + final Set> entries = map.entrySet(); + entries.clear(); + + assertThat(entries).isEmpty(); + assertThat(map).isEmpty(); + assertThat(map.getModifyRemovals()).containsExactly(object1); + } + + @Test + public void entrySet_remove() { + final BeanMap map = newModifyListeningMap5(); + final Set> entries = map.entrySet(); + + assertThat(map).hasSize(5); + + final boolean existed1 = entries.remove(new AbstractMap.SimpleEntry<>("1", object1)); + assertThat(existed1).isTrue(); + + final boolean existed22 = entries.remove(new AbstractMap.SimpleEntry<>("22", object1)); + assertThat(existed22).isFalse(); + + assertThat(map).hasSize(4); + assertThat(map.getModifyRemovals()).containsExactly(object1); + } + + @Test + public void entrySet_remove_whenNotEqualValue() { + final BeanMap map = newModifyListeningMap5(); + final Set> entries = map.entrySet(); + + assertThat(map).hasSize(5); + + final boolean modified = entries.remove(new AbstractMap.SimpleEntry<>("1", object2)); + assertThat(modified).isFalse(); + + assertThat(map).hasSize(5); + assertThat(map.getModifyRemovals()).isNull(); + } + + @Test + public void entrySet_iterator_remove() { + final BeanMap map = newModifyListeningMap5(); + final Set> entries = map.entrySet(); + final Iterator> iterator = entries.iterator(); + while (iterator.hasNext()) { + final Map.Entry entry = iterator.next(); + if (entry.getKey().equals("2") || entry.getKey().equals("5")) { + iterator.remove(); + } + } + assertThat(map).hasSize(3); + assertThat(entries).hasSize(3); + assertThat(map.getModifyRemovals()).containsExactly(object2, object5); + } + + @Test + public void entrySet_removeAll() { + final BeanMap map = newModifyListeningMap5(); + final Set> entries = map.entrySet(); + + entries.removeAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4))); + assertThat(map).hasSize(3); + assertThat(entries).hasSize(3); + assertThat(map.getModifyRemovals()).containsExactly(object1, object4); + } + + @Test + public void entrySet_retainAll() { + final BeanMap map = newModifyListeningMap5(); + final Set> entries = map.entrySet(); + + entries.retainAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4))); + assertThat(map).hasSize(2); + assertThat(entries).hasSize(2); + assertThat(map.getModifyRemovals()).containsExactly(object2, object3, object5); + } + + private BeanMap newModifyListeningMap() { + BeanMap map = new BeanMap<>(); + map.put("1", object1); + map.setModifyListening(BeanCollection.ModifyListenMode.ALL); + return map; + } + + private BeanMap newModifyListeningMap5() { + BeanMap map = new BeanMap<>(); + map.put("1", object1); + map.put("2", object2); + map.put("3", object3); + map.put("4", object4); + map.put("5", object5); + map.setModifyListening(BeanCollection.ModifyListenMode.ALL); + return map; + } } diff --git a/ebean-core/src/test/java/org/tests/model/map/BeanMapOrphanRemovalTest.java b/ebean-core/src/test/java/org/tests/model/map/BeanMapOrphanRemovalTest.java new file mode 100644 index 000000000..48288e7a3 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/map/BeanMapOrphanRemovalTest.java @@ -0,0 +1,50 @@ +package org.tests.model.map; + +import io.ebean.DB; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Map; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class BeanMapOrphanRemovalTest { + + @Test + public void keySet_retainAll() { + + MpUser user = new MpUser(); + user.setName("u1"); + addRoles(user, "r1", "r2", "r3", "r4"); + DB.save(user); + + final MpUser user1 = DB.find(MpUser.class, user.getId()); + final Map roles = user1.getRoles(); + assertThat(roles).hasSize(4); + + + final Set keySet = roles.keySet(); + keySet.retainAll(Arrays.asList("r2", "r3")); + + DB.save(user1); + + final MpUser user2 = DB.find(MpUser.class, user.getId()); + final Map roles2 = user2.getRoles(); + assertThat(roles2).hasSize(2); + } + + private void addRoles(MpUser user, String... roles){ + for (String code : roles) { + MpRole role = newRole(code); + user.getRoles().put(role.getCode(), role); + } + } + + private MpRole newRole(String code) { + MpRole role = new MpRole(); + role.setCode(code); + return role; + } +} diff --git a/ebean-core/src/test/java/org/tests/model/map/MpUser.java b/ebean-core/src/test/java/org/tests/model/map/MpUser.java index 100e1e6a7..94edddb19 100644 --- a/ebean-core/src/test/java/org/tests/model/map/MpUser.java +++ b/ebean-core/src/test/java/org/tests/model/map/MpUser.java @@ -1,11 +1,7 @@ package org.tests.model.map; -import javax.persistence.CascadeType; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.MapKey; -import javax.persistence.OneToMany; -import java.util.HashMap; +import javax.persistence.*; +import java.util.LinkedHashMap; import java.util.Map; @Entity @@ -16,9 +12,9 @@ public class MpUser { private String name; - @OneToMany(cascade = CascadeType.ALL) + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @MapKey(name = "code") - public Map roles = new HashMap<>(); + private Map roles = new LinkedHashMap<>(); public Long getId() { return id; From 2158925d3ca77b9610c3ae03c244594d74ed6639 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Sun, 23 May 2021 21:28:11 +1200 Subject: [PATCH 16/72] #2202 - kotlin-maven-plugin issue with JDK 16 InaccessibleObjectException: Unable to make protected void java.util.ResourceBundle.setParent(java.util.ResourceBundle) accessible: module java.base does not "opens java.util" to unnamed module @62732be7 --- kotlin-querybean-generator/pom.xml | 75 +++++++++++++++--------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml index a475fdcc4..917d4275a 100644 --- a/kotlin-querybean-generator/pom.xml +++ b/kotlin-querybean-generator/pom.xml @@ -12,7 +12,7 @@ kotlin-querybean-generator - 1.4.31 + 1.5.0 @@ -81,43 +81,42 @@ src/test/kotlin - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + test-compile + test-compile + + test-compile + + + + test-kapt + + test-kapt + + + + src/test/kotlin + + + + io.ebean + kotlin-querybean-generator + 12.8.2 + + + + + + + 1.8 + + + org.apache.maven.plugins maven-compiler-plugin From 01bce5cfa622af053545ce15f885d1a25a2e0337 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Sun, 23 May 2021 22:20:40 +1200 Subject: [PATCH 17/72] #2226 - Automatically determining join columns and ignoring PrimaryKeyJoinColumn.name Id warning when I want to use Id as a foreign key Improve the message and dropping to INFO level. --- .../server/deploy/parse/AnnotationAssocOnes.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java index d313ce925..b5929d6a0 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java @@ -54,7 +54,6 @@ public class AnnotationAssocOnes extends AnnotationAssoc { } private void readAssocOne(DeployBeanPropertyAssocOne prop) { - ManyToOne manyToOne = get(prop, ManyToOne.class); if (manyToOne != null) { readManyToOne(manyToOne, prop); @@ -183,7 +182,6 @@ public class AnnotationAssocOnes extends AnnotationAssoc { } private void readManyToOne(ManyToOne propAnn, DeployBeanPropertyAssocOne beanProp) { - setCascadeTypes(propAnn.cascade(), beanProp.getCascadeInfo()); setTargetType(propAnn.targetEntity(), beanProp); setBeanTable(beanProp); @@ -194,7 +192,6 @@ public class AnnotationAssocOnes extends AnnotationAssoc { } private void readOneToOne(OneToOne propAnn, DeployBeanPropertyAssocOne prop) { - prop.setOneToOne(); prop.setDbInsertable(true); prop.setDbUpdateable(true); @@ -223,21 +220,19 @@ public class AnnotationAssocOnes extends AnnotationAssoc { } private void readPrimaryKeyJoin(PrimaryKeyJoinColumn primaryKeyJoin, DeployBeanPropertyAssocOne prop) { - if (!prop.isOneToOne()) { throw new IllegalStateException("Expecting property " + prop.getFullBeanName() + " with PrimaryKeyJoinColumn to be a OneToOne?"); } prop.setPrimaryKeyJoin(true); if (!primaryKeyJoin.name().isEmpty()) { - log.warn("Automatically determining join columns and ignoring PrimaryKeyJoinColumn.name {} on {}", primaryKeyJoin.name(), prop.getFullBeanName()); + log.info("Automatically determining join columns for @PrimaryKeyJoinColumn - ignoring PrimaryKeyJoinColumn.name attribute [{}] on {}", primaryKeyJoin.name(), prop.getFullBeanName()); } if (!primaryKeyJoin.referencedColumnName().isEmpty()) { - log.warn("Automatically determining join columns and Ignoring PrimaryKeyJoinColumn.referencedColumnName {} on {}", primaryKeyJoin.referencedColumnName(), prop.getFullBeanName()); + log.info("Automatically determining join columns for @PrimaryKeyJoinColumn - Ignoring PrimaryKeyJoinColumn.referencedColumnName attribute [{}] on {}", primaryKeyJoin.referencedColumnName(), prop.getFullBeanName()); } BeanTable baseBeanTable = factory.getBeanTable(info.getDescriptor().getBeanType()); - String localPrimaryKey = baseBeanTable.getIdColumn(); String foreignColumn = getBeanTable(prop).getIdColumn(); @@ -245,7 +240,6 @@ public class AnnotationAssocOnes extends AnnotationAssoc { } private void readEmbedded(DeployBeanPropertyAssocOne prop, Embedded embedded) { - if (descriptor.isDocStoreOnly() && prop.getDocStoreDoc() == null) { prop.setDocStoreEmbedded(""); } @@ -257,7 +251,6 @@ public class AnnotationAssocOnes extends AnnotationAssoc { } catch (NoSuchMethodError e) { // using standard JPA API without prefix option, maybe in EE container } - readEmbeddedAttributeOverrides(prop); } From 4c317f11ae097acd5414bf7414b2e592b1ee3d68 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Sun, 23 May 2021 22:21:05 +1200 Subject: [PATCH 18/72] Add additional test for PrimaryKeyJoinColumn --- .../tests/model/onetoone/calcd/CalcDData.java | 46 +++++++++++++++++++ .../model/onetoone/calcd/CalcDInput.java | 42 +++++++++++++++++ .../TestOneToOnePrimaryKeyJoinMapping.java | 26 +++++++++++ 3 files changed, 114 insertions(+) create mode 100644 ebean-core/src/test/java/org/tests/model/onetoone/calcd/CalcDData.java create mode 100644 ebean-core/src/test/java/org/tests/model/onetoone/calcd/CalcDInput.java create mode 100644 ebean-core/src/test/java/org/tests/model/onetoone/calcd/TestOneToOnePrimaryKeyJoinMapping.java diff --git a/ebean-core/src/test/java/org/tests/model/onetoone/calcd/CalcDData.java b/ebean-core/src/test/java/org/tests/model/onetoone/calcd/CalcDData.java new file mode 100644 index 000000000..eef657be5 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/onetoone/calcd/CalcDData.java @@ -0,0 +1,46 @@ +package org.tests.model.onetoone.calcd; + +import io.ebean.Model; +import io.ebean.annotation.ConstraintMode; +import io.ebean.annotation.DbForeignKey; + +import javax.persistence.*; + +@Entity +@Table(name = "calcd_data") +public class CalcDData extends Model { + + @Id + private Integer id; + + @OneToOne(optional = false, cascade = CascadeType.ALL) + @DbForeignKey(onDelete = ConstraintMode.CASCADE) + @PrimaryKeyJoinColumn//(name = "Id", referencedColumnName = "Id") + private CalcDInput input; + + private final String name; + + public CalcDData(String name) { + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public CalcDInput getInput() { + return input; + } + + public void setInput(CalcDInput input) { + this.input = input; + } + + public String getName() { + return name; + } +} diff --git a/ebean-core/src/test/java/org/tests/model/onetoone/calcd/CalcDInput.java b/ebean-core/src/test/java/org/tests/model/onetoone/calcd/CalcDInput.java new file mode 100644 index 000000000..00704eb27 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/onetoone/calcd/CalcDInput.java @@ -0,0 +1,42 @@ +package org.tests.model.onetoone.calcd; + +import io.ebean.Model; + +import javax.persistence.*; + +@Entity +@Table(name = "calcd_input") +public class CalcDInput extends Model { + + @Id + private Integer id; + + @OneToOne(optional = false, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy = "input") + private CalcDData data; + + private final String name; + + public CalcDInput(String name) { + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public CalcDData getData() { + return data; + } + + public void setData(CalcDData data) { + this.data = data; + } +} diff --git a/ebean-core/src/test/java/org/tests/model/onetoone/calcd/TestOneToOnePrimaryKeyJoinMapping.java b/ebean-core/src/test/java/org/tests/model/onetoone/calcd/TestOneToOnePrimaryKeyJoinMapping.java new file mode 100644 index 000000000..9e1aa68f0 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/onetoone/calcd/TestOneToOnePrimaryKeyJoinMapping.java @@ -0,0 +1,26 @@ +package org.tests.model.onetoone.calcd; + +import io.ebean.DB; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestOneToOnePrimaryKeyJoinMapping { + + @Test + public void test() { + + CalcDInput inputs = new CalcDInput("input0"); + + CalcDData data = new CalcDData("data0"); + inputs.setData(data); + inputs.save(); + + CalcDInput found = DB.find(CalcDInput.class, inputs.getId()); + + assertThat(found).isNotNull(); + assertThat(found.getName()).isEqualTo("input0"); + assertThat(found.getData().getName()).isEqualTo("data0"); + + } +} From 622a90c7d5dd27b2cb477df635611314e100688d Mon Sep 17 00:00:00 2001 From: rbygrave Date: Sun, 23 May 2021 23:00:34 +1200 Subject: [PATCH 19/72] No effective change - tidy whitespace in TransactionManager --- .../transaction/TransactionManager.java | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java index b71868e5d..0728eebac 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java @@ -53,7 +53,6 @@ import java.util.concurrent.atomic.AtomicLong; * Manages transactions. *

* Keeps the Cache and Cluster in sync when transactions are committed. - *

*/ public class TransactionManager implements SpiTransactionManager { @@ -198,8 +197,6 @@ public class TransactionManager implements SpiTransactionManager { this.txnMain = metricFactory.createTimedMetric("txn.main"); this.txnReadOnly = metricFactory.createTimedMetric("txn.readonly"); this.txnNamed = metricFactory.createTimedMetricMap("txn.named."); - // Add gauges for db pool size - scopeManager.register(this); } @@ -304,20 +301,16 @@ public class TransactionManager implements SpiTransactionManager { * There is a potential optimisation available when read committed is the default * isolation level. If it is, then Connections used only for queries do not require * commit or rollback but instead can just be put back into the pool via close(). - *

*

* If the Isolation level is higher (say SERIALIZABLE) then Connections used * just for queries do need to be committed or rollback after the query. - *

*/ OnQueryOnly initOnQueryOnly(OnQueryOnly dbPlatformOnQueryOnly) { - // first check for a system property 'override' String systemPropertyValue = System.getProperty("ebean.transaction.onqueryonly"); if (systemPropertyValue != null) { return OnQueryOnly.valueOf(systemPropertyValue.trim().toUpperCase()); } - // default to rollback if not defined on the platform return dbPlatformOnQueryOnly == null ? OnQueryOnly.COMMIT : dbPlatformOnQueryOnly; } @@ -359,9 +352,7 @@ public class TransactionManager implements SpiTransactionManager { * Wrap an externally supplied Connection with a known transaction id. */ private SpiTransaction wrapExternalConnection(String id, Connection c) { - ExternalJdbcTransaction t = new ExternalJdbcTransaction(id, true, c, this); - // set the default batch mode t.setBatchMode(persistBatch); t.setBatchOnCascade(persistBatchOnCascade); @@ -409,7 +400,6 @@ public class TransactionManager implements SpiTransactionManager { */ @Override public void notifyOfRollback(SpiTransaction transaction, Throwable cause) { - try { if (txnLogger.isDebug()) { String msg = transaction.getLogPrefix() + "Rollback"; @@ -418,7 +408,6 @@ public class TransactionManager implements SpiTransactionManager { } txnLogger.debug(msg); } - } catch (Exception ex) { logger.error("Error while notifying TransactionEventListener of rollback event", ex); } @@ -445,7 +434,6 @@ public class TransactionManager implements SpiTransactionManager { } private void formatThrowable(Throwable e, StringBuilder sb) { - sb.append(e.toString()); StackTraceElement[] stackTrace = e.getStackTrace(); if (stackTrace.length > 0) { @@ -468,11 +456,9 @@ public class TransactionManager implements SpiTransactionManager { if (txnLogger.isDebug()) { txnLogger.debug(transaction.getLogPrefix() + "Commit"); } - PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, transaction); postCommit.notifyLocalCache(); backgroundExecutor.execute(postCommit.backgroundNotify()); - } catch (Exception ex) { logger.error("NotifyOfCommit failed. L2 Cache potentially not notified.", ex); } @@ -488,7 +474,6 @@ public class TransactionManager implements SpiTransactionManager { } private void externalModificationEvent(TransactionEventTable tableEvents) { - TransactionEvent event = new TransactionEvent(); event.add(tableEvents); @@ -501,25 +486,21 @@ public class TransactionManager implements SpiTransactionManager { * Notify local BeanPersistListeners etc of events from another server in the cluster. */ public void remoteTransactionEvent(RemoteTransactionEvent remoteEvent) { - if (clusterLogger.isDebugEnabled()) { clusterLogger.debug("processing {}", remoteEvent); } - CacheChangeSet changeSet = new CacheChangeSet(); RemoteTableMod tableMod = remoteEvent.getRemoteTableMod(); if (tableMod != null) { changeSet.addInvalidate(tableMod.getTables()); } - List tableIUDList = remoteEvent.getTableIUDList(); if (tableIUDList != null) { for (TableIUD tableIUD : tableIUDList) { beanDescriptorManager.cacheNotify(tableIUD, changeSet); } } - // note DeleteById is written as BeanPersistIds and getBeanPersistList() // processes both Bean IUD and DeleteById List beanPersistList = remoteEvent.getBeanPersistList(); @@ -528,7 +509,6 @@ public class TransactionManager implements SpiTransactionManager { persistIds.notifyCache(changeSet); } } - changeSet.apply(); } @@ -543,10 +523,8 @@ public class TransactionManager implements SpiTransactionManager { * Prepare and then send/log the changeSet. */ void sendChangeLog(final ChangeSet changeSet) { - // can set userId, userIpAddress & userContext if desired if (changeLogPrepare.prepare(changeSet)) { - if (changeLogAsync) { // call the log method in background backgroundExecutor.execute(() -> changeLogListener.log(changeSet)); @@ -653,9 +631,7 @@ public class TransactionManager implements SpiTransactionManager { * Begin a scoped transaction. */ public ScopedTransaction beginScopedTransaction(TxScope txScope) { - txScope = initTxScope(txScope); - ScopedTransaction txnContainer = getActiveScoped(); boolean setToScope; @@ -678,7 +654,6 @@ public class TransactionManager implements SpiTransactionManager { if (nestedSavepoint && (type == TxType.REQUIRED || type == TxType.REQUIRES_NEW)) { createTransaction = true; transaction = createSavepoint(transaction, this); - } else { createTransaction = isCreateNewTransaction(transaction, type); if (createTransaction) { @@ -748,32 +723,26 @@ public class TransactionManager implements SpiTransactionManager { * Determine whether to create a new transaction or not. *

* This will also potentially throw exceptions for MANDATORY and NEVER types. - *

*/ private boolean isCreateNewTransaction(SpiTransaction current, TxType type) { switch (type) { case REQUIRED: case SUPPORTS: return current == null; - case REQUIRES_NEW: return true; - case MANDATORY: if (current == null) { throw new PersistenceException("Transaction missing when MANDATORY"); } return false; - case NEVER: if (current != null) { throw new PersistenceException("Transaction exists for Transactional NEVER"); } return true; // always use NoTransaction instance - case NOT_SUPPORTED: return true; // always use NoTransaction instance - default: throw new RuntimeException("Should never get here?"); } From 850f1c1469253ec0c680247724261bbd0388757a Mon Sep 17 00:00:00 2001 From: rbygrave Date: Sun, 23 May 2021 23:08:38 +1200 Subject: [PATCH 20/72] [maven-release-plugin] prepare release ebean-parent-12.9.0 --- ebean-api/pom.xml | 2 +- ebean-autotune/pom.xml | 6 +++--- ebean-bom/pom.xml | 30 +++++++++++++++--------------- ebean-core-type/pom.xml | 4 ++-- ebean-core/pom.xml | 10 +++++----- ebean-ddl-generator/pom.xml | 6 +++--- ebean-externalmapping-api/pom.xml | 2 +- ebean-externalmapping-xml/pom.xml | 10 +++++----- ebean-postgis/pom.xml | 6 +++--- ebean-querybean/pom.xml | 10 +++++----- ebean-redis/pom.xml | 12 ++++++------ ebean-test/pom.xml | 6 +++--- ebean/pom.xml | 8 ++++---- kotlin-querybean-generator/pom.xml | 8 ++++---- pom.xml | 4 ++-- querybean-generator/pom.xml | 2 +- 16 files changed, 63 insertions(+), 63 deletions(-) diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index 10e919049..e329d4f8e 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0-SNAPSHOT + 12.9.0 ebean api diff --git a/ebean-autotune/pom.xml b/ebean-autotune/pom.xml index 1c7d48b4e..d58436ad9 100644 --- a/ebean-autotune/pom.xml +++ b/ebean-autotune/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0-SNAPSHOT + 12.9.0 @@ -14,7 +14,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.8.0 + ebean-parent-12.9.0 ebean autotune @@ -26,7 +26,7 @@ io.ebean ebean-core - 12.9.0-SNAPSHOT + 12.9.0 provided diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index dc1478d34..26e65f569 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0-SNAPSHOT + 12.9.0 ebean bom @@ -81,88 +81,88 @@ io.ebean ebean - 12.9.0-SNAPSHOT + 12.9.0 io.ebean ebean-api - 12.9.0-SNAPSHOT + 12.9.0 io.ebean ebean-core - 12.9.0-SNAPSHOT + 12.9.0 io.ebean ebean-core-type - 12.9.0-SNAPSHOT + 12.9.0 io.ebean ebean-ddl-generator - 12.9.0-SNAPSHOT + 12.9.0 io.ebean ebean-externalmapping-api - 12.9.0-SNAPSHOT + 12.9.0 io.ebean ebean-externalmapping-xml - 12.9.0-SNAPSHOT + 12.9.0 io.ebean ebean-autotune - 12.9.0-SNAPSHOT + 12.9.0 io.ebean ebean-querybean - 12.9.0-SNAPSHOT + 12.9.0 io.ebean querybean-generator - 12.9.0-SNAPSHOT + 12.9.0 provided io.ebean kotlin-querybean-generator - 12.9.0-SNAPSHOT + 12.9.0 provided io.ebean ebean-test - 12.9.0-SNAPSHOT + 12.9.0 test io.ebean ebean-postgis - 12.9.0-SNAPSHOT + 12.9.0 io.ebean ebean-redis - 12.9.0-SNAPSHOT + 12.9.0
diff --git a/ebean-core-type/pom.xml b/ebean-core-type/pom.xml index e6589d839..c83c12657 100644 --- a/ebean-core-type/pom.xml +++ b/ebean-core-type/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0-SNAPSHOT + 12.9.0 ebean-core-type @@ -16,7 +16,7 @@ io.ebean ebean-api - 12.9.0-SNAPSHOT + 12.9.0 diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 16e400f91..294126146 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -3,7 +3,7 @@ ebean-parent io.ebean - 12.9.0-SNAPSHOT + 12.9.0 ebean-core @@ -15,7 +15,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.8.0 + ebean-parent-12.9.0 @@ -87,19 +87,19 @@ io.ebean ebean-api - 12.9.0-SNAPSHOT + 12.9.0 io.ebean ebean-core-type - 12.9.0-SNAPSHOT + 12.9.0 io.ebean ebean-externalmapping-api - 12.9.0-SNAPSHOT + 12.9.0 diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml index 6b16e9b2a..51d288d68 100644 --- a/ebean-ddl-generator/pom.xml +++ b/ebean-ddl-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0-SNAPSHOT + 12.9.0 ebean ddl generation @@ -28,14 +28,14 @@ io.ebean ebean-core-type - 12.9.0-SNAPSHOT + 12.9.0 provided io.ebean ebean-core - 12.9.0-SNAPSHOT + 12.9.0 provided diff --git a/ebean-externalmapping-api/pom.xml b/ebean-externalmapping-api/pom.xml index 57372b097..1858ca78a 100644 --- a/ebean-externalmapping-api/pom.xml +++ b/ebean-externalmapping-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0-SNAPSHOT + 12.9.0 ebean external mapping api diff --git a/ebean-externalmapping-xml/pom.xml b/ebean-externalmapping-xml/pom.xml index 0fb961ac4..174af5f49 100644 --- a/ebean-externalmapping-xml/pom.xml +++ b/ebean-externalmapping-xml/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0-SNAPSHOT + 12.9.0 @@ -14,7 +14,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.8.0 + ebean-parent-12.9.0 ebean external mapping xml @@ -33,7 +33,7 @@ io.ebean ebean-externalmapping-api - 12.9.0-SNAPSHOT + 12.9.0 @@ -59,14 +59,14 @@ io.ebean ebean-core - 12.9.0-SNAPSHOT + 12.9.0 test io.ebean ebean-ddl-generator - 12.9.0-SNAPSHOT + 12.9.0 test diff --git a/ebean-postgis/pom.xml b/ebean-postgis/pom.xml index e000db688..86ddf6ab5 100644 --- a/ebean-postgis/pom.xml +++ b/ebean-postgis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0-SNAPSHOT + 12.9.0 ebean postgis @@ -23,7 +23,7 @@ io.ebean ebean-core - 12.9.0-SNAPSHOT + 12.9.0 provided @@ -74,7 +74,7 @@ io.ebean ebean-test - 12.9.0-SNAPSHOT + 12.9.0 test diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml index 57b01e349..fe67071c0 100644 --- a/ebean-querybean/pom.xml +++ b/ebean-querybean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0-SNAPSHOT + 12.9.0 ebean querybean @@ -17,7 +17,7 @@ io.ebean ebean-core - 12.9.0-SNAPSHOT + 12.9.0 provided @@ -57,21 +57,21 @@ io.ebean ebean-ddl-generator - 12.9.0-SNAPSHOT + 12.9.0 test io.ebean querybean-generator - 12.9.0-SNAPSHOT + 12.9.0 test io.ebean ebean-test - 12.9.0-SNAPSHOT + 12.9.0 test diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml index c500e7508..dd8d1d5bf 100644 --- a/ebean-redis/pom.xml +++ b/ebean-redis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0-SNAPSHOT + 12.9.0 ebean-redis @@ -22,35 +22,35 @@ io.ebean ebean-api - 12.9.0-SNAPSHOT + 12.9.0 provided io.ebean ebean-core - 12.9.0-SNAPSHOT + 12.9.0 provided io.ebean ebean-querybean - 12.9.0-SNAPSHOT + 12.9.0 test io.ebean querybean-generator - 12.9.0-SNAPSHOT + 12.9.0 test io.ebean ebean-test - 12.9.0-SNAPSHOT + 12.9.0 test diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml index 94cba52d6..cd17d895d 100644 --- a/ebean-test/pom.xml +++ b/ebean-test/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0-SNAPSHOT + 12.9.0 ebean test @@ -29,14 +29,14 @@ io.ebean ebean-core - 12.9.0-SNAPSHOT + 12.9.0 provided io.ebean ebean-ddl-generator - 12.9.0-SNAPSHOT + 12.9.0 diff --git a/ebean/pom.xml b/ebean/pom.xml index d284c0bda..510e0a78f 100644 --- a/ebean/pom.xml +++ b/ebean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0-SNAPSHOT + 12.9.0 ebean composite @@ -22,20 +22,20 @@ io.ebean ebean-api - 12.9.0-SNAPSHOT + 12.9.0 io.ebean ebean-core - 12.9.0-SNAPSHOT + 12.9.0 io.ebean ebean-querybean - 12.9.0-SNAPSHOT + 12.9.0 diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml index 917d4275a..76d9c49de 100644 --- a/kotlin-querybean-generator/pom.xml +++ b/kotlin-querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0-SNAPSHOT + 12.9.0 kotlin querybean generator @@ -29,7 +29,7 @@ io.ebean ebean-querybean - 12.9.0-SNAPSHOT + 12.9.0 test @@ -43,7 +43,7 @@ io.ebean ebean-core - 12.9.0-SNAPSHOT + 12.9.0 test @@ -64,7 +64,7 @@ io.ebean ebean-ddl-generator - 12.9.0-SNAPSHOT + 12.9.0 test diff --git a/pom.xml b/pom.xml index 14f2e3f08..d96af125b 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.ebean ebean-parent - 12.9.0-SNAPSHOT + 12.9.0 pom ebean parent @@ -18,7 +18,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.8.0 + ebean-parent-12.9.0 diff --git a/querybean-generator/pom.xml b/querybean-generator/pom.xml index d5d769ac4..4bbbd7cf6 100644 --- a/querybean-generator/pom.xml +++ b/querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0-SNAPSHOT + 12.9.0 querybean generator From 47bd2ff03d34128f069df13c6d3581b8a4b125aa Mon Sep 17 00:00:00 2001 From: rbygrave Date: Sun, 23 May 2021 23:08:47 +1200 Subject: [PATCH 21/72] [maven-release-plugin] prepare for next development iteration --- ebean-api/pom.xml | 2 +- ebean-autotune/pom.xml | 6 +++--- ebean-bom/pom.xml | 30 +++++++++++++++--------------- ebean-core-type/pom.xml | 4 ++-- ebean-core/pom.xml | 10 +++++----- ebean-ddl-generator/pom.xml | 6 +++--- ebean-externalmapping-api/pom.xml | 2 +- ebean-externalmapping-xml/pom.xml | 10 +++++----- ebean-postgis/pom.xml | 6 +++--- ebean-querybean/pom.xml | 10 +++++----- ebean-redis/pom.xml | 12 ++++++------ ebean-test/pom.xml | 6 +++--- ebean/pom.xml | 8 ++++---- kotlin-querybean-generator/pom.xml | 8 ++++---- pom.xml | 4 ++-- querybean-generator/pom.xml | 2 +- 16 files changed, 63 insertions(+), 63 deletions(-) diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index e329d4f8e..250066b16 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0 + 12.9.1-SNAPSHOT ebean api diff --git a/ebean-autotune/pom.xml b/ebean-autotune/pom.xml index d58436ad9..8aadc2cb8 100644 --- a/ebean-autotune/pom.xml +++ b/ebean-autotune/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0 + 12.9.1-SNAPSHOT @@ -14,7 +14,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.9.0 + ebean-parent-12.8.0 ebean autotune @@ -26,7 +26,7 @@ io.ebean ebean-core - 12.9.0 + 12.9.1-SNAPSHOT provided diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index 26e65f569..bc4b1c60d 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0 + 12.9.1-SNAPSHOT ebean bom @@ -81,88 +81,88 @@ io.ebean ebean - 12.9.0 + 12.9.1-SNAPSHOT io.ebean ebean-api - 12.9.0 + 12.9.1-SNAPSHOT io.ebean ebean-core - 12.9.0 + 12.9.1-SNAPSHOT io.ebean ebean-core-type - 12.9.0 + 12.9.1-SNAPSHOT io.ebean ebean-ddl-generator - 12.9.0 + 12.9.1-SNAPSHOT io.ebean ebean-externalmapping-api - 12.9.0 + 12.9.1-SNAPSHOT io.ebean ebean-externalmapping-xml - 12.9.0 + 12.9.1-SNAPSHOT io.ebean ebean-autotune - 12.9.0 + 12.9.1-SNAPSHOT io.ebean ebean-querybean - 12.9.0 + 12.9.1-SNAPSHOT io.ebean querybean-generator - 12.9.0 + 12.9.1-SNAPSHOT provided io.ebean kotlin-querybean-generator - 12.9.0 + 12.9.1-SNAPSHOT provided io.ebean ebean-test - 12.9.0 + 12.9.1-SNAPSHOT test io.ebean ebean-postgis - 12.9.0 + 12.9.1-SNAPSHOT io.ebean ebean-redis - 12.9.0 + 12.9.1-SNAPSHOT diff --git a/ebean-core-type/pom.xml b/ebean-core-type/pom.xml index c83c12657..2cc5360a1 100644 --- a/ebean-core-type/pom.xml +++ b/ebean-core-type/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0 + 12.9.1-SNAPSHOT ebean-core-type @@ -16,7 +16,7 @@ io.ebean ebean-api - 12.9.0 + 12.9.1-SNAPSHOT diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 294126146..582ea91a8 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -3,7 +3,7 @@ ebean-parent io.ebean - 12.9.0 + 12.9.1-SNAPSHOT ebean-core @@ -15,7 +15,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.9.0 + ebean-parent-12.8.0 @@ -87,19 +87,19 @@ io.ebean ebean-api - 12.9.0 + 12.9.1-SNAPSHOT io.ebean ebean-core-type - 12.9.0 + 12.9.1-SNAPSHOT io.ebean ebean-externalmapping-api - 12.9.0 + 12.9.1-SNAPSHOT diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml index 51d288d68..548d4a6bb 100644 --- a/ebean-ddl-generator/pom.xml +++ b/ebean-ddl-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0 + 12.9.1-SNAPSHOT ebean ddl generation @@ -28,14 +28,14 @@ io.ebean ebean-core-type - 12.9.0 + 12.9.1-SNAPSHOT provided io.ebean ebean-core - 12.9.0 + 12.9.1-SNAPSHOT provided diff --git a/ebean-externalmapping-api/pom.xml b/ebean-externalmapping-api/pom.xml index 1858ca78a..e4fe48811 100644 --- a/ebean-externalmapping-api/pom.xml +++ b/ebean-externalmapping-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0 + 12.9.1-SNAPSHOT ebean external mapping api diff --git a/ebean-externalmapping-xml/pom.xml b/ebean-externalmapping-xml/pom.xml index 174af5f49..7be57fd2c 100644 --- a/ebean-externalmapping-xml/pom.xml +++ b/ebean-externalmapping-xml/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0 + 12.9.1-SNAPSHOT @@ -14,7 +14,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.9.0 + ebean-parent-12.8.0 ebean external mapping xml @@ -33,7 +33,7 @@ io.ebean ebean-externalmapping-api - 12.9.0 + 12.9.1-SNAPSHOT @@ -59,14 +59,14 @@ io.ebean ebean-core - 12.9.0 + 12.9.1-SNAPSHOT test io.ebean ebean-ddl-generator - 12.9.0 + 12.9.1-SNAPSHOT test diff --git a/ebean-postgis/pom.xml b/ebean-postgis/pom.xml index 86ddf6ab5..837464a0b 100644 --- a/ebean-postgis/pom.xml +++ b/ebean-postgis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0 + 12.9.1-SNAPSHOT ebean postgis @@ -23,7 +23,7 @@ io.ebean ebean-core - 12.9.0 + 12.9.1-SNAPSHOT provided @@ -74,7 +74,7 @@ io.ebean ebean-test - 12.9.0 + 12.9.1-SNAPSHOT test diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml index fe67071c0..91a109c24 100644 --- a/ebean-querybean/pom.xml +++ b/ebean-querybean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0 + 12.9.1-SNAPSHOT ebean querybean @@ -17,7 +17,7 @@ io.ebean ebean-core - 12.9.0 + 12.9.1-SNAPSHOT provided @@ -57,21 +57,21 @@ io.ebean ebean-ddl-generator - 12.9.0 + 12.9.1-SNAPSHOT test io.ebean querybean-generator - 12.9.0 + 12.9.1-SNAPSHOT test io.ebean ebean-test - 12.9.0 + 12.9.1-SNAPSHOT test diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml index dd8d1d5bf..e051ea21b 100644 --- a/ebean-redis/pom.xml +++ b/ebean-redis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0 + 12.9.1-SNAPSHOT ebean-redis @@ -22,35 +22,35 @@ io.ebean ebean-api - 12.9.0 + 12.9.1-SNAPSHOT provided io.ebean ebean-core - 12.9.0 + 12.9.1-SNAPSHOT provided io.ebean ebean-querybean - 12.9.0 + 12.9.1-SNAPSHOT test io.ebean querybean-generator - 12.9.0 + 12.9.1-SNAPSHOT test io.ebean ebean-test - 12.9.0 + 12.9.1-SNAPSHOT test diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml index cd17d895d..dbf417b93 100644 --- a/ebean-test/pom.xml +++ b/ebean-test/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0 + 12.9.1-SNAPSHOT ebean test @@ -29,14 +29,14 @@ io.ebean ebean-core - 12.9.0 + 12.9.1-SNAPSHOT provided io.ebean ebean-ddl-generator - 12.9.0 + 12.9.1-SNAPSHOT diff --git a/ebean/pom.xml b/ebean/pom.xml index 510e0a78f..ac9ee412e 100644 --- a/ebean/pom.xml +++ b/ebean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0 + 12.9.1-SNAPSHOT ebean composite @@ -22,20 +22,20 @@ io.ebean ebean-api - 12.9.0 + 12.9.1-SNAPSHOT io.ebean ebean-core - 12.9.0 + 12.9.1-SNAPSHOT io.ebean ebean-querybean - 12.9.0 + 12.9.1-SNAPSHOT diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml index 76d9c49de..ab6342a77 100644 --- a/kotlin-querybean-generator/pom.xml +++ b/kotlin-querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0 + 12.9.1-SNAPSHOT kotlin querybean generator @@ -29,7 +29,7 @@ io.ebean ebean-querybean - 12.9.0 + 12.9.1-SNAPSHOT test @@ -43,7 +43,7 @@ io.ebean ebean-core - 12.9.0 + 12.9.1-SNAPSHOT test @@ -64,7 +64,7 @@ io.ebean ebean-ddl-generator - 12.9.0 + 12.9.1-SNAPSHOT test diff --git a/pom.xml b/pom.xml index d96af125b..fc8e0db25 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.ebean ebean-parent - 12.9.0 + 12.9.1-SNAPSHOT pom ebean parent @@ -18,7 +18,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.9.0 + ebean-parent-12.8.0 diff --git a/querybean-generator/pom.xml b/querybean-generator/pom.xml index 4bbbd7cf6..3c6a182a2 100644 --- a/querybean-generator/pom.xml +++ b/querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.0 + 12.9.1-SNAPSHOT querybean generator From 16832ac67a3994ace10108caaaf9bd461b54ea71 Mon Sep 17 00:00:00 2001 From: Ben Kempe Date: Mon, 24 May 2021 23:47:55 +0200 Subject: [PATCH 22/72] add test testSelectOneToOneDisjunction --- .../joins/TestDisjunctWhereOuterJoin.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/ebean-core/src/test/java/org/tests/query/joins/TestDisjunctWhereOuterJoin.java b/ebean-core/src/test/java/org/tests/query/joins/TestDisjunctWhereOuterJoin.java index 28b8b0dbd..5d895adbc 100644 --- a/ebean-core/src/test/java/org/tests/query/joins/TestDisjunctWhereOuterJoin.java +++ b/ebean-core/src/test/java/org/tests/query/joins/TestDisjunctWhereOuterJoin.java @@ -5,6 +5,7 @@ import io.ebean.Ebean; import io.ebean.Expr; import io.ebean.Query; import org.junit.Test; +import org.tests.basic.one2one.Wheel; import org.tests.model.basic.MRole; import org.tests.model.basic.MUser; @@ -82,6 +83,24 @@ public class TestDisjunctWhereOuterJoin extends BaseTestCase { assertThat(sql).contains("where (t0.user_name = ? or u1.roleid = ?)"); } + @Test + public void testSelectOneToOneDisjunction() { + Ebean.beginTransaction(); + try { + Query query = Ebean.find(Wheel.class) + .select("id") + .where().or() + .ge("tire.id", 100) + .lt("tire.id", 100) + .endOr().query(); + query.findList(); + String sql = sqlOf(query); + assertThat(sql).contains("join"); + } finally { + Ebean.rollbackTransaction(); + } + } + private void queryOrExpression(Integer roleid) { Query query = Ebean.find(MUser.class) From bf2420702cfccc6c8c3321e44e693ad1b238cd9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20P=C3=B6hler?= Date: Tue, 1 Jun 2021 17:19:38 +0200 Subject: [PATCH 23/72] FIX: set ddl-generator version to a repo-available version --- ebean-core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 582ea91a8..accc9e877 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -72,7 +72,7 @@ io.ebean ebean-ddl-generator - 12.8.4a + 12.9.0 test From 348c075a1478f1d92b3922b99ae0b4d264dbd8ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20P=C3=B6hler?= Date: Tue, 1 Jun 2021 17:56:47 +0200 Subject: [PATCH 24/72] FIX: Build for java8 --- ebean-core/pom.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index accc9e877..eabc9a68f 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -119,6 +119,15 @@ provided + + + com.h2database + h2 + 1.4.199 + provided + + javax.transaction jta @@ -214,15 +223,6 @@ test - - - com.h2database - h2 - 1.4.199 - provided - - org.xerial sqlite-jdbc From 6697aecd48e95fd461d2c97fc29f8455c325149a Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 4 Jun 2021 17:16:50 +1200 Subject: [PATCH 25/72] #2238 - Select with disjunction of expressions over OneToOne relationship missing join clause Noting that there might be a better fix. The addJoin() at line 151 is suppressed as it is a duplicate for this case of a !oneToOneExported so a better fix might be to only add the soft delete clause when the join as actually added (not suppressed as a duplicate). --- .../ebeaninternal/server/query/SqlTreeNodeExtraJoin.java | 4 ++-- .../softdelete/TestSoftDeleteOptionalRelationship.java | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java index 7238224d7..1b31ee08c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java @@ -143,13 +143,13 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode { // "promote" to left join as the path contains a many joinType = SqlJoinType.OUTER; } - if (!manyToMany && !oneToOneExported) { + if (!manyToMany) { if (assocBeanProperty.isFormula()) { // add joins for formula beans assocBeanProperty.appendFrom(ctx, joinType); } joinType = assocBeanProperty.addJoin(joinType, prefix, ctx); - if (assocBeanProperty.isTargetSoftDelete() && temporalMode != SpiQuery.TemporalMode.SOFT_DELETED) { + if (!oneToOneExported && assocBeanProperty.isTargetSoftDelete() && temporalMode != SpiQuery.TemporalMode.SOFT_DELETED) { ctx.append(" and ").append(assocBeanProperty.getSoftDeletePredicate(ctx.getTableAlias(prefix))); } } diff --git a/ebean-core/src/test/java/org/tests/softdelete/TestSoftDeleteOptionalRelationship.java b/ebean-core/src/test/java/org/tests/softdelete/TestSoftDeleteOptionalRelationship.java index 2488060d4..b4e320b21 100644 --- a/ebean-core/src/test/java/org/tests/softdelete/TestSoftDeleteOptionalRelationship.java +++ b/ebean-core/src/test/java/org/tests/softdelete/TestSoftDeleteOptionalRelationship.java @@ -3,12 +3,14 @@ package org.tests.softdelete; import io.ebean.BaseTestCase; import io.ebean.DB; import io.ebean.Ebean; +import org.ebeantest.LoggedSqlCollector; import org.tests.model.softdelete.ESoftDelMid; import io.ebean.Finder; import org.junit.Test; import org.tests.model.softdelete.ESoftDelY; import org.tests.model.softdelete.ESoftDelZ; +import java.util.List; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; @@ -44,6 +46,8 @@ public class TestSoftDeleteOptionalRelationship extends BaseTestCase { DB.save(y); } + LoggedSqlCollector.start(); + Finder finder = new Finder<>(ESoftDelY.class); ESoftDelY bean = finder .query() @@ -53,7 +57,10 @@ public class TestSoftDeleteOptionalRelationship extends BaseTestCase { .findOne(); assertThat(bean).isNotNull(); + + final List sql = LoggedSqlCollector.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("left join esoft_del_z t1 on t1.id = t0.organization_id and t1.deleted = false where"); } - } From b189e67b4cf3040f48c98ffee7b0685b6eeba315 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 4 Jun 2021 17:35:19 +1200 Subject: [PATCH 26/72] #2241 - Offline DDL migration generation for H2 requires H2 Driver --- .../dbmigration/ddlgeneration/platform/H2HistoryDdl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/H2HistoryDdl.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/H2HistoryDdl.java index bb474c1e4..2e7f97e2c 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/H2HistoryDdl.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/H2HistoryDdl.java @@ -12,7 +12,7 @@ import java.io.IOException; */ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl { - private static final String TRIGGER_CLASS = H2HistoryTrigger.class.getName(); + private static final String TRIGGER_CLASS = "io.ebean.config.dbplatform.h2.H2HistoryTrigger"; H2HistoryDdl() { this.sysPeriodType = "timestamp"; From c9120be347aadd413371d46f974b2aa41630fa96 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 4 Jun 2021 17:37:37 +1200 Subject: [PATCH 27/72] No effective change - tidy only H2HistoryTrigger and H2HistoryDdl --- .../config/dbplatform/h2/H2HistoryTrigger.java | 13 +++---------- .../ddlgeneration/platform/H2HistoryDdl.java | 7 ------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/h2/H2HistoryTrigger.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/h2/H2HistoryTrigger.java index 785e24a0c..323dd1120 100644 --- a/ebean-api/src/main/java/io/ebean/config/dbplatform/h2/H2HistoryTrigger.java +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/h2/H2HistoryTrigger.java @@ -4,11 +4,7 @@ import org.h2.api.Trigger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; +import java.sql.*; import java.util.Arrays; /** @@ -43,7 +39,6 @@ public class H2HistoryTrigger implements Trigger { @Override public void init(Connection conn, String schemaName, String triggerName, String tableName, boolean before, int type) throws SQLException { - // get the columns for the table ResultSet rs = conn.getMetaData().getColumns(null, schemaName, tableName, null); @@ -79,7 +74,6 @@ public class H2HistoryTrigger implements Trigger { @Override public void fire(Connection connection, Object[] oldRow, Object[] newRow) throws SQLException { - if (oldRow != null) { // a delete or update event Timestamp now = new Timestamp(System.currentTimeMillis()); @@ -99,7 +93,6 @@ public class H2HistoryTrigger implements Trigger { * Insert the data into the history table. */ private void insertIntoHistory(Connection connection, Object[] oldRow) throws SQLException { - try (PreparedStatement stmt = connection.prepareStatement(insertHistorySql)) { for (int i = 0; i < oldRow.length; i++) { stmt.setObject(i + 1, oldRow[i]); @@ -110,11 +103,11 @@ public class H2HistoryTrigger implements Trigger { @Override public void close() throws SQLException { - + // do nothing } @Override public void remove() throws SQLException { - + // do nothing } } diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/H2HistoryDdl.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/H2HistoryDdl.java index 2e7f97e2c..5bd58adeb 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/H2HistoryDdl.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/H2HistoryDdl.java @@ -1,6 +1,5 @@ package io.ebeaninternal.dbmigration.ddlgeneration.platform; -import io.ebean.config.dbplatform.h2.H2HistoryTrigger; import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer; import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite; import io.ebeaninternal.dbmigration.model.MTable; @@ -22,31 +21,25 @@ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl { @Override protected void dropTriggers(DdlBuffer buffer, String baseTable) throws IOException { - buffer.append("drop trigger ").append(updateTriggerName(baseTable)).endOfStatement(); } @Override protected void createTriggers(DdlWrite writer, MTable table) throws IOException { - String baseTableName = table.getName(); DdlBuffer apply = writer.applyHistoryTrigger(); - addCreateTrigger(apply, updateTriggerName(baseTableName), baseTableName); } @Override protected void updateHistoryTriggers(DbTriggerUpdate update) throws IOException { - recreateHistoryView(update); - DdlBuffer buffer = update.historyTriggerBuffer(); dropTriggers(buffer, update.getBaseTable()); addCreateTrigger(buffer, updateTriggerName(update.getBaseTable()), update.getBaseTable()); } private void addCreateTrigger(DdlBuffer apply, String triggerName, String baseTable) throws IOException { - // Note that this does not take into account the historyTable name (excepts _history suffix) and // does not take into account excluded columns (all columns included in history) apply From ae4f50e7b3a1b15fe3a0f97a9ec01803518f72e7 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 4 Jun 2021 17:46:31 +1200 Subject: [PATCH 28/72] #2236 - Auto-tune does not work / throws RuntimeException (Regression from 12.7.1) Note we are missing a test on ProfileManager but doing this for now. --- .../server/autotune/service/ProfileManager.java | 10 +--------- .../server/autotune/service/ProfileOriginTest.java | 10 +++++----- .../ebeaninternal/server/querydefn/OrmQueryDetail.java | 2 +- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/ProfileManager.java b/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/ProfileManager.java index f334208f9..170a3a406 100644 --- a/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/ProfileManager.java +++ b/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/ProfileManager.java @@ -46,7 +46,6 @@ public class ProfileManager implements ProfilingListener { @Override public boolean isProfileRequest(ObjectGraphNode origin, SpiQuery query) { - ProfileOrigin profileOrigin = profileMap.get(origin.getOriginQueryPoint().getKey()); if (profileOrigin == null) { profileMap.put(origin.getOriginQueryPoint().getKey(), createProfileOrigin(origin, query)); @@ -61,12 +60,11 @@ public class ProfileManager implements ProfilingListener { *

* For new profiling entries it is useful to compare the profiling against the current * query detail that is specified in the code (as the query might already be manually optimised). - *

*/ private ProfileOrigin createProfileOrigin(ObjectGraphNode origin, SpiQuery query) { ProfileOrigin profileOrigin = new ProfileOrigin(origin.getOriginQueryPoint(), queryTuningAddVersion, profilingBase, profilingRate); // set the current query detail (fetch group) so that we can compare against profiling for new entries - profileOrigin.setOriginalQuery(query.getDetail().toString()); + profileOrigin.setOriginalQuery(query.getDetail().asString()); return profileOrigin; } @@ -77,7 +75,6 @@ public class ProfileManager implements ProfilingListener { */ @Override public void collectQueryInfo(ObjectGraphNode node, long beans, long micros) { - if (node != null) { ObjectGraphOrigin origin = node.getOriginQueryPoint(); if (origin != null) { @@ -92,11 +89,9 @@ public class ProfileManager implements ProfilingListener { *

* This is sent to use from a EntityBeanIntercept when the finalise method * is called on the bean. - *

*/ @Override public void collectNodeUsage(NodeUsageCollector usageCollector) { - ProfileOrigin profileOrigin = getProfileOrigin(usageCollector.getNode().getOriginQueryPoint()); profileOrigin.collectUsageInfo(usageCollector); } @@ -114,16 +109,13 @@ public class ProfileManager implements ProfilingListener { * Collect all the profiling information. */ public AutoTuneCollection profilingCollection(boolean reset) { - AutoTuneCollection req = new AutoTuneCollection(); - for (ProfileOrigin origin : profileMap.values()) { BeanDescriptor desc = server.getBeanDescriptorById(origin.getOrigin().getBeanType()); if (desc != null) { origin.profilingCollection(desc, req, reset); } } - return req; } diff --git a/ebean-autotune/src/test/java/io/ebeaninternal/server/autotune/service/ProfileOriginTest.java b/ebean-autotune/src/test/java/io/ebeaninternal/server/autotune/service/ProfileOriginTest.java index c3995d215..b1f982024 100644 --- a/ebean-autotune/src/test/java/io/ebeaninternal/server/autotune/service/ProfileOriginTest.java +++ b/ebean-autotune/src/test/java/io/ebeaninternal/server/autotune/service/ProfileOriginTest.java @@ -27,7 +27,7 @@ public class ProfileOriginTest extends BaseTestCase { OrmQueryDetail detail = po.buildDetail(desc); - assertThat(detail.asStringDebug().trim()).isEqualTo("fetch customer (name)"); + assertThat(detail.asString().trim()).isEqualTo("fetch customer (name)"); } @Test @@ -46,7 +46,7 @@ public class ProfileOriginTest extends BaseTestCase { OrmQueryDetail detail = po.buildDetail(desc); - assertThat(detail.asStringDebug()).isEqualTo("select (orderDate) fetch customer (name)"); + assertThat(detail.asString()).isEqualTo("select (orderDate) fetch customer (name)"); } @Test @@ -64,7 +64,7 @@ public class ProfileOriginTest extends BaseTestCase { OrmQueryDetail detail = po.buildDetail(desc); - assertThat(detail.asStringDebug().trim()).isEqualTo("select (orderDate,customer)"); + assertThat(detail.asString().trim()).isEqualTo("select (orderDate,customer)"); } @Test @@ -87,7 +87,7 @@ public class ProfileOriginTest extends BaseTestCase { OrmQueryDetail detail = po.buildDetail(desc); - assertThat(detail.asStringDebug()).isEqualTo("select (orderDate) fetch customer (billingAddress)"); + assertThat(detail.asString()).isEqualTo("select (orderDate) fetch customer (billingAddress)"); } @@ -116,7 +116,7 @@ public class ProfileOriginTest extends BaseTestCase { po.collectUsageInfo(c); OrmQueryDetail detail = po.buildDetail(desc); - assertThat(detail.asStringDebug()).isEqualTo("fetch customer (name,note) fetch customer.billingAddress (line1)"); + assertThat(detail.asString()).isEqualTo("fetch customer (name,note) fetch customer.billingAddress (line1)"); } private NodeUsageCollector node(String path) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java index 6646746eb..2fabfdd79 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -100,7 +100,7 @@ public class OrmQueryDetail implements Serializable { /** * Return the detail in string form. */ - public String asStringDebug() { + public String asString() { StringBuilder sb = new StringBuilder(); if (baseProps.hasProperties()) { baseProps.asStringDebug("select ", sb); From ce5f1b25f5402f05cbc8f3dae311940fafc057f2 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 4 Jun 2021 17:50:46 +1200 Subject: [PATCH 29/72] Bump ebean-maven-plugin to 12.9.1 --- ebean-bom/pom.xml | 2 +- ebean-core/pom.xml | 2 +- ebean-ddl-generator/pom.xml | 2 +- kotlin-querybean-generator/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index bc4b1c60d..1dfdafb52 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -19,7 +19,7 @@ 4.1 7.0 12.9.0 - 12.9.0 + 12.9.1 diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index eabc9a68f..aad54f60e 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -302,7 +302,7 @@ io.ebean ebean-maven-plugin - 12.9.0 + 12.9.1 test diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml index 548d4a6bb..3056cb064 100644 --- a/ebean-ddl-generator/pom.xml +++ b/ebean-ddl-generator/pom.xml @@ -76,7 +76,7 @@ io.ebean ebean-maven-plugin - 12.9.0 + 12.9.1 test diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml index ab6342a77..6e081b514 100644 --- a/kotlin-querybean-generator/pom.xml +++ b/kotlin-querybean-generator/pom.xml @@ -145,7 +145,7 @@ io.ebean ebean-maven-plugin - 12.9.0 + 12.9.1 test From 64405ade5204b20caeff42bfd00e1428f93bf0e5 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 4 Jun 2021 17:59:11 +1200 Subject: [PATCH 30/72] [maven-release-plugin] prepare release ebean-parent-12.9.1 --- ebean-api/pom.xml | 2 +- ebean-autotune/pom.xml | 6 +++--- ebean-bom/pom.xml | 30 +++++++++++++++--------------- ebean-core-type/pom.xml | 4 ++-- ebean-core/pom.xml | 10 +++++----- ebean-ddl-generator/pom.xml | 6 +++--- ebean-externalmapping-api/pom.xml | 2 +- ebean-externalmapping-xml/pom.xml | 10 +++++----- ebean-postgis/pom.xml | 6 +++--- ebean-querybean/pom.xml | 10 +++++----- ebean-redis/pom.xml | 12 ++++++------ ebean-test/pom.xml | 6 +++--- ebean/pom.xml | 8 ++++---- kotlin-querybean-generator/pom.xml | 8 ++++---- pom.xml | 4 ++-- querybean-generator/pom.xml | 2 +- 16 files changed, 63 insertions(+), 63 deletions(-) diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index 250066b16..286f7ae58 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1-SNAPSHOT + 12.9.1 ebean api diff --git a/ebean-autotune/pom.xml b/ebean-autotune/pom.xml index 8aadc2cb8..17084e262 100644 --- a/ebean-autotune/pom.xml +++ b/ebean-autotune/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1-SNAPSHOT + 12.9.1 @@ -14,7 +14,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.8.0 + ebean-parent-12.9.1 ebean autotune @@ -26,7 +26,7 @@ io.ebean ebean-core - 12.9.1-SNAPSHOT + 12.9.1 provided diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index 1dfdafb52..5ea0b74cf 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1-SNAPSHOT + 12.9.1 ebean bom @@ -81,88 +81,88 @@ io.ebean ebean - 12.9.1-SNAPSHOT + 12.9.1 io.ebean ebean-api - 12.9.1-SNAPSHOT + 12.9.1 io.ebean ebean-core - 12.9.1-SNAPSHOT + 12.9.1 io.ebean ebean-core-type - 12.9.1-SNAPSHOT + 12.9.1 io.ebean ebean-ddl-generator - 12.9.1-SNAPSHOT + 12.9.1 io.ebean ebean-externalmapping-api - 12.9.1-SNAPSHOT + 12.9.1 io.ebean ebean-externalmapping-xml - 12.9.1-SNAPSHOT + 12.9.1 io.ebean ebean-autotune - 12.9.1-SNAPSHOT + 12.9.1 io.ebean ebean-querybean - 12.9.1-SNAPSHOT + 12.9.1 io.ebean querybean-generator - 12.9.1-SNAPSHOT + 12.9.1 provided io.ebean kotlin-querybean-generator - 12.9.1-SNAPSHOT + 12.9.1 provided io.ebean ebean-test - 12.9.1-SNAPSHOT + 12.9.1 test io.ebean ebean-postgis - 12.9.1-SNAPSHOT + 12.9.1 io.ebean ebean-redis - 12.9.1-SNAPSHOT + 12.9.1 diff --git a/ebean-core-type/pom.xml b/ebean-core-type/pom.xml index 2cc5360a1..d35cfa676 100644 --- a/ebean-core-type/pom.xml +++ b/ebean-core-type/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1-SNAPSHOT + 12.9.1 ebean-core-type @@ -16,7 +16,7 @@ io.ebean ebean-api - 12.9.1-SNAPSHOT + 12.9.1 diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index aad54f60e..1ba09d035 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -3,7 +3,7 @@ ebean-parent io.ebean - 12.9.1-SNAPSHOT + 12.9.1 ebean-core @@ -15,7 +15,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.8.0 + ebean-parent-12.9.1 @@ -87,19 +87,19 @@ io.ebean ebean-api - 12.9.1-SNAPSHOT + 12.9.1 io.ebean ebean-core-type - 12.9.1-SNAPSHOT + 12.9.1 io.ebean ebean-externalmapping-api - 12.9.1-SNAPSHOT + 12.9.1 diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml index 3056cb064..86f69ec5c 100644 --- a/ebean-ddl-generator/pom.xml +++ b/ebean-ddl-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1-SNAPSHOT + 12.9.1 ebean ddl generation @@ -28,14 +28,14 @@ io.ebean ebean-core-type - 12.9.1-SNAPSHOT + 12.9.1 provided io.ebean ebean-core - 12.9.1-SNAPSHOT + 12.9.1 provided diff --git a/ebean-externalmapping-api/pom.xml b/ebean-externalmapping-api/pom.xml index e4fe48811..1d69bcc30 100644 --- a/ebean-externalmapping-api/pom.xml +++ b/ebean-externalmapping-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1-SNAPSHOT + 12.9.1 ebean external mapping api diff --git a/ebean-externalmapping-xml/pom.xml b/ebean-externalmapping-xml/pom.xml index 7be57fd2c..e4af2cc57 100644 --- a/ebean-externalmapping-xml/pom.xml +++ b/ebean-externalmapping-xml/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1-SNAPSHOT + 12.9.1 @@ -14,7 +14,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.8.0 + ebean-parent-12.9.1 ebean external mapping xml @@ -33,7 +33,7 @@ io.ebean ebean-externalmapping-api - 12.9.1-SNAPSHOT + 12.9.1 @@ -59,14 +59,14 @@ io.ebean ebean-core - 12.9.1-SNAPSHOT + 12.9.1 test io.ebean ebean-ddl-generator - 12.9.1-SNAPSHOT + 12.9.1 test diff --git a/ebean-postgis/pom.xml b/ebean-postgis/pom.xml index 837464a0b..8352cef21 100644 --- a/ebean-postgis/pom.xml +++ b/ebean-postgis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1-SNAPSHOT + 12.9.1 ebean postgis @@ -23,7 +23,7 @@ io.ebean ebean-core - 12.9.1-SNAPSHOT + 12.9.1 provided @@ -74,7 +74,7 @@ io.ebean ebean-test - 12.9.1-SNAPSHOT + 12.9.1 test diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml index 91a109c24..fb83cca3e 100644 --- a/ebean-querybean/pom.xml +++ b/ebean-querybean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1-SNAPSHOT + 12.9.1 ebean querybean @@ -17,7 +17,7 @@ io.ebean ebean-core - 12.9.1-SNAPSHOT + 12.9.1 provided @@ -57,21 +57,21 @@ io.ebean ebean-ddl-generator - 12.9.1-SNAPSHOT + 12.9.1 test io.ebean querybean-generator - 12.9.1-SNAPSHOT + 12.9.1 test io.ebean ebean-test - 12.9.1-SNAPSHOT + 12.9.1 test diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml index e051ea21b..61ee622d9 100644 --- a/ebean-redis/pom.xml +++ b/ebean-redis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1-SNAPSHOT + 12.9.1 ebean-redis @@ -22,35 +22,35 @@ io.ebean ebean-api - 12.9.1-SNAPSHOT + 12.9.1 provided io.ebean ebean-core - 12.9.1-SNAPSHOT + 12.9.1 provided io.ebean ebean-querybean - 12.9.1-SNAPSHOT + 12.9.1 test io.ebean querybean-generator - 12.9.1-SNAPSHOT + 12.9.1 test io.ebean ebean-test - 12.9.1-SNAPSHOT + 12.9.1 test diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml index dbf417b93..e35fd1d9f 100644 --- a/ebean-test/pom.xml +++ b/ebean-test/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1-SNAPSHOT + 12.9.1 ebean test @@ -29,14 +29,14 @@ io.ebean ebean-core - 12.9.1-SNAPSHOT + 12.9.1 provided io.ebean ebean-ddl-generator - 12.9.1-SNAPSHOT + 12.9.1 diff --git a/ebean/pom.xml b/ebean/pom.xml index ac9ee412e..0aacf1dc3 100644 --- a/ebean/pom.xml +++ b/ebean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1-SNAPSHOT + 12.9.1 ebean composite @@ -22,20 +22,20 @@ io.ebean ebean-api - 12.9.1-SNAPSHOT + 12.9.1 io.ebean ebean-core - 12.9.1-SNAPSHOT + 12.9.1 io.ebean ebean-querybean - 12.9.1-SNAPSHOT + 12.9.1 diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml index 6e081b514..2b3522a64 100644 --- a/kotlin-querybean-generator/pom.xml +++ b/kotlin-querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1-SNAPSHOT + 12.9.1 kotlin querybean generator @@ -29,7 +29,7 @@ io.ebean ebean-querybean - 12.9.1-SNAPSHOT + 12.9.1 test @@ -43,7 +43,7 @@ io.ebean ebean-core - 12.9.1-SNAPSHOT + 12.9.1 test @@ -64,7 +64,7 @@ io.ebean ebean-ddl-generator - 12.9.1-SNAPSHOT + 12.9.1 test diff --git a/pom.xml b/pom.xml index fc8e0db25..05caa74f3 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.ebean ebean-parent - 12.9.1-SNAPSHOT + 12.9.1 pom ebean parent @@ -18,7 +18,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.8.0 + ebean-parent-12.9.1 diff --git a/querybean-generator/pom.xml b/querybean-generator/pom.xml index 3c6a182a2..c30aae273 100644 --- a/querybean-generator/pom.xml +++ b/querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1-SNAPSHOT + 12.9.1 querybean generator From 612295fb9dee24a189d201daa6ff27fd8c82d166 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 4 Jun 2021 17:59:21 +1200 Subject: [PATCH 31/72] [maven-release-plugin] prepare for next development iteration --- ebean-api/pom.xml | 2 +- ebean-autotune/pom.xml | 6 +++--- ebean-bom/pom.xml | 30 +++++++++++++++--------------- ebean-core-type/pom.xml | 4 ++-- ebean-core/pom.xml | 10 +++++----- ebean-ddl-generator/pom.xml | 6 +++--- ebean-externalmapping-api/pom.xml | 2 +- ebean-externalmapping-xml/pom.xml | 10 +++++----- ebean-postgis/pom.xml | 6 +++--- ebean-querybean/pom.xml | 10 +++++----- ebean-redis/pom.xml | 12 ++++++------ ebean-test/pom.xml | 6 +++--- ebean/pom.xml | 8 ++++---- kotlin-querybean-generator/pom.xml | 8 ++++---- pom.xml | 4 ++-- querybean-generator/pom.xml | 2 +- 16 files changed, 63 insertions(+), 63 deletions(-) diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index 286f7ae58..1d382e05c 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1 + 12.9.2-SNAPSHOT ebean api diff --git a/ebean-autotune/pom.xml b/ebean-autotune/pom.xml index 17084e262..b8908e306 100644 --- a/ebean-autotune/pom.xml +++ b/ebean-autotune/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1 + 12.9.2-SNAPSHOT @@ -14,7 +14,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.9.1 + ebean-parent-12.8.0 ebean autotune @@ -26,7 +26,7 @@ io.ebean ebean-core - 12.9.1 + 12.9.2-SNAPSHOT provided diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index 5ea0b74cf..94223e4be 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1 + 12.9.2-SNAPSHOT ebean bom @@ -81,88 +81,88 @@ io.ebean ebean - 12.9.1 + 12.9.2-SNAPSHOT io.ebean ebean-api - 12.9.1 + 12.9.2-SNAPSHOT io.ebean ebean-core - 12.9.1 + 12.9.2-SNAPSHOT io.ebean ebean-core-type - 12.9.1 + 12.9.2-SNAPSHOT io.ebean ebean-ddl-generator - 12.9.1 + 12.9.2-SNAPSHOT io.ebean ebean-externalmapping-api - 12.9.1 + 12.9.2-SNAPSHOT io.ebean ebean-externalmapping-xml - 12.9.1 + 12.9.2-SNAPSHOT io.ebean ebean-autotune - 12.9.1 + 12.9.2-SNAPSHOT io.ebean ebean-querybean - 12.9.1 + 12.9.2-SNAPSHOT io.ebean querybean-generator - 12.9.1 + 12.9.2-SNAPSHOT provided io.ebean kotlin-querybean-generator - 12.9.1 + 12.9.2-SNAPSHOT provided io.ebean ebean-test - 12.9.1 + 12.9.2-SNAPSHOT test io.ebean ebean-postgis - 12.9.1 + 12.9.2-SNAPSHOT io.ebean ebean-redis - 12.9.1 + 12.9.2-SNAPSHOT diff --git a/ebean-core-type/pom.xml b/ebean-core-type/pom.xml index d35cfa676..d7ea19e6f 100644 --- a/ebean-core-type/pom.xml +++ b/ebean-core-type/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1 + 12.9.2-SNAPSHOT ebean-core-type @@ -16,7 +16,7 @@ io.ebean ebean-api - 12.9.1 + 12.9.2-SNAPSHOT diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 1ba09d035..8f0b27243 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -3,7 +3,7 @@ ebean-parent io.ebean - 12.9.1 + 12.9.2-SNAPSHOT ebean-core @@ -15,7 +15,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.9.1 + ebean-parent-12.8.0 @@ -87,19 +87,19 @@ io.ebean ebean-api - 12.9.1 + 12.9.2-SNAPSHOT io.ebean ebean-core-type - 12.9.1 + 12.9.2-SNAPSHOT io.ebean ebean-externalmapping-api - 12.9.1 + 12.9.2-SNAPSHOT diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml index 86f69ec5c..6397c239b 100644 --- a/ebean-ddl-generator/pom.xml +++ b/ebean-ddl-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1 + 12.9.2-SNAPSHOT ebean ddl generation @@ -28,14 +28,14 @@ io.ebean ebean-core-type - 12.9.1 + 12.9.2-SNAPSHOT provided io.ebean ebean-core - 12.9.1 + 12.9.2-SNAPSHOT provided diff --git a/ebean-externalmapping-api/pom.xml b/ebean-externalmapping-api/pom.xml index 1d69bcc30..45e433741 100644 --- a/ebean-externalmapping-api/pom.xml +++ b/ebean-externalmapping-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1 + 12.9.2-SNAPSHOT ebean external mapping api diff --git a/ebean-externalmapping-xml/pom.xml b/ebean-externalmapping-xml/pom.xml index e4af2cc57..17aa58fdb 100644 --- a/ebean-externalmapping-xml/pom.xml +++ b/ebean-externalmapping-xml/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1 + 12.9.2-SNAPSHOT @@ -14,7 +14,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.9.1 + ebean-parent-12.8.0 ebean external mapping xml @@ -33,7 +33,7 @@ io.ebean ebean-externalmapping-api - 12.9.1 + 12.9.2-SNAPSHOT @@ -59,14 +59,14 @@ io.ebean ebean-core - 12.9.1 + 12.9.2-SNAPSHOT test io.ebean ebean-ddl-generator - 12.9.1 + 12.9.2-SNAPSHOT test diff --git a/ebean-postgis/pom.xml b/ebean-postgis/pom.xml index 8352cef21..74ea548c1 100644 --- a/ebean-postgis/pom.xml +++ b/ebean-postgis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1 + 12.9.2-SNAPSHOT ebean postgis @@ -23,7 +23,7 @@ io.ebean ebean-core - 12.9.1 + 12.9.2-SNAPSHOT provided @@ -74,7 +74,7 @@ io.ebean ebean-test - 12.9.1 + 12.9.2-SNAPSHOT test diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml index fb83cca3e..2994c396d 100644 --- a/ebean-querybean/pom.xml +++ b/ebean-querybean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1 + 12.9.2-SNAPSHOT ebean querybean @@ -17,7 +17,7 @@ io.ebean ebean-core - 12.9.1 + 12.9.2-SNAPSHOT provided @@ -57,21 +57,21 @@ io.ebean ebean-ddl-generator - 12.9.1 + 12.9.2-SNAPSHOT test io.ebean querybean-generator - 12.9.1 + 12.9.2-SNAPSHOT test io.ebean ebean-test - 12.9.1 + 12.9.2-SNAPSHOT test diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml index 61ee622d9..12187d225 100644 --- a/ebean-redis/pom.xml +++ b/ebean-redis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1 + 12.9.2-SNAPSHOT ebean-redis @@ -22,35 +22,35 @@ io.ebean ebean-api - 12.9.1 + 12.9.2-SNAPSHOT provided io.ebean ebean-core - 12.9.1 + 12.9.2-SNAPSHOT provided io.ebean ebean-querybean - 12.9.1 + 12.9.2-SNAPSHOT test io.ebean querybean-generator - 12.9.1 + 12.9.2-SNAPSHOT test io.ebean ebean-test - 12.9.1 + 12.9.2-SNAPSHOT test diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml index e35fd1d9f..66aeb2e78 100644 --- a/ebean-test/pom.xml +++ b/ebean-test/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1 + 12.9.2-SNAPSHOT ebean test @@ -29,14 +29,14 @@ io.ebean ebean-core - 12.9.1 + 12.9.2-SNAPSHOT provided io.ebean ebean-ddl-generator - 12.9.1 + 12.9.2-SNAPSHOT diff --git a/ebean/pom.xml b/ebean/pom.xml index 0aacf1dc3..c8f3462d4 100644 --- a/ebean/pom.xml +++ b/ebean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1 + 12.9.2-SNAPSHOT ebean composite @@ -22,20 +22,20 @@ io.ebean ebean-api - 12.9.1 + 12.9.2-SNAPSHOT io.ebean ebean-core - 12.9.1 + 12.9.2-SNAPSHOT io.ebean ebean-querybean - 12.9.1 + 12.9.2-SNAPSHOT diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml index 2b3522a64..20bf130a3 100644 --- a/kotlin-querybean-generator/pom.xml +++ b/kotlin-querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1 + 12.9.2-SNAPSHOT kotlin querybean generator @@ -29,7 +29,7 @@ io.ebean ebean-querybean - 12.9.1 + 12.9.2-SNAPSHOT test @@ -43,7 +43,7 @@ io.ebean ebean-core - 12.9.1 + 12.9.2-SNAPSHOT test @@ -64,7 +64,7 @@ io.ebean ebean-ddl-generator - 12.9.1 + 12.9.2-SNAPSHOT test diff --git a/pom.xml b/pom.xml index 05caa74f3..af7bc24b2 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.ebean ebean-parent - 12.9.1 + 12.9.2-SNAPSHOT pom ebean parent @@ -18,7 +18,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.9.1 + ebean-parent-12.8.0 diff --git a/querybean-generator/pom.xml b/querybean-generator/pom.xml index c30aae273..1e59c8065 100644 --- a/querybean-generator/pom.xml +++ b/querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.1 + 12.9.2-SNAPSHOT querybean generator From 4613f63c6613358656c9a4f2798f2ceb21b4b6f2 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Wed, 9 Jun 2021 12:57:56 +1200 Subject: [PATCH 32/72] Refactor internals DataReader remove resetColumnPosition() - Move the reset of position to the DataReader.next() method - Tidy up use of wasNull() in RsetDataReader --- .../java/io/ebean/core/type/DataReader.java | 2 - .../server/core/AbstractSqlQueryRequest.java | 22 +++---- .../server/core/DtoQueryRequest.java | 5 +- .../server/core/RelationalQueryRequest.java | 5 ++ .../io/ebeaninternal/server/query/CQuery.java | 4 -- .../query/CQueryFetchSingleAttribute.java | 1 - .../server/type/RsetDataReader.java | 57 +++---------------- 7 files changed, 25 insertions(+), 71 deletions(-) diff --git a/ebean-core-type/src/main/java/io/ebean/core/type/DataReader.java b/ebean-core-type/src/main/java/io/ebean/core/type/DataReader.java index 45fba0e0e..a8ace12be 100644 --- a/ebean-core-type/src/main/java/io/ebean/core/type/DataReader.java +++ b/ebean-core-type/src/main/java/io/ebean/core/type/DataReader.java @@ -11,8 +11,6 @@ public interface DataReader { boolean next() throws SQLException; - void resetColumnPosition(); - void incrementPos(int increment); byte[] getBinaryBytes() throws SQLException; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java index c12534146..3c1050faf 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java @@ -1,13 +1,8 @@ package io.ebeaninternal.server.core; -import io.ebean.EbeanServer; import io.ebean.Transaction; import io.ebean.util.JdbcClose; -import io.ebeaninternal.api.BindParams; -import io.ebeaninternal.api.SpiEbeanServer; -import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.api.SpiSqlBinding; -import io.ebeaninternal.api.SpiTransaction; +import io.ebeaninternal.api.*; import io.ebeaninternal.server.lib.Str; import io.ebeaninternal.server.persist.Binder; import io.ebeaninternal.server.persist.TrimLogSql; @@ -83,11 +78,6 @@ public abstract class AbstractSqlQueryRequest { return transaction.isLogSql(); } - /** - * Set the resultSet and associated query plan if known. - */ - abstract void setResultSet(ResultSet resultSet, Object queryPlanKey) throws SQLException; - /** * Return the bindLog for this request. */ @@ -95,12 +85,15 @@ public abstract class AbstractSqlQueryRequest { return bindLog; } + /** + * Set the resultSet and associated query plan if known. + */ + abstract void setResultSet(ResultSet resultSet, Object queryPlanKey) throws SQLException; + /** * Return true if we can navigate to the next row. */ - public boolean next() throws SQLException { - return resultSet.next(); - } + public abstract boolean next() throws SQLException; protected abstract void requestComplete(); @@ -113,7 +106,6 @@ public abstract class AbstractSqlQueryRequest { JdbcClose.close(pstmt); } - /** * Prepare the SQL taking into account named bind parameters. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java index 2372ed7c3..b988c83b5 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java @@ -110,9 +110,12 @@ public final class DtoQueryRequest extends AbstractSqlQueryRequest { return queryEngine.findList(this); } + public boolean next() throws SQLException { + return dataReader.next(); + } + @SuppressWarnings("unchecked") public T readNextBean() throws SQLException { - dataReader.resetColumnPosition(); return (T) plan.readRow(dataReader); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java index 7850139c7..9f1413031 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java @@ -139,6 +139,11 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest { rows++; } + @Override + public boolean next() throws SQLException { + return resultSet.next(); + } + public List mapList(RowMapper mapper) throws SQLException { List list = new ArrayList<>(); while (next()) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java index 9f30e9a29..49182242e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java @@ -486,14 +486,11 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran * Return true if we can move to the next resultSet row. */ private boolean moveToNextRow() throws SQLException { - if (!dataReader.next()) { noMoreRows = true; return false; } - rowCount++; - dataReader.resetColumnPosition(); return true; } @@ -502,7 +499,6 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran } boolean readBean() throws SQLException { - boolean result = hasNext(); updateExecutionStatistics(); return result; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java index 45fabfde7..9fd7278cc 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java @@ -111,7 +111,6 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent { value = new CountedValue<>(value, dataReader.getLong()); } result.add(value); - dataReader.resetColumnPosition(); rowCount++; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/RsetDataReader.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/RsetDataReader.java index 3448251d5..9763a5667 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/RsetDataReader.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/RsetDataReader.java @@ -43,12 +43,8 @@ public class RsetDataReader implements DataReader { @Override public boolean next() throws SQLException { - return rset.next(); - } - - @Override - public void resetColumnPosition() { pos = 0; + return rset.next(); } @Override @@ -75,7 +71,6 @@ public class RsetDataReader implements DataReader { return rset.getBigDecimal(pos()); } - @Override public InputStream getBinaryStream() throws SQLException { return rset.getBinaryStream(pos()); @@ -84,19 +79,13 @@ public class RsetDataReader implements DataReader { @Override public Boolean getBoolean() throws SQLException { boolean v = rset.getBoolean(pos()); - if (rset.wasNull()) { - return null; - } - return v; + return rset.wasNull() ? null : v; } @Override public Byte getByte() throws SQLException { byte v = rset.getByte(pos()); - if (rset.wasNull()) { - return null; - } - return v; + return rset.wasNull() ? null : v; } @Override @@ -117,62 +106,42 @@ public class RsetDataReader implements DataReader { @Override public Double getDouble() throws SQLException { double v = rset.getDouble(pos()); - if (rset.wasNull()) { - return null; - } - return v; + return rset.wasNull() ? null : v; } @Override public Float getFloat() throws SQLException { float v = rset.getFloat(pos()); - if (rset.wasNull()) { - return null; - } - return v; + return rset.wasNull() ? null : v; } @Override public Integer getInt() throws SQLException { int v = rset.getInt(pos()); - if (rset.wasNull()) { - return null; - } - return v; + return rset.wasNull() ? null : v; } - @Override public Long getLong() throws SQLException { long v = rset.getLong(pos()); - if (rset.wasNull()) { - return null; - } - return v; + return rset.wasNull() ? null : v; } - public Ref getRef() throws SQLException { return rset.getRef(pos()); } - @Override public Short getShort() throws SQLException { short s = rset.getShort(pos()); - if (rset.wasNull()) { - return null; - } - return s; + return rset.wasNull() ? null : s; } - @Override public String getString() throws SQLException { return rset.getString(pos()); } - @Override public Time getTime() throws SQLException { Calendar cal = dataTimeZone.getTimeZone(); @@ -196,14 +165,10 @@ public class RsetDataReader implements DataReader { @Override public String getStringFromStream() throws SQLException { Reader reader = rset.getCharacterStream(pos()); - if (reader == null) { - return null; - } - return readStringLob(reader); + return reader == null ? null : readStringLob(reader); } protected String readStringLob(Reader reader) throws SQLException { - char[] buffer = new char[clobBufferSize]; int readLength; StringBuilder out = new StringBuilder(stringInitialSize); @@ -215,7 +180,6 @@ public class RsetDataReader implements DataReader { } catch (IOException e) { throw new SQLException("IOException reading Clob " + e.getMessage()); } - return out.toString(); } @@ -226,19 +190,16 @@ public class RsetDataReader implements DataReader { } protected byte[] getBinaryLob(InputStream in) throws SQLException { - if (in == null) { return null; } try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { - byte[] buf = new byte[bufferSize]; int len; while ((len = in.read(buf, 0, buf.length)) != -1) { out.write(buf, 0, len); } byte[] data = out.toByteArray(); - if (data.length == 0) { data = null; } From 3add4472ef09f101fca17384ff523c98f070e77d Mon Sep 17 00:00:00 2001 From: rbygrave Date: Wed, 9 Jun 2021 14:58:08 +1200 Subject: [PATCH 33/72] Refactor internals DefaultRelationalQueryEngine.findListMapper() - Remove findListMapper() method - Introduce RowReader interface - Combine findList internals for both SqlRow and RowMapper use --- .../server/core/RelationalQueryEngine.java | 11 +- .../server/core/RelationalQueryRequest.java | 31 ++---- .../ebeaninternal/server/core/RowReader.java | 14 +++ .../query/DefaultRelationalQueryEngine.java | 105 ++++++------------ .../querydefn/DefaultRelationalQuery.java | 19 +++- .../tests/query/sqlquery/SqlQueryTests.java | 3 +- 6 files changed, 76 insertions(+), 107 deletions(-) create mode 100644 ebean-core/src/main/java/io/ebeaninternal/server/core/RowReader.java diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryEngine.java index 40f6d1a3c..86a439def 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryEngine.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryEngine.java @@ -20,17 +20,17 @@ public interface RelationalQueryEngine { /** * Find a list of beans using relational query. */ - List findList(RelationalQueryRequest request); + List findList(RelationalQueryRequest request, RowReader reader); /** * Find each query using relational query. */ - void findEach(RelationalQueryRequest request, Consumer consumer); + void findEach(RelationalQueryRequest request, RowReader reader, Consumer consumer); /** * Find each while query using relational query. */ - void findEach(RelationalQueryRequest request, Predicate consumer); + void findEach(RelationalQueryRequest request, RowReader reader, Predicate consumer); /** * Find single attribute. @@ -47,11 +47,6 @@ public interface RelationalQueryEngine { */ T findOneMapper(RelationalQueryRequest request, RowMapper mapper); - /** - * Find list via mapper. - */ - List findListMapper(RelationalQueryRequest request, RowMapper mapper); - /** * Find each via raw consumer. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java index 9f1413031..c0371e546 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java @@ -1,10 +1,6 @@ package io.ebeaninternal.server.core; -import io.ebean.RowConsumer; -import io.ebean.RowMapper; -import io.ebean.SqlQuery; -import io.ebean.SqlRow; -import io.ebean.Transaction; +import io.ebean.*; import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.api.SpiSqlBinding; @@ -63,7 +59,7 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest { List findListMapper(RowMapper mapper) { flushJdbcBatchOnQuery(); - return queryEngine.findListMapper(this, mapper); + return queryEngine.findList(this, () -> mapper.map(resultSet, rows++)); } T findOneMapper(RowMapper mapper) { @@ -83,17 +79,17 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest { public void findEach(Consumer consumer) { flushJdbcBatchOnQuery(); - queryEngine.findEach(this, consumer); + queryEngine.findEach(this, this::createNewRow, consumer); } public void findEachWhile(Predicate consumer) { flushJdbcBatchOnQuery(); - queryEngine.findEach(this, consumer); + queryEngine.findEach(this, this::createNewRow, consumer); } public List findList() { flushJdbcBatchOnQuery(); - return queryEngine.findList(this); + return queryEngine.findList(this, this::createNewRow); } /** @@ -135,21 +131,14 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest { return resultSet; } - public void incrementRows() { - rows++; - } - @Override public boolean next() throws SQLException { - return resultSet.next(); - } - - public List mapList(RowMapper mapper) throws SQLException { - List list = new ArrayList<>(); - while (next()) { - list.add(mapper.map(resultSet, rows++)); + if (!resultSet.next()) { + return false; + } else { + rows++; + return true; } - return list; } public T mapOne(RowMapper mapper) throws SQLException { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/RowReader.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/RowReader.java new file mode 100644 index 000000000..2a7d0b119 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/RowReader.java @@ -0,0 +1,14 @@ +package io.ebeaninternal.server.core; + +import java.sql.SQLException; + +/** + * Read a row building a result for that row. + */ +public interface RowReader { + + /** + * Build and return a result for a row. + */ + T read() throws SQLException; +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultRelationalQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultRelationalQueryEngine.java index f49df1d20..655c0863a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultRelationalQueryEngine.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultRelationalQueryEngine.java @@ -3,6 +3,7 @@ package io.ebeaninternal.server.query; import io.ebean.RowConsumer; import io.ebean.RowMapper; import io.ebean.SqlRow; +import io.ebean.core.type.DataReader; import io.ebean.core.type.ScalarType; import io.ebean.meta.MetricVisitor; import io.ebean.metric.MetricFactory; @@ -10,10 +11,10 @@ import io.ebean.metric.TimedMetricMap; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.server.core.RelationalQueryEngine; import io.ebeaninternal.server.core.RelationalQueryRequest; +import io.ebeaninternal.server.core.RowReader; import io.ebeaninternal.server.persist.Binder; import javax.persistence.PersistenceException; -import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -59,12 +60,11 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine { } @Override - public void findEach(RelationalQueryRequest request, Predicate consumer) { - + public void findEach(RelationalQueryRequest request, RowReader reader, Predicate consumer) { try { request.executeSql(binder, SpiQuery.Type.ITERATE); while (request.next()) { - if (!consumer.test(readRow(request))) { + if (!consumer.test(reader.read())) { break; } } @@ -79,12 +79,11 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine { } @Override - public void findEach(RelationalQueryRequest request, Consumer consumer) { - + public void findEach(RelationalQueryRequest request, RowReader reader, Consumer consumer) { try { request.executeSql(binder, SpiQuery.Type.ITERATE); while (request.next()) { - consumer.accept(readRow(request)); + consumer.accept(reader.read()); } request.logSummary(); @@ -112,22 +111,6 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine { } } - @Override - public List findListMapper(RelationalQueryRequest request, RowMapper mapper) { - try { - request.executeSql(binder, SpiQuery.Type.LIST); - List list = request.mapList(mapper); - request.logSummary(); - return list; - - } catch (Exception e) { - throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e); - - } finally { - request.close(); - } - } - @Override public void findEachRow(RelationalQueryRequest request, RowConsumer consumer) { try { @@ -143,51 +126,17 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine { } } - @SuppressWarnings("unchecked") - @Override - public List findSingleAttributeList(RelationalQueryRequest request, Class cls) { - ScalarType scalarType = (ScalarType) binder.getScalarType(cls); - return findScalarList(request, scalarType); - } - - private List findScalarList(RelationalQueryRequest request, ScalarType scalarType) { - try { - request.executeSql(binder, SpiQuery.Type.ATTRIBUTE); - List list = new ArrayList<>(); - while (request.next()) { - request.incrementRows(); - list.add(scalarType.read(binder.createDataReader(request.getResultSet()))); - } - - request.logSummary(); - return list; - - } catch (Exception e) { - throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e); - - } finally { - request.close(); - } - } - @SuppressWarnings("unchecked") @Override public T findSingleAttribute(RelationalQueryRequest request, Class cls) { - ScalarType scalarType = (ScalarType) binder.getScalarType(cls); - return findScalar(request, scalarType); - } - - private T findScalar(RelationalQueryRequest request, ScalarType scalarType) { try { request.executeSql(binder, SpiQuery.Type.ATTRIBUTE); - + final DataReader dataReader = binder.createDataReader(request.getResultSet()); T value = null; - if (request.next()) { - request.incrementRows(); - value = scalarType.read(binder.createDataReader(request.getResultSet())); + if (dataReader.next()) { + value = scalarType.read(dataReader); } - request.logSummary(); return value; @@ -199,16 +148,17 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine { } } + @SuppressWarnings("unchecked") @Override - public List findList(RelationalQueryRequest request) { - + public List findSingleAttributeList(RelationalQueryRequest request, Class cls) { + ScalarType scalarType = (ScalarType) binder.getScalarType(cls); try { - request.executeSql(binder, SpiQuery.Type.LIST); - List rows = new ArrayList<>(); - while (request.next()) { - rows.add(readRow(request)); + request.executeSql(binder, SpiQuery.Type.ATTRIBUTE); + final DataReader dataReader = binder.createDataReader(request.getResultSet()); + List rows = new ArrayList<>(); + while (dataReader.next()) { + rows.add(scalarType.read(dataReader)); } - request.logSummary(); return rows; @@ -220,11 +170,22 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine { } } - /** - * Read the row from the ResultSet and return as a MapBean. - */ - private SqlRow readRow(RelationalQueryRequest request) throws SQLException { - return request.createNewRow(); + @Override + public List findList(RelationalQueryRequest request, RowReader reader) { + try { + request.executeSql(binder, SpiQuery.Type.LIST); + List rows = new ArrayList<>(); + while (request.next()) { + rows.add(reader.read()); + } + request.logSummary(); + return rows; + } catch (Exception e) { + throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e); + + } finally { + request.close(); + } } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java index ec5b3c01a..4cf446d0f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java @@ -23,9 +23,9 @@ public class DefaultRelationalQuery implements SpiSqlQuery { private final transient SpiEbeanServer server; - private String label; + private final String query; - private String query; + private String label; private int firstRow; @@ -208,19 +208,25 @@ public class DefaultRelationalQuery implements SpiSqlQuery { return query; } - T mapperFindOne(RowMapper mapper) { + private T mapperFindOne(RowMapper mapper) { return server.findOneMapper(this, mapper); } - List mapperFindList(RowMapper mapper) { + private List mapperFindList(RowMapper mapper) { return server.findListMapper(this, mapper); } + private void mapperFindEach(RowMapper mapper, Consumer consumer) { + server.findEachRow(this, (resultSet, rowNum) -> consumer.accept(mapper.map(resultSet, rowNum))); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) @Override public TypeQuery mapToScalar(Class attributeType) { return new Scalar(attributeType); } + @SuppressWarnings({"rawtypes", "unchecked"}) @Override public TypeQuery mapTo(RowMapper mapper) { return new Mapper(mapper); @@ -272,5 +278,10 @@ public class DefaultRelationalQuery implements SpiSqlQuery { public List findList() { return mapperFindList(mapper); } + + //@Override + public void findEach(Consumer consumer) { + mapperFindEach(mapper, consumer); + } } } diff --git a/ebean-core/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java b/ebean-core/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java index c79f07d21..7745b4ca6 100644 --- a/ebean-core/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java +++ b/ebean-core/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java @@ -2,7 +2,6 @@ package org.tests.query.sqlquery; import io.ebean.BaseTestCase; import io.ebean.DB; -import io.ebean.Ebean; import io.ebean.RowMapper; import io.ebean.SqlQuery; import io.ebean.SqlRow; @@ -361,7 +360,7 @@ public class SqlQueryTests extends BaseTestCase { ResetBasicData.reset(); - int expectedRows = Ebean.find(Order.class).findCount(); + int expectedRows = DB.find(Order.class).findCount(); final AtomicInteger count = new AtomicInteger(); From 1df2ccb1c6e84754f799ef8ecd60baabe1694bf5 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Wed, 9 Jun 2021 15:19:35 +1200 Subject: [PATCH 34/72] Refactor internals DefaultRelationalQueryEngine remove unnecessary method - Remove findEach() method that took RowReader and Consumer - Use findEachRow() instead --- .../server/core/RelationalQueryEngine.java | 25 +++++------ .../server/core/RelationalQueryRequest.java | 2 +- .../query/DefaultRelationalQueryEngine.java | 42 ++++++------------- 3 files changed, 23 insertions(+), 46 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryEngine.java index 86a439def..4e34a3894 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryEngine.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryEngine.java @@ -22,16 +22,21 @@ public interface RelationalQueryEngine { */ List findList(RelationalQueryRequest request, RowReader reader); - /** - * Find each query using relational query. - */ - void findEach(RelationalQueryRequest request, RowReader reader, Consumer consumer); - /** * Find each while query using relational query. */ void findEach(RelationalQueryRequest request, RowReader reader, Predicate consumer); + /** + * Find each via raw consumer. + */ + void findEachRow(RelationalQueryRequest request, RowConsumer mapper); + + /** + * Find one via mapper. + */ + T findOneMapper(RelationalQueryRequest request, RowMapper mapper); + /** * Find single attribute. */ @@ -42,16 +47,6 @@ public interface RelationalQueryEngine { */ List findSingleAttributeList(RelationalQueryRequest request, Class cls); - /** - * Find one via mapper. - */ - T findOneMapper(RelationalQueryRequest request, RowMapper mapper); - - /** - * Find each via raw consumer. - */ - void findEachRow(RelationalQueryRequest request, RowConsumer mapper); - /** * Collect SQL query execution statistics. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java index c0371e546..250186c2d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java @@ -79,7 +79,7 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest { public void findEach(Consumer consumer) { flushJdbcBatchOnQuery(); - queryEngine.findEach(this, this::createNewRow, consumer); + queryEngine.findEachRow(this, (resultSet, rowNum) -> consumer.accept(createNewRow())); } public void findEachWhile(Predicate consumer) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultRelationalQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultRelationalQueryEngine.java index 655c0863a..b150c9c4b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultRelationalQueryEngine.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultRelationalQueryEngine.java @@ -17,7 +17,6 @@ import io.ebeaninternal.server.persist.Binder; import javax.persistence.PersistenceException; import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; import java.util.function.Predicate; /** @@ -60,14 +59,10 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine { } @Override - public void findEach(RelationalQueryRequest request, RowReader reader, Predicate consumer) { + public void findEachRow(RelationalQueryRequest request, RowConsumer consumer) { try { request.executeSql(binder, SpiQuery.Type.ITERATE); - while (request.next()) { - if (!consumer.test(reader.read())) { - break; - } - } + request.mapEach(consumer); request.logSummary(); } catch (Exception e) { @@ -79,11 +74,13 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine { } @Override - public void findEach(RelationalQueryRequest request, RowReader reader, Consumer consumer) { + public void findEach(RelationalQueryRequest request, RowReader reader, Predicate consumer) { try { request.executeSql(binder, SpiQuery.Type.ITERATE); while (request.next()) { - consumer.accept(reader.read()); + if (!consumer.test(reader.read())) { + break; + } } request.logSummary(); @@ -112,12 +109,15 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine { } @Override - public void findEachRow(RelationalQueryRequest request, RowConsumer consumer) { + public List findList(RelationalQueryRequest request, RowReader reader) { try { request.executeSql(binder, SpiQuery.Type.LIST); - request.mapEach(consumer); + List rows = new ArrayList<>(); + while (request.next()) { + rows.add(reader.read()); + } request.logSummary(); - + return rows; } catch (Exception e) { throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e); @@ -170,22 +170,4 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine { } } - @Override - public List findList(RelationalQueryRequest request, RowReader reader) { - try { - request.executeSql(binder, SpiQuery.Type.LIST); - List rows = new ArrayList<>(); - while (request.next()) { - rows.add(reader.read()); - } - request.logSummary(); - return rows; - } catch (Exception e) { - throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e); - - } finally { - request.close(); - } - } - } From f3af43a9ceb2606b067ab711ac49eb53f84f562e Mon Sep 17 00:00:00 2001 From: rbygrave Date: Wed, 9 Jun 2021 15:49:48 +1200 Subject: [PATCH 35/72] Add support for findEach() on SqlQuery with RowMapper and scalar result --- .../src/main/java/io/ebean/SqlQuery.java | 5 +++ .../io/ebeaninternal/api/SpiEbeanServer.java | 5 +++ .../server/core/DefaultServer.java | 5 +++ .../server/core/RelationalQueryEngine.java | 9 ++++- .../server/core/RelationalQueryRequest.java | 12 ++++-- .../query/DefaultRelationalQueryEngine.java | 24 ++++++++++- .../querydefn/DefaultRelationalQuery.java | 11 ++++- .../ebeaninternal/api/TDSpiEbeanServer.java | 4 ++ .../tests/query/sqlquery/SqlQueryTests.java | 40 +++++++++++++++++++ 9 files changed, 107 insertions(+), 8 deletions(-) diff --git a/ebean-api/src/main/java/io/ebean/SqlQuery.java b/ebean-api/src/main/java/io/ebean/SqlQuery.java index 8b5452ee7..46b26c90a 100644 --- a/ebean-api/src/main/java/io/ebean/SqlQuery.java +++ b/ebean-api/src/main/java/io/ebean/SqlQuery.java @@ -365,5 +365,10 @@ public interface SqlQuery extends Serializable { * Return the list of values. */ List findList(); + + /** + * Find streaming the result effectively consuming a row at a time. + */ + void findEach(Consumer consumer); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java index deb9c281c..a2e572904 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java @@ -234,6 +234,11 @@ public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanCollect */ List findSingleAttributeList(SpiSqlQuery query, Class cls); + /** + * SqlQuery find single attribute streaming the result to a consumer. + */ + void findSingleAttributeEach(SpiSqlQuery query, Class cls, Consumer consumer); + /** * SqlQuery find one with mapper. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 6595d9816..81c16cf41 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -1606,6 +1606,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return executeSqlQuery((req) -> req.findOneMapper(mapper), query); } + @Override + public void findSingleAttributeEach(SpiSqlQuery query, Class cls, Consumer consumer) { + executeSqlQuery((req) -> req.findSingleAttributeEach(cls, consumer), query); + } + @Override public List findSingleAttributeList(SpiSqlQuery query, Class cls) { return executeSqlQuery((req) -> req.findSingleAttributeList(cls), query); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryEngine.java index 4e34a3894..e9d18797a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryEngine.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryEngine.java @@ -30,12 +30,12 @@ public interface RelationalQueryEngine { /** * Find each via raw consumer. */ - void findEachRow(RelationalQueryRequest request, RowConsumer mapper); + void findEach(RelationalQueryRequest request, RowConsumer mapper); /** * Find one via mapper. */ - T findOneMapper(RelationalQueryRequest request, RowMapper mapper); + T findOne(RelationalQueryRequest request, RowMapper mapper); /** * Find single attribute. @@ -47,6 +47,11 @@ public interface RelationalQueryEngine { */ List findSingleAttributeList(RelationalQueryRequest request, Class cls); + /** + * Find single attribute streaming the result to a consumer. + */ + void findSingleAttributeEach(RelationalQueryRequest request, Class cls, Consumer consumer); + /** * Collect SQL query execution statistics. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java index 250186c2d..9a0ceb153 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java @@ -53,7 +53,7 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest { boolean findEachRow(RowConsumer mapper) { flushJdbcBatchOnQuery(); - queryEngine.findEachRow(this, mapper); + queryEngine.findEach(this, mapper); return true; } @@ -64,7 +64,13 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest { T findOneMapper(RowMapper mapper) { flushJdbcBatchOnQuery(); - return queryEngine.findOneMapper(this, mapper); + return queryEngine.findOne(this, mapper); + } + + public boolean findSingleAttributeEach(Class cls, Consumer consumer) { + flushJdbcBatchOnQuery(); + queryEngine.findSingleAttributeEach(this, cls, consumer); + return true; } public List findSingleAttributeList(Class cls) { @@ -79,7 +85,7 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest { public void findEach(Consumer consumer) { flushJdbcBatchOnQuery(); - queryEngine.findEachRow(this, (resultSet, rowNum) -> consumer.accept(createNewRow())); + queryEngine.findEach(this, (resultSet, rowNum) -> consumer.accept(createNewRow())); } public void findEachWhile(Predicate consumer) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultRelationalQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultRelationalQueryEngine.java index b150c9c4b..78b9717d9 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultRelationalQueryEngine.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultRelationalQueryEngine.java @@ -17,6 +17,7 @@ import io.ebeaninternal.server.persist.Binder; import javax.persistence.PersistenceException; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import java.util.function.Predicate; /** @@ -59,7 +60,7 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine { } @Override - public void findEachRow(RelationalQueryRequest request, RowConsumer consumer) { + public void findEach(RelationalQueryRequest request, RowConsumer consumer) { try { request.executeSql(binder, SpiQuery.Type.ITERATE); request.mapEach(consumer); @@ -93,7 +94,7 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine { } @Override - public T findOneMapper(RelationalQueryRequest request, RowMapper mapper) { + public T findOne(RelationalQueryRequest request, RowMapper mapper) { try { request.executeSql(binder, SpiQuery.Type.BEAN); T value = request.mapOne(mapper); @@ -170,4 +171,23 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine { } } + @SuppressWarnings("unchecked") + @Override + public void findSingleAttributeEach(RelationalQueryRequest request, Class cls, Consumer consumer) { + ScalarType scalarType = (ScalarType) binder.getScalarType(cls); + try { + request.executeSql(binder, SpiQuery.Type.ATTRIBUTE); + final DataReader dataReader = binder.createDataReader(request.getResultSet()); + while (dataReader.next()) { + consumer.accept(scalarType.read(dataReader)); + } + request.logSummary(); + + } catch (Exception e) { + throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e); + + } finally { + request.close(); + } + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java index 4cf446d0f..182b1cfd4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java @@ -254,6 +254,15 @@ public class DefaultRelationalQuery implements SpiSqlQuery { public List findList() { return findSingleAttributeList(type); } + + @Override + public void findEach(Consumer consumer) { + scalarFindEach(type, consumer); + } + } + + private void scalarFindEach(Class type, Consumer consumer) { + server.findSingleAttributeEach(this, type, consumer); } private class Mapper implements SqlQuery.TypeQuery { @@ -279,7 +288,7 @@ public class DefaultRelationalQuery implements SpiSqlQuery { return mapperFindList(mapper); } - //@Override + @Override public void findEach(Consumer consumer) { mapperFindEach(mapper, consumer); } diff --git a/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java b/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java index 03ed3362f..71e73b59f 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java +++ b/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java @@ -493,6 +493,10 @@ public class TDSpiEbeanServer implements SpiEbeanServer { return null; } + @Override + public void findSingleAttributeEach(SpiSqlQuery query, Class cls, Consumer consumer) { + } + @Override public T findOneMapper(SpiSqlQuery query, RowMapper mapper) { return null; diff --git a/ebean-core/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java b/ebean-core/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java index 7745b4ca6..572ebf39e 100644 --- a/ebean-core/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java +++ b/ebean-core/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java @@ -39,6 +39,28 @@ public class SqlQueryTests extends BaseTestCase { assertThat(lineAmounts).isNotEmpty(); } + @Test + public void findSingleAttributeEach_decimal() { + + ResetBasicData.reset(); + + String sql = "select (unit_price * order_qty) from o_order_detail where unit_price > ? order by (unit_price * order_qty) desc"; + + AtomicLong counter = new AtomicLong(); + AtomicLong inc = new AtomicLong(); + + DB.sqlQuery(sql) + .setParameter(3) + .mapToScalar(BigDecimal.class) + .findEach(val -> { + counter.incrementAndGet(); + inc.addAndGet(val.longValue()); + }); + + assertThat(inc.get()).isGreaterThan(counter.get()); + assertThat(counter.get()).isGreaterThan(0); + } + @Test public void findSingleDecimal() { @@ -141,6 +163,24 @@ public class SqlQueryTests extends BaseTestCase { private static final CustMapper CUST_MAPPER = new CustMapper(); + @Test + public void findEach_mapper() { + + ResetBasicData.reset(); + + String sql = "select id, name, status from o_customer where name is not null"; + + AtomicInteger counter = new AtomicInteger(); + DB.sqlQuery(sql) + .mapTo(CUST_MAPPER) + .findEach(custDto -> { + counter.incrementAndGet(); + assertThat(custDto.name).isNotNull(); + }); + + assertThat(counter.get()).isGreaterThan(0); + } + @Test public void findOne_mapper() { From c8345ce09365d1a5b03cf59645fdd74b27c187d1 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Wed, 9 Jun 2021 16:21:15 +1200 Subject: [PATCH 36/72] No functional change - refactor internal toStream() to use closure rather than explicit Runnable type for closing the stream Conceptually javac closure might be better than an extra explicit type here --- .../ebeaninternal/server/core/DefaultServer.java | 16 +--------------- .../org/tests/query/TestQueryFindStream.java | 12 +++++++++--- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 81c16cf41..c39addd6a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -1444,21 +1444,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } private Stream toStream(QueryIterator queryIterator) { - return stream(spliteratorUnknownSize(queryIterator, Spliterator.ORDERED), false) - .onClose(new QueryIteratorClose(queryIterator)); - } - - private static class QueryIteratorClose implements Runnable { - private final QueryIterator iterator; - - private QueryIteratorClose(QueryIterator iterator) { - this.iterator = iterator; - } - - @Override - public void run() { - iterator.close(); - } + return stream(spliteratorUnknownSize(queryIterator, Spliterator.ORDERED), false).onClose(queryIterator::close); } @Override diff --git a/ebean-core/src/test/java/org/tests/query/TestQueryFindStream.java b/ebean-core/src/test/java/org/tests/query/TestQueryFindStream.java index b05ada969..c4a852f38 100644 --- a/ebean-core/src/test/java/org/tests/query/TestQueryFindStream.java +++ b/ebean-core/src/test/java/org/tests/query/TestQueryFindStream.java @@ -39,9 +39,7 @@ public class TestQueryFindStream extends BaseTestCase { @Test public void findLargeStream_basic() { - ResetBasicData.reset(); - try (Stream stream = DB.find(Customer.class) .findLargeStream()) { @@ -61,8 +59,16 @@ public class TestQueryFindStream extends BaseTestCase { } @Test - public void manualTest_findSteam_when_streamNotClosed_connectionLeak() { + public void manualTest_findSteam_when_closeWithResources() { + // confirm manually the stream is closed via try with resources block + try (Stream stream = DB.find(Customer.class).findStream()) { + // silly assert + assertThat(stream.hashCode()).isNotZero(); + } + } + @Test + public void manualTest_findSteam_when_streamNotClosed_connectionLeak() { Stream stream = DB.find(Customer.class).findStream(); // remember a steam MUST be closed or we leak resources // comment out the close(); below to leak a connection From 678743cc10312afc86a14fb3fd43aa72484b8139 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Wed, 9 Jun 2021 21:59:19 +1200 Subject: [PATCH 37/72] Use upper case E for class name of generated _Ebean$ModuleInfo --- .../io/ebean/querybean/generator/SimpleModuleInfoWriter.java | 2 +- .../io/ebean/querybean/generator/SimpleModuleInfoWriter.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleModuleInfoWriter.java b/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleModuleInfoWriter.java index 04d1c270c..a5683165d 100644 --- a/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleModuleInfoWriter.java +++ b/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleModuleInfoWriter.java @@ -24,7 +24,7 @@ class SimpleModuleInfoWriter { SimpleModuleInfoWriter(ProcessingContext processingContext) { this.processingContext = processingContext; this.factoryPackage = processingContext.getFactoryPackage(); - this.factoryShortName = "_ebean$ModuleInfo"; + this.factoryShortName = "_Ebean$ModuleInfo"; this.factoryFullName = factoryPackage + "." + factoryShortName; } diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleModuleInfoWriter.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleModuleInfoWriter.java index e7e2826eb..e9f8c30f0 100644 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleModuleInfoWriter.java +++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleModuleInfoWriter.java @@ -24,7 +24,7 @@ class SimpleModuleInfoWriter { SimpleModuleInfoWriter(ProcessingContext processingContext) { this.processingContext = processingContext; this.factoryPackage = processingContext.getFactoryPackage(); - this.factoryShortName = "_ebean$ModuleInfo"; + this.factoryShortName = "_Ebean$ModuleInfo"; this.factoryFullName = factoryPackage + "." + factoryShortName; } From 0b87ae5eac20bdc6a8121549563ddee306f28008 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Wed, 9 Jun 2021 23:38:10 +1200 Subject: [PATCH 38/72] No effective change - tidy BindParams --- .../src/main/java/io/ebeaninternal/api/BindParams.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) 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 a45d98e6d..afb1e44b8 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/BindParams.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/BindParams.java @@ -155,7 +155,6 @@ public class BindParams implements Serializable { * Set an In Out parameter using position. */ public void setParameter(int position, Object value, int outType) { - Param p = getParam(position); p.setInValue(value); p.setOutType(outType); @@ -178,8 +177,8 @@ public class BindParams implements Serializable { * Using position set the In value of a parameter. Note that for nulls you * must use setNullParameter. */ + @SuppressWarnings("rawtypes") public void setParameter(int position, Object value) { - Param p = getParam(position); if (value instanceof Collection) { // use of postgres ANY with positioned parameter @@ -214,7 +213,6 @@ public class BindParams implements Serializable { * Set a named In Out parameter. */ public void setParameter(String name, Object value, int outType) { - Param p = getParam(name); p.setInValue(value); p.setOutType(outType); @@ -232,7 +230,6 @@ public class BindParams implements Serializable { * Set a named In parameter that is not null. */ public Param setParameter(String name, Object value) { - Param p = getParam(name); p.setInValue(value); return p; @@ -299,7 +296,6 @@ public class BindParams implements Serializable { * Return true if the bind hash and count has not changed. */ public boolean isSameBindHash() { - if (bindHash == null) { bindHash = calcQueryPlanHash(); return false; @@ -330,10 +326,6 @@ public class BindParams implements Serializable { private final StringBuilder preparedSql; - public OrderedList() { - this(new ArrayList<>()); - } - public OrderedList(List paramList) { this.paramList = paramList; this.preparedSql = new StringBuilder(); From 33ac1923ff1f96c744abb9aae36b02a5aa078ac4 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Wed, 9 Jun 2021 23:40:48 +1200 Subject: [PATCH 39/72] No effective change - refactor move Str into server.util package Allows the removal of io.ebeaninternal.server.lib package as that was the only thing in there. --- .../server/core/AbstractSqlQueryRequest.java | 2 +- .../server/core/PersistRequestUpdateSql.java | 2 +- .../java/io/ebeaninternal/server/lib/package.html | 13 ------------- .../server/persist/dml/DmlHandler.java | 2 +- .../io/ebeaninternal/server/query/CQueryEngine.java | 2 +- .../io/ebeaninternal/server/query/CQueryPlan.java | 2 +- .../server/transaction/JdbcTransaction.java | 2 +- .../server/transaction/SavepointTransaction.java | 2 +- .../io/ebeaninternal/server/{lib => util}/Str.java | 3 +-- 9 files changed, 8 insertions(+), 22 deletions(-) delete mode 100644 ebean-core/src/main/java/io/ebeaninternal/server/lib/package.html rename ebean-core/src/main/java/io/ebeaninternal/server/{lib => util}/Str.java (96%) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java index 3c1050faf..8a5e9214d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java @@ -3,7 +3,7 @@ package io.ebeaninternal.server.core; import io.ebean.Transaction; import io.ebean.util.JdbcClose; import io.ebeaninternal.api.*; -import io.ebeaninternal.server.lib.Str; +import io.ebeaninternal.server.util.Str; import io.ebeaninternal.server.persist.Binder; import io.ebeaninternal.server.persist.TrimLogSql; import io.ebeaninternal.server.util.BindParamsParser; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestUpdateSql.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestUpdateSql.java index 80471f5aa..f16a9d7cd 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestUpdateSql.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestUpdateSql.java @@ -3,7 +3,7 @@ package io.ebeaninternal.server.core; import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.api.SpiSqlUpdate; import io.ebeaninternal.api.SpiTransaction; -import io.ebeaninternal.server.lib.Str; +import io.ebeaninternal.server.util.Str; import io.ebeaninternal.server.persist.BatchControl; import io.ebeaninternal.server.persist.PersistExecute; import io.ebeaninternal.server.persist.TrimLogSql; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/lib/package.html b/ebean-core/src/main/java/io/ebeaninternal/server/lib/package.html deleted file mode 100644 index a114cce65..000000000 --- a/ebean-core/src/main/java/io/ebeaninternal/server/lib/package.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - AvajeLib - - -Core services for an application. -

- Core services including Logging, Deployment properties and Background thread for - running frequent tasks. -

- - diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java index ac870b65e..189613808 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java @@ -3,7 +3,7 @@ package io.ebeaninternal.server.persist.dml; import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.server.core.PersistRequestBean; import io.ebeaninternal.server.deploy.BeanProperty; -import io.ebeaninternal.server.lib.Str; +import io.ebeaninternal.server.util.Str; import io.ebeaninternal.server.persist.BatchedPstmt; import io.ebeaninternal.server.persist.BatchedPstmtHolder; import io.ebeaninternal.server.persist.dmlbind.BindableRequest; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java index 6e4804ca6..c632c9aeb 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java @@ -16,7 +16,7 @@ import io.ebeaninternal.server.core.DiffHelp; import io.ebeaninternal.server.core.OrmQueryRequest; import io.ebeaninternal.server.core.SpiResultSet; import io.ebeaninternal.server.deploy.BeanDescriptor; -import io.ebeaninternal.server.lib.Str; +import io.ebeaninternal.server.util.Str; import io.ebeaninternal.server.persist.Binder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java index 86e8d209d..295cbe7c2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java @@ -13,7 +13,7 @@ import io.ebeaninternal.api.SpiQueryBindCapture; import io.ebeaninternal.api.SpiQueryPlan; import io.ebeaninternal.server.core.OrmQueryRequest; import io.ebeaninternal.server.core.timezone.DataTimeZone; -import io.ebeaninternal.server.lib.Str; +import io.ebeaninternal.server.util.Str; import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot; import io.ebeaninternal.server.type.DataBind; import io.ebeaninternal.server.type.DataBindCapture; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java index 7d3b3ddf1..9f59c44bd 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java @@ -14,7 +14,7 @@ import io.ebeaninternal.api.TransactionEvent; import io.ebeaninternal.api.TxnProfileEventCodes; import io.ebeaninternal.server.core.PersistDeferredRelationship; import io.ebeaninternal.server.core.PersistRequestBean; -import io.ebeaninternal.server.lib.Str; +import io.ebeaninternal.server.util.Str; import io.ebeaninternal.server.persist.BatchControl; import io.ebeaninternal.server.persist.BatchedSqlException; import io.ebeanservice.docstore.api.DocStoreTransaction; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/SavepointTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/SavepointTransaction.java index b50122f5c..dafaa05b5 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/SavepointTransaction.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/SavepointTransaction.java @@ -2,7 +2,7 @@ package io.ebeaninternal.server.transaction; import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.api.SpiTransactionProxy; -import io.ebeaninternal.server.lib.Str; +import io.ebeaninternal.server.util.Str; import javax.persistence.PersistenceException; import java.sql.Connection; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/lib/Str.java b/ebean-core/src/main/java/io/ebeaninternal/server/util/Str.java similarity index 96% rename from ebean-core/src/main/java/io/ebeaninternal/server/lib/Str.java rename to ebean-core/src/main/java/io/ebeaninternal/server/util/Str.java index 02b3d9e37..56788dc98 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/lib/Str.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/util/Str.java @@ -1,4 +1,4 @@ -package io.ebeaninternal.server.lib; +package io.ebeaninternal.server.util; /** * String utility for adding strings together. @@ -11,7 +11,6 @@ public class Str { * Append strings together. */ public static String add(String s0, String s1, String... args) { - // determine a decent buffer size int len = 16 + s0.length(); if (s1 != null) { From 4fd462b1c8cf4f8fc0ac1f30940a6680e52b59b5 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Thu, 10 Jun 2021 13:34:33 +1200 Subject: [PATCH 40/72] #2244 - ENH: Add SQL Server support for transparent encryption via EncryptByPassPhrase, DecryptByPassPhrase --- .../config/dbplatform/AbstractDbEncrypt.java | 6 --- .../config/dbplatform/h2/H2DbEncrypt.java | 2 - .../dbplatform/mysql/MySqlDbEncrypt.java | 2 - .../dbplatform/oracle/OracleDbEncrypt.java | 1 - .../sqlserver/SqlServerBasePlatform.java | 1 + .../sqlserver/SqlServerDbEncrypt.java | 46 +++++++++++++++++++ .../org/tests/basic/encrypt/TestEncrypt.java | 6 +-- .../org/tests/model/basic/EBasicEncrypt.java | 2 +- 8 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 ebean-api/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerDbEncrypt.java diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/AbstractDbEncrypt.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/AbstractDbEncrypt.java index be2c0941e..6f8e27556 100644 --- a/ebean-api/src/main/java/io/ebean/config/dbplatform/AbstractDbEncrypt.java +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/AbstractDbEncrypt.java @@ -9,9 +9,6 @@ import java.sql.Types; * functions for varchar, date and timestamp. If they are left null then that is * treated as though that data type can not be encrypted in the DB and will * instead use java client encryption. - *

- * - * @author rbygrave */ public abstract class AbstractDbEncrypt implements DbEncrypt { @@ -47,13 +44,10 @@ public abstract class AbstractDbEncrypt implements DbEncrypt { case Types.CHAR: case Types.LONGVARCHAR: return varcharEncryptFunction; - case Types.DATE: return dateEncryptFunction; - case Types.TIMESTAMP: return timestampEncryptFunction; - default: return null; } diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/h2/H2DbEncrypt.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/h2/H2DbEncrypt.java index ec7b1d9a7..f3eeeffa0 100644 --- a/ebean-api/src/main/java/io/ebean/config/dbplatform/h2/H2DbEncrypt.java +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/h2/H2DbEncrypt.java @@ -5,8 +5,6 @@ import io.ebean.config.dbplatform.DbEncryptFunction; /** * H2 encryption support via encrypt decrypt function. - * - * @author rbygrave */ public class H2DbEncrypt extends AbstractDbEncrypt { diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/mysql/MySqlDbEncrypt.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/mysql/MySqlDbEncrypt.java index 2a22d8ab2..f8717936b 100644 --- a/ebean-api/src/main/java/io/ebean/config/dbplatform/mysql/MySqlDbEncrypt.java +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/mysql/MySqlDbEncrypt.java @@ -5,8 +5,6 @@ import io.ebean.config.dbplatform.DbEncryptFunction; /** * MySql aes_encrypt aes_decrypt based encryption support. - * - * @author rbygrave */ public class MySqlDbEncrypt extends AbstractDbEncrypt { diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/OracleDbEncrypt.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/OracleDbEncrypt.java index e1194ac37..2eae251a9 100644 --- a/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/OracleDbEncrypt.java +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/OracleDbEncrypt.java @@ -60,7 +60,6 @@ public class OracleDbEncrypt extends AbstractDbEncrypt { * @param decryptFunction the decrypt stored procedure */ public OracleDbEncrypt(String encryptFunction, String decryptFunction) { - this.varcharEncryptFunction = new OraVarcharFunction(encryptFunction, decryptFunction); this.dateEncryptFunction = new OraDateFunction(encryptFunction, decryptFunction); } diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerBasePlatform.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerBasePlatform.java index 8a9c5b4be..4490c148d 100644 --- a/ebean-api/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerBasePlatform.java +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerBasePlatform.java @@ -25,6 +25,7 @@ abstract class SqlServerBasePlatform extends DatabasePlatform { this.platform = Platform.SQLSERVER; // disable persistBatchOnCascade mode for // SQL Server unless we are using sequences + this.dbEncrypt = new SqlServerDbEncrypt(); this.persistBatchOnCascade = PersistBatch.NONE; this.idInExpandedForm = true; this.selectCountWithAlias = true; diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerDbEncrypt.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerDbEncrypt.java new file mode 100644 index 000000000..5647483cd --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerDbEncrypt.java @@ -0,0 +1,46 @@ +package io.ebean.config.dbplatform.sqlserver; + +import io.ebean.config.dbplatform.AbstractDbEncrypt; +import io.ebean.config.dbplatform.DbEncryptFunction; + +/** + * SQL Server EncryptByPassPhrase DecryptByPassPhrase based encryption support. + */ +public class SqlServerDbEncrypt extends AbstractDbEncrypt { + + public SqlServerDbEncrypt() { + this.varcharEncryptFunction = new VarcharFunction(); + this.dateEncryptFunction = new DateFunction(); + } + + @Override + public boolean isBindEncryptDataFirst() { + return false; + } + + private static class VarcharFunction implements DbEncryptFunction { + + @Override + public String getDecryptSql(String columnWithTableAlias) { + return "convert(nvarchar,DecryptByPassPhrase(?," + columnWithTableAlias + "))"; + } + + @Override + public String getEncryptBindSql() { + return "EncryptByPassPhrase(?,?)"; + } + } + + private static class DateFunction implements DbEncryptFunction { + + @Override + public String getDecryptSql(String columnWithTableAlias) { + return "cast(convert(nvarchar,DecryptByPassPhrase(?," + columnWithTableAlias + ")) as date)"; + } + + @Override + public String getEncryptBindSql() { + return "EncryptByPassPhrase(?,format(?,'yyyy-MM-dd'))"; + } + } +} diff --git a/ebean-core/src/test/java/org/tests/basic/encrypt/TestEncrypt.java b/ebean-core/src/test/java/org/tests/basic/encrypt/TestEncrypt.java index 80b4c9061..fb80fcabb 100644 --- a/ebean-core/src/test/java/org/tests/basic/encrypt/TestEncrypt.java +++ b/ebean-core/src/test/java/org/tests/basic/encrypt/TestEncrypt.java @@ -22,7 +22,7 @@ public class TestEncrypt extends BaseTestCase { @Test - @ForPlatform(Platform.H2) // only run this on H2 - PGCrypto not happy on CI server + @ForPlatform({Platform.H2, Platform.SQLSERVER}) // only run this on H2 - PGCrypto not happy on CI server public void testQueryBind() { LoggedSqlCollector.start(); @@ -36,7 +36,7 @@ public class TestEncrypt extends BaseTestCase { } @Test - @ForPlatform(Platform.H2) // only run this on H2 - PGCrypto not happy on CI server + @ForPlatform({Platform.H2, Platform.SQLSERVER}) // only run this on H2 - PGCrypto not happy on CI server public void testQueryJoin() { LoggedSqlCollector.start(); @@ -99,7 +99,7 @@ public class TestEncrypt extends BaseTestCase { } @Test - @ForPlatform(Platform.H2) + @ForPlatform({Platform.H2, Platform.SQLSERVER}) public void test() { DB.find(EBasicEncrypt.class).delete(); diff --git a/ebean-core/src/test/java/org/tests/model/basic/EBasicEncrypt.java b/ebean-core/src/test/java/org/tests/model/basic/EBasicEncrypt.java index 859212781..31cbcab7a 100644 --- a/ebean-core/src/test/java/org/tests/model/basic/EBasicEncrypt.java +++ b/ebean-core/src/test/java/org/tests/model/basic/EBasicEncrypt.java @@ -28,7 +28,7 @@ public class EBasicEncrypt { @Encrypted(dbLength = 80) String description; - @Encrypted(dbLength = 20) + @Encrypted(dbLength = 80) Date dob; @Enumerated(EnumType.ORDINAL) From 37dff62fa9ed60f54b81793996d8c125eee8456c Mon Sep 17 00:00:00 2001 From: rbygrave Date: Thu, 10 Jun 2021 14:55:31 +1200 Subject: [PATCH 41/72] #2246 - ENH: Add DtoQuery findStream() and findIterate() --- .../src/main/java/io/ebean/DtoQuery.java | 21 ++++++ .../src/main/java/io/ebean/QueryIterator.java | 2 +- .../io/ebeaninternal/api/SpiEbeanServer.java | 21 +++--- .../server/core/DefaultServer.java | 26 ++++--- .../server/core/DtoQueryRequest.java | 6 ++ .../server/query/DtoQueryEngine.java | 11 +++ .../server/query/DtoQueryIterator.java | 55 +++++++++++++++ .../server/querydefn/DefaultDtoQuery.java | 12 ++++ .../src/test/java/io/ebean/DtoQuery2Test.java | 70 +++++++++++++++++++ .../ebeaninternal/api/TDSpiEbeanServer.java | 10 +++ 10 files changed, 216 insertions(+), 18 deletions(-) create mode 100644 ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryIterator.java diff --git a/ebean-api/src/main/java/io/ebean/DtoQuery.java b/ebean-api/src/main/java/io/ebean/DtoQuery.java index 33f439b93..95ad6eff5 100644 --- a/ebean-api/src/main/java/io/ebean/DtoQuery.java +++ b/ebean-api/src/main/java/io/ebean/DtoQuery.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Stream; /** * Query for performing native SQL queries that return DTO Bean's. @@ -45,6 +46,26 @@ public interface DtoQuery { @Nonnull List findList(); + /** + * Execute the query iterating a row at a time. + *

+ * Note that the QueryIterator holds resources related to the underlying + * resultSet and potentially connection and MUST be closed. We should use + * QueryIterator in a try with resource block. + */ + @Nonnull + QueryIterator findIterate(); + + /** + * Execute the query returning a Stream. + *

+ * Note that the Stream holds resources related to the underlying + * resultSet and potentially connection and MUST be closed. We should use + * the Stream in a try with resource block. + */ + @Nonnull + Stream findStream(); + /** * Execute the query iterating a row at a time. *

diff --git a/ebean-api/src/main/java/io/ebean/QueryIterator.java b/ebean-api/src/main/java/io/ebean/QueryIterator.java index c158e62c6..2ac789073 100644 --- a/ebean-api/src/main/java/io/ebean/QueryIterator.java +++ b/ebean-api/src/main/java/io/ebean/QueryIterator.java @@ -56,7 +56,7 @@ import java.util.Iterator; * * @param the type of entity bean in the iteration */ -public interface QueryIterator extends Iterator, java.io.Closeable { +public interface QueryIterator extends Iterator, AutoCloseable { /** * Returns true if the iteration has more elements. diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java index a2e572904..daf6418d0 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java @@ -1,14 +1,6 @@ package io.ebeaninternal.api; -import io.ebean.DtoQuery; -import io.ebean.EbeanServer; -import io.ebean.ExtendedServer; -import io.ebean.PersistenceContextScope; -import io.ebean.Query; -import io.ebean.RowConsumer; -import io.ebean.RowMapper; -import io.ebean.Transaction; -import io.ebean.TxScope; +import io.ebean.*; import io.ebean.bean.BeanCollectionLoader; import io.ebean.bean.CallOrigin; import io.ebean.config.DatabaseConfig; @@ -25,6 +17,7 @@ import io.ebeaninternal.server.transaction.RemoteTransactionEvent; import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Stream; /** * Service Provider extension to EbeanServer. @@ -254,6 +247,16 @@ public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanCollect */ void findEachRow(SpiSqlQuery query, RowConsumer consumer); + /** + * DTO findIterate query. + */ + QueryIterator findDtoIterate(SpiDtoQuery query); + + /** + * DTO findStream query. + */ + Stream findDtoStream(SpiDtoQuery query); + /** * DTO findList query. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index c39addd6a..231f0b718 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -1433,14 +1433,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Nonnull @Override public Stream findStream(Query query, Transaction transaction) { - SpiOrmQueryRequest request = createQueryRequest(Type.ITERATE, query, transaction); - try { - request.initTransIfRequired(); - return toStream(request.findIterate()); - } catch (RuntimeException ex) { - request.endTransIfRequired(); - throw ex; - } + return toStream(findIterate(query, transaction)); } private Stream toStream(QueryIterator queryIterator) { @@ -1640,6 +1633,23 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } + @Override + public QueryIterator findDtoIterate(SpiDtoQuery query) { + DtoQueryRequest request = new DtoQueryRequest<>(this, dtoQueryEngine, query); + try { + request.initTransIfRequired(); + return request.findIterate(); + } catch (RuntimeException ex) { + request.endTransIfRequired(); + throw ex; + } + } + + @Override + public Stream findDtoStream(SpiDtoQuery query) { + return toStream(findDtoIterate(query)); + } + @Override public List findDtoList(SpiDtoQuery query) { DtoQueryRequest request = new DtoQueryRequest<>(this, dtoQueryEngine, query); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java index b988c83b5..f4cdbc236 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.core; +import io.ebean.QueryIterator; import io.ebean.core.type.DataReader; import io.ebeaninternal.api.SpiDtoQuery; import io.ebeaninternal.api.SpiEbeanServer; @@ -90,6 +91,11 @@ public final class DtoQueryRequest extends AbstractSqlQueryRequest { } } + public QueryIterator findIterate() { + flushJdbcBatchOnQuery(); + return queryEngine.findIterate(this); + } + public void findEach(Consumer consumer) { flushJdbcBatchOnQuery(); queryEngine.findEach(this, consumer); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryEngine.java index bbd111f1c..46de28b8b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryEngine.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryEngine.java @@ -1,10 +1,12 @@ package io.ebeaninternal.server.query; +import io.ebean.QueryIterator; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.server.core.DtoQueryRequest; import io.ebeaninternal.server.persist.Binder; import javax.persistence.PersistenceException; +import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -34,6 +36,15 @@ public class DtoQueryEngine { } } + public QueryIterator findIterate(DtoQueryRequest request) { + try { + request.executeSql(binder, SpiQuery.Type.ITERATE); + return new DtoQueryIterator<>(request); + } catch (SQLException e) { + throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e); + } + } + public void findEach(DtoQueryRequest request, Consumer consumer) { try { request.executeSql(binder, SpiQuery.Type.ITERATE); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryIterator.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryIterator.java new file mode 100644 index 000000000..cb22fdd3a --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryIterator.java @@ -0,0 +1,55 @@ +package io.ebeaninternal.server.query; + +import io.ebean.QueryIterator; +import io.ebeaninternal.server.core.DtoQueryRequest; + +import javax.persistence.PersistenceException; +import java.sql.SQLException; + +class DtoQueryIterator implements QueryIterator { + + private final DtoQueryRequest request; + private boolean closed; + + DtoQueryIterator(DtoQueryRequest request) { + this.request = request; + } + + @Override + public boolean hasNext() { + boolean result = false; + try { + result = request.next(); + return result; + } catch (SQLException e) { + throw new PersistenceException(e); + } finally { + if (!result) { + close(); + } + } + } + + @Override + public T next() { + try { + return request.readNextBean(); + } catch (SQLException e) { + throw new PersistenceException(e); + } + } + + @Override + public void close() { + if (!closed) { + closed = true; + request.close(); + request.endTransIfRequired(); + } + } + + @Override + public void remove() { + throw new IllegalStateException("remove() not allowed"); + } +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultDtoQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultDtoQuery.java index d5877572a..9afcb961f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultDtoQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultDtoQuery.java @@ -2,6 +2,7 @@ package io.ebeaninternal.server.querydefn; import io.ebean.DtoQuery; import io.ebean.ProfileLocation; +import io.ebean.QueryIterator; import io.ebean.Transaction; import io.ebeaninternal.api.BindParams; import io.ebeaninternal.api.SpiDtoQuery; @@ -15,6 +16,7 @@ import java.util.List; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Stream; /** * Default implementation of DtoQuery. @@ -112,6 +114,16 @@ public class DefaultDtoQuery implements SpiDtoQuery { server.findDtoEachWhile(this, consumer); } + @Override + public QueryIterator findIterate() { + return server.findDtoIterate(this); + } + + @Override + public Stream findStream() { + return server.findDtoStream(this); + } + @Override public List findList() { return server.findDtoList(this); diff --git a/ebean-core/src/test/java/io/ebean/DtoQuery2Test.java b/ebean-core/src/test/java/io/ebean/DtoQuery2Test.java index 679fddd63..ba30e9a5c 100644 --- a/ebean-core/src/test/java/io/ebean/DtoQuery2Test.java +++ b/ebean-core/src/test/java/io/ebean/DtoQuery2Test.java @@ -6,10 +6,13 @@ import org.ebeantest.LoggedSqlCollector; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.tests.model.basic.Customer; import org.tests.model.basic.ResetBasicData; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; @@ -30,6 +33,73 @@ public class DtoQuery2Test extends BaseTestCase { assertThat(list).isNotEmpty(); } + @Test + public void dto_findIterator_closeWithResources() { + ResetBasicData.reset(); + + int counter = 0; + try (QueryIterator iterator = server() + .findDto(DCust.class, "select id, name from o_customer where id > ?") + .setParameter(0) + .findIterate()) { + + if (iterator.hasNext()) { + counter++; + } + } + + assertThat(counter).isEqualTo(1); + } + + @Test + public void dto_findIterator() { + ResetBasicData.reset(); + final int expectedCount = server().find(Customer.class).findCount(); + + LoggedSqlCollector.start(); + int counter = 0; + try (final QueryIterator iterator = server().findDto(DCust.class, "select id, name from o_customer where id > :id") + .setParameter("id", 0) + .findIterate()) { + + while (iterator.hasNext()) { + final DCust cust = iterator.next(); + counter++; + assertThat(cust).isNotNull(); + assertThat(cust.getName()).isNotNull(); + } + } + + assertThat(counter).isEqualTo(expectedCount); + + List sql = LoggedSqlCollector.stop(); + assertSql(sql.get(0)).contains("select id, name from o_customer where id > ?"); + } + + @Test + public void dto_findStream() { + ResetBasicData.reset(); + final int expectedCount = server().find(Customer.class).findCount(); + + LoggedSqlCollector.start(); + + try (final Stream stream = + server() + .findDto(DCust.class, "select id, name from o_customer where id > ?") + .setParameter(0) + .findStream()) { + + final List names = stream + .map(DCust::getName) + .collect(Collectors.toList()); + + assertThat(names.size()).isEqualTo(expectedCount); + } + + List sql = LoggedSqlCollector.stop(); + assertSql(sql.get(0)).contains("select id, name from o_customer where id > ?"); + } + @Test public void dto_findEach_constructorMatch() { diff --git a/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java b/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java index 71e73b59f..f0f0c0779 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java +++ b/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java @@ -453,6 +453,16 @@ public class TDSpiEbeanServer implements SpiEbeanServer { public void findDtoEachWhile(SpiDtoQuery query, Predicate consumer) { } + @Override + public QueryIterator findDtoIterate(SpiDtoQuery query) { + return null; + } + + @Override + public Stream findDtoStream(SpiDtoQuery query) { + return null; + } + @Override public List findDtoList(SpiDtoQuery query) { return null; From 9be1dcff6c9369081e4950a26a54b68ffca334c0 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Thu, 10 Jun 2021 19:00:35 +1200 Subject: [PATCH 42/72] Tidy internals ScalarTypeJsonObjectMapper, no functional change --- .../server/type/ScalarTypeJsonObjectMapper.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java index fc88f10da..b001e5ddf 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java @@ -142,7 +142,7 @@ public class ScalarTypeJsonObjectMapper { private final ObjectMapper objectReader; - private JavaType deserType; + private final JavaType deserType; private final String pgType; @@ -224,12 +224,7 @@ public class ScalarTypeJsonObjectMapper { if (value == null) { binder.setNull(Types.VARCHAR); // use varchar, otherwise SqlServer/db2 will fail with 'Invalid JDBC data type 5.001.' } else { - try { - String json = objectWriter.writeValueAsString(value); - binder.setString(json); - } catch (JsonProcessingException e) { - throw new SQLException("Unable to create JSON", e); - } + binder.setString(formatValue(value)); } } } From 3c36b434494f4bfe2031ee7c863beb647dfa959f Mon Sep 17 00:00:00 2001 From: rbygrave Date: Thu, 10 Jun 2021 19:26:56 +1200 Subject: [PATCH 43/72] No effective change - tidy EntityBeanIntercept --- .../io/ebean/bean/EntityBeanIntercept.java | 61 ++++++------------- 1 file changed, 20 insertions(+), 41 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 0c2333026..4695c4112 100644 --- a/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java +++ b/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java @@ -32,39 +32,47 @@ public final class EntityBeanIntercept implements Serializable { private static final int STATE_REFERENCE = 1; private static final int STATE_LOADED = 2; + /** + * Used when a bean is partially filled. + */ + private static final byte FLAG_LOADED_PROP = 1; + private static final byte FLAG_CHANGED_PROP = 2; + private static final byte FLAG_CHANGEDLOADED_PROP = 3; + /** + * Flags indicating if a property is a dirty embedded bean. Used to distinguish + * between an embedded bean being completely overwritten and one of its + * embedded properties being made dirty. + */ + private static final byte FLAG_EMBEDDED_DIRTY = 4; + /** + * Flags indicating if a property is a dirty embedded bean. Used to distinguish + * between an embedded bean being completely overwritten and one of its + * embedded properties being made dirty. + */ + private static final byte FLAG_ORIG_VALUE_SET = 8; + private transient final ReentrantLock lock = new ReentrantLock(); - private transient NodeUsageCollector nodeUsageCollector; - private transient PersistenceContext persistenceContext; - private transient BeanLoader beanLoader; - private transient PreGetterCallback preGetterCallback; private String ebeanServerName; - private boolean deletedFromCollection; /** * The actual entity bean that 'owns' this intercept. */ private final EntityBean owner; - private EntityBean embeddedOwner; private int embeddedOwnerIndex; - /** * One of NEW, REF, UPD. */ private int state; - private boolean forceUpdate; - private boolean readOnly; - private boolean dirty; - /** * Flag set to disable lazy loading - typically for SQL "report" type entity beans. */ @@ -74,37 +82,9 @@ public final class EntityBeanIntercept implements Serializable { * Flag set when lazy loading failed due to the underlying bean being deleted in the DB. */ private boolean lazyLoadFailure; - - /** - * Used when a bean is partially filled. - */ - private static final byte FLAG_LOADED_PROP = 1; - - /** - * Set of changed properties. - */ - private static final byte FLAG_CHANGED_PROP = 2; - - private static final byte FLAG_CHANGEDLOADED_PROP = 3; - - /** - * Flags indicating if a property is a dirty embedded bean. Used to distinguish - * between an embedded bean being completely overwritten and one of its - * embedded properties being made dirty. - */ - private static final byte FLAG_EMBEDDED_DIRTY = 4; - - /** - * Flags indicating if a property is a dirty embedded bean. Used to distinguish - * between an embedded bean being completely overwritten and one of its - * embedded properties being made dirty. - */ - private static final byte FLAG_ORIG_VALUE_SET = 8; - - private final byte[] flags; - private boolean fullyLoadedBean; private boolean loadedFromCache; + private final byte[] flags; private Object[] origValues; private Exception[] loadErrors; private int lazyLoadProperty = -1; @@ -983,7 +963,6 @@ public final class EntityBeanIntercept implements Serializable { } } - /** * Check for primitive boolean. */ From adfc54200fba0bd94ee42f15bc8ca66db1639f43 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Thu, 10 Jun 2021 19:27:42 +1200 Subject: [PATCH 44/72] No effective change - extract JacksonTypeHelper for ScalarTypeJsonObjectMapper --- .../server/type/JacksonTypeHelper.java | 46 +++++++++++++++++++ .../type/ScalarTypeJsonObjectMapper.java | 35 ++------------ 2 files changed, 49 insertions(+), 32 deletions(-) create mode 100644 ebean-core/src/main/java/io/ebeaninternal/server/type/JacksonTypeHelper.java diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/JacksonTypeHelper.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/JacksonTypeHelper.java new file mode 100644 index 000000000..1e1db244d --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/JacksonTypeHelper.java @@ -0,0 +1,46 @@ +package io.ebeaninternal.server.type; + +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.introspect.AnnotatedField; + +class JacksonTypeHelper { + + private final AnnotatedField field; + private final ObjectMapper objectMapper; + private final JavaType javaType; + private final DeserializationConfig deserConfig; + private final AnnotationIntrospector ai; + + JacksonTypeHelper(AnnotatedField field, ObjectMapper objectMapper) { + this.field = field; + this.objectMapper = objectMapper; + this.javaType = field.getType(); + this.deserConfig = objectMapper.getDeserializationConfig(); + this.ai = deserConfig.getAnnotationIntrospector(); + } + + JavaType type() { + if (ai == null || javaType == null || javaType.hasRawClass(Object.class)) { + return javaType; + } else { + try { + return ai.refineDeserializationType(deserConfig, field, javaType); + } catch (JsonMappingException e) { + throw new RuntimeException(e); + } + } + } + + ObjectWriter objectWriter() { + if (ai == null || javaType == null || javaType.hasRawClass(Object.class)) { + return objectMapper.writerFor(javaType); + } else { + try { + JavaType serType = ai.refineSerializationType(objectMapper.getSerializationConfig(), field, javaType); + return objectMapper.writerFor(serType); + } catch (JsonMappingException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java index b001e5ddf..3cf05d9c2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java @@ -3,13 +3,9 @@ package io.ebeaninternal.server.type; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.AnnotationIntrospector; -import com.fasterxml.jackson.databind.DeserializationConfig; import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; -import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.introspect.AnnotatedField; import io.ebean.config.dbplatform.DbPlatformType; import io.ebean.core.type.DataBinder; @@ -156,34 +152,9 @@ public class ScalarTypeJsonObjectMapper { this.pgType = pgType; this.docType = docType; this.objectReader = objectMapper; - - JavaType javaType = field.getType(); - DeserializationConfig deserConfig = objectMapper.getDeserializationConfig(); - AnnotationIntrospector ai = deserConfig.getAnnotationIntrospector(); - - if (ai != null && javaType != null && !javaType.hasRawClass(Object.class)) { - try { - this.deserType = ai.refineDeserializationType(deserConfig, field, javaType); - } catch (JsonMappingException e) { - throw new RuntimeException(e); - } - } else { - this.deserType = javaType; - } - - SerializationConfig serConfig = objectMapper.getSerializationConfig(); - ai = deserConfig.getAnnotationIntrospector(); - - if (ai != null && javaType != null && !javaType.hasRawClass(Object.class)) { - try { - JavaType serType = ai.refineSerializationType(serConfig, field, javaType); - this.objectWriter = objectMapper.writerFor(serType); - } catch (JsonMappingException e) { - throw new RuntimeException(e); - } - } else { - this.objectWriter = objectMapper.writerFor(javaType); - } + final JacksonTypeHelper helper = new JacksonTypeHelper(field, objectMapper); + this.deserType = helper.type(); + this.objectWriter = helper.objectWriter(); } /** From 87ffcbf23e70c6c83c32afbff4f8e69ba5001336 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Thu, 10 Jun 2021 19:45:27 +1200 Subject: [PATCH 45/72] No effective change - tidy CQuery --- .../io/ebeaninternal/server/query/CQuery.java | 43 ++----------------- 1 file changed, 3 insertions(+), 40 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java index 49182242e..0fb7e9dcf 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java @@ -2,13 +2,7 @@ package io.ebeaninternal.server.query; import io.ebean.QueryIterator; import io.ebean.Version; -import io.ebean.bean.BeanCollection; -import io.ebean.bean.EntityBean; -import io.ebean.bean.EntityBeanIntercept; -import io.ebean.bean.NodeUsageCollector; -import io.ebean.bean.NodeUsageListener; -import io.ebean.bean.ObjectGraphNode; -import io.ebean.bean.PersistenceContext; +import io.ebean.bean.*; import io.ebean.core.type.DataReader; import io.ebean.event.readaudit.ReadEvent; import io.ebean.util.JdbcClose; @@ -19,12 +13,7 @@ import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.server.autotune.ProfilingListener; import io.ebeaninternal.server.core.OrmQueryRequest; import io.ebeaninternal.server.core.SpiOrmQueryRequest; -import io.ebeaninternal.server.deploy.BeanCollectionHelpFactory; -import io.ebeaninternal.server.deploy.BeanDescriptor; -import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; -import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; -import io.ebeaninternal.server.deploy.DbReadContext; -import io.ebeaninternal.server.type.DataBind; +import io.ebeaninternal.server.deploy.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,11 +23,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; +import java.util.*; import java.util.concurrent.locks.ReentrantLock; /** @@ -47,11 +32,9 @@ import java.util.concurrent.locks.ReentrantLock; * The SqlSelect is based on a tree (Object Graph). The tree is traversed to see * what parts are included in the tree according to the value of * find.getInclude(); - *

*

* The tree structure is flattened into a SqlSelectChain. The SqlSelectChain is * the key object used in reading the flat resultSet back into Objects. - *

*/ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTransactionEvent { @@ -205,19 +188,15 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran this.query = request.getQuery(); this.queryMode = query.getMode(); this.lazyLoadManyProperty = query.getLazyLoadMany(); - this.readOnly = request.isReadOnly(); this.disableLazyLoading = query.isDisableLazyLoading(); - this.objectGraphNode = query.getParentNode(); this.profilingListener = query.getProfilingListener(); this.autoTuneProfiling = profilingListener != null; this.profilingListenerRef = autoTuneProfiling ? new WeakReference<>(profilingListener) : null; - // set the generated sql back to the query // so its available to the user... query.setGeneratedSql(queryPlan.getSql()); - SqlTree sqlTree = queryPlan.getSqlTree(); this.rootNode = sqlTree.getRootNode(); this.manyProperty = sqlTree.getManyProperty(); @@ -373,7 +352,6 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran * Close the resources. *

* The JDBC resultSet and statement need to be closed. Its important that this method is called. - *

*/ public void close() { try { @@ -405,14 +383,12 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran @Override public void setLazyLoadedChildBean(EntityBean bean, Object lazyLoadParentId) { - if (lazyLoadParentId != null) { if (!lazyLoadParentId.equals(this.lazyLoadParentId)) { // get the appropriate parent bean from the persistence context this.lazyLoadParentBean = (EntityBean) lazyLoadManyProperty.getBeanDescriptor().contextGet(getPersistenceContext(), lazyLoadParentId); this.lazyLoadParentId = lazyLoadParentId; } - // add the loadedBean to the appropriate collection of lazyLoadParentBean lazyLoadManyProperty.addBeanToCollectionWithCreate(lazyLoadParentBean, bean, true); } @@ -423,10 +399,8 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran *

* If the query includes a many then the first object in the returned array is * the one/master and the second the many/detail. - *

*/ private boolean readNextBean() throws SQLException { - if (!moveToNextRow()) { if (currentBean == null) { nextBean = null; @@ -440,7 +414,6 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran } loadedBeanCount++; - if (manyProperty == null) { // only single resultSet row required to build object so we are done // read a single resultSet row into single bean @@ -535,20 +508,16 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran * Read version beans and their effective dates. */ List> readVersions() throws SQLException { - List> versionList = new ArrayList<>(); - Version version; while ((version = readNextVersion()) != null) { versionList.add(version); } - updateExecutionStatistics(); return versionList; } private Version readNextVersion() throws SQLException { - if (moveToNextRow()) { return rootNode.loadVersion(this); } @@ -692,7 +661,6 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran * Should we create profileNodes for beans created in this query. *

* This is true for all queries except lazy load bean queries. - *

*/ @Override public boolean isAutoTuneProfiling() { @@ -703,13 +671,11 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran } private String getPath(String propertyName) { - if (currentPrefix == null) { return propertyName; } else if (propertyName == null) { return currentPrefix; } - String path = currentPathMap.get(propertyName); if (path != null) { return path; @@ -720,7 +686,6 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran @Override public void profileBean(EntityBeanIntercept ebi, String prefix) { - ObjectGraphNode node = request.getGraphContext().getObjectGraphNode(prefix); ebi.setNodeUsageCollector(new NodeUsageCollector(node, profilingListenerRef)); } @@ -745,7 +710,6 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran * a find many query with read auditing so build the ReadEvent and log it. */ void auditFindMany() { - if (auditIds != null && !auditIds.isEmpty()) { // get the id values of the underlying collection ReadEvent futureReadEvent = query.getFutureFetchAudit(); @@ -783,7 +747,6 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran * Add the id to the audit id buffer and flush if needed in batches of 100. */ private void auditNextBean() { - if (auditIds == null) { auditIds = new ArrayList<>(100); } From 9ee891dc66d9aae3f05068ddabec9a60e98d6096 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Thu, 10 Jun 2021 20:09:34 +1200 Subject: [PATCH 46/72] #2247 - Refactor internals - Change DefaultTypeManager from class.newInstance() to class.getDeclaredConstructor().newInstance() --- .../server/type/DefaultTypeManager.java | 56 ++++--------------- 1 file changed, 11 insertions(+), 45 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index 7a2d82baa..8e62235a4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -3,11 +3,7 @@ package io.ebeaninternal.server.type; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.introspect.AnnotatedField; -import io.ebean.annotation.DbArray; -import io.ebean.annotation.DbEnumType; -import io.ebean.annotation.DbEnumValue; -import io.ebean.annotation.EnumValue; -import io.ebean.annotation.Platform; +import io.ebean.annotation.*; import io.ebean.config.DatabaseConfig; import io.ebean.config.JsonConfig; import io.ebean.config.PlatformConfig; @@ -34,48 +30,19 @@ import org.slf4j.LoggerFactory; import javax.persistence.AttributeConverter; import javax.persistence.EnumType; import java.io.File; -import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.URI; -import java.net.URL; +import java.net.*; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; -import java.time.DayOfWeek; -import java.time.Duration; -import java.time.Instant; -import java.time.Month; -import java.time.MonthDay; -import java.time.OffsetDateTime; -import java.time.OffsetTime; -import java.time.Year; -import java.time.YearMonth; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Currency; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.ServiceLoader; -import java.util.Set; -import java.util.TimeZone; -import java.util.UUID; +import java.time.*; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** @@ -722,20 +689,18 @@ public final class DefaultTypeManager implements TypeManager { try { ScalarType scalarType; if (objectMapper == null) { - scalarType = cls.newInstance(); + scalarType = cls.getDeclaredConstructor().newInstance(); } else { try { // first try objectMapper constructor - Constructor> constructor = cls.getConstructor(ObjectMapper.class); - scalarType = constructor.newInstance((ObjectMapper) objectMapper); + scalarType = cls.getDeclaredConstructor(ObjectMapper.class).newInstance(objectMapper); } catch (NoSuchMethodException e) { - scalarType = cls.newInstance(); + scalarType = cls.getDeclaredConstructor().newInstance(); } } addCustomType(scalarType); } catch (Exception e) { - String msg = "Error loading ScalarType [" + cls.getName() + "]"; - logger.error(msg, e); + logger.error("Error loading ScalarType [" + cls.getName() + "]", e); } } } @@ -768,7 +733,7 @@ public final class DefaultTypeManager implements TypeManager { if (wrappedType == null) { throw new IllegalStateException("Could not find ScalarType for: " + paramTypes[1]); } - ScalarTypeConverter converter = foundType.newInstance(); + ScalarTypeConverter converter = foundType.getDeclaredConstructor().newInstance(); ScalarTypeWrapper stw = new ScalarTypeWrapper(logicalType, wrappedType, converter); logger.debug("Register ScalarTypeWrapper from {} -> {} using:{}", logicalType, persistType, foundType); add(stw); @@ -793,7 +758,7 @@ public final class DefaultTypeManager implements TypeManager { if (wrappedType == null) { throw new IllegalStateException("Could not find ScalarType for: " + paramTypes[1]); } - AttributeConverter converter = foundType.newInstance(); + AttributeConverter converter = foundType.getDeclaredConstructor().newInstance(); ScalarTypeWrapper stw = new ScalarTypeWrapper(logicalType, wrappedType, new AttributeConverterAdapter(converter)); logger.debug("Register ScalarTypeWrapper from {} -> {} using:{}", logicalType, persistType, foundType); add(stw); @@ -1027,6 +992,7 @@ public final class DefaultTypeManager implements TypeManager { nativeMap.put(Types.TIMESTAMP, timestampType); } + @SuppressWarnings("rawtypes") private void addInetAddressType(ScalarType scalarType) { addType(InetAddress.class, scalarType); addType(Inet4Address.class, scalarType); From 441f51ed7916a5cdf605fc0217ec8203391a2090 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Thu, 10 Jun 2021 20:30:07 +1200 Subject: [PATCH 47/72] #2247 - Refactor internals - Change DefaultTypeManager - tidy --- .../server/type/DefaultTypeManager.java | 69 +++---------------- 1 file changed, 10 insertions(+), 59 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index 8e62235a4..5f8a64699 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -49,77 +49,50 @@ import java.util.concurrent.ConcurrentHashMap; * Default implementation of TypeManager. *

* Manages the list of ScalarType that is available. - *

*/ public final class DefaultTypeManager implements TypeManager { private static final Logger logger = LoggerFactory.getLogger(DefaultTypeManager.class); private final ConcurrentHashMap, ScalarType> typeMap; - private final ConcurrentHashMap> nativeMap; - private final ConcurrentHashMap> logicalMap; private final DefaultTypeFactory extraTypeFactory; private final ScalarType hstoreType = new ScalarTypePostgresHstore(); - private final ScalarTypeFile fileType = new ScalarTypeFile(); - private final ScalarType charType = new ScalarTypeChar(); - private final ScalarType charArrayType = new ScalarTypeCharArray(); - private final ScalarType longVarcharType = new ScalarTypeLongVarchar(); - private final ScalarType clobType = new ScalarTypeClob(); - private final ScalarType byteType = new ScalarTypeByte(); - private final ScalarType binaryType = new ScalarTypeBytesBinary(); - private final ScalarType blobType = new ScalarTypeBytesBlob(); - private final ScalarType varbinaryType = new ScalarTypeBytesVarbinary(); - private final ScalarType longVarbinaryType = new ScalarTypeBytesLongVarbinary(); - private final ScalarType shortType = new ScalarTypeShort(); - private final ScalarType integerType = ScalarTypeInteger.INSTANCE; - private final ScalarType longType = new ScalarTypeLong(); - private final ScalarType doubleType = new ScalarTypeDouble(); - private final ScalarType floatType = new ScalarTypeFloat(); - private final ScalarType bigDecimalType = new ScalarTypeBigDecimal(); - private final ScalarType timeType = new ScalarTypeTime(); - private final ScalarType urlType = new ScalarTypeURL(); private final ScalarType uriType = new ScalarTypeURI(); private final ScalarType localeType = new ScalarTypeLocale(); private final ScalarType currencyType = new ScalarTypeCurrency(); private final ScalarType timeZoneType = new ScalarTypeTimeZone(); - private final ScalarType stringType = ScalarTypeString.INSTANCE; - private final ScalarType classType = new ScalarTypeClass(); private final JsonConfig.DateTime jsonDateTime; private final JsonConfig.Date jsonDate; private final Object objectMapper; - private final boolean objectMapperPresent; - private final boolean postgres; - private final boolean offlineMigrationGeneration; - private final EnumType defaultEnumType; // OPTIONAL ScalarTypes registered if Jackson/JsonNode is in the classpath @@ -158,7 +131,6 @@ public final class DefaultTypeManager implements TypeManager { this.typeMap = new ConcurrentHashMap<>(); this.nativeMap = new ConcurrentHashMap<>(); this.logicalMap = new ConcurrentHashMap<>(); - this.objectMapperPresent = config.getClassLoadConfig().isJacksonObjectMapperPresent(); this.objectMapper = (objectMapperPresent) ? initObjectMapper(config) : null; @@ -166,16 +138,13 @@ public final class DefaultTypeManager implements TypeManager { this.postgres = isPostgres(config.getDatabasePlatform()); this.arrayTypeListFactory = arrayTypeListFactory(config.getDatabasePlatform()); this.arrayTypeSetFactory = arrayTypeSetFactory(config.getDatabasePlatform()); - this.offlineMigrationGeneration = DbOffline.isGenerateMigration(); - this.defaultEnumType = config.getDefaultEnumType(); initialiseStandard(config); initialiseJavaTimeTypes(config); initialiseJodaTypes(config); initialiseJacksonTypes(config); - loadTypesFromProviders(config, objectMapper); loadGeoTypeBinder(config); @@ -555,8 +524,7 @@ public final class DefaultTypeManager implements TypeManager { private ScalarTypeEnum createEnumScalarType2(Class enumType) { boolean integerType = true; Map nameValueMap = new LinkedHashMap<>(); - Field[] fields = enumType.getDeclaredFields(); - for (Field field : fields) { + for (Field field : enumType.getDeclaredFields()) { EnumValue enumValue = AnnotationUtil.get(field, EnumValue.class); if (enumValue != null) { nameValueMap.put(field.getName(), enumValue.value()); @@ -619,8 +587,7 @@ public final class DefaultTypeManager implements TypeManager { } private ScalarTypeEnum createEnumScalarTypePerExtentions(Class> enumType) { - Method[] methods = enumType.getMethods(); - for (Method method : methods) { + for (Method method : enumType.getMethods()) { DbEnumValue dbValue = AnnotationUtil.get(method, DbEnumValue.class); if (dbValue != null) { boolean integerValues = DbEnumType.INTEGER == dbValue.storage(); @@ -635,7 +602,6 @@ public final class DefaultTypeManager implements TypeManager { * Create the Mapping of Enum fields to DB values using EnumValue annotations. *

* Return null if the EnumValue annotations are not present/used. - *

*/ private ScalarTypeEnum createEnumScalarTypeDbValue(Class> enumType, Method method, boolean integerType, int length, boolean withConstraint) { Map nameValueMap = new LinkedHashMap<>(); @@ -664,11 +630,11 @@ public final class DefaultTypeManager implements TypeManager { EnumToDbValueMap beanDbMap = EnumToDbValueMap.create(integerType); int maxValueLen = 0; for (Map.Entry entry : nameValueMap.entrySet()) { - String name = entry.getKey(); + String name = entry.getKey().trim(); String value = entry.getValue(); maxValueLen = Math.max(maxValueLen, value.length()); - Object enumValue = Enum.valueOf(enumType, name.trim()); - beanDbMap.add(enumValue, value, name.trim()); + Object enumValue = Enum.valueOf(enumType, name); + beanDbMap.add(enumValue, value, name); } if (dbColumnLength == 0 && !integerType) { dbColumnLength = maxValueLen; @@ -678,11 +644,6 @@ public final class DefaultTypeManager implements TypeManager { /** * Automatically find any ScalarTypes by searching through the class path. - *

- * In avaje.properties define a list of packages in which ScalarTypes are - * found. This will search for any class that implements the ScalarType - * interface and register it with this TypeManager. - *

*/ private void initialiseCustomScalarTypes(BootupClasses bootupClasses) { for (Class> cls : bootupClasses.getScalarTypes()) { @@ -720,8 +681,7 @@ public final class DefaultTypeManager implements TypeManager { @SuppressWarnings({"unchecked", "rawtypes"}) private void initialiseScalarConverters(BootupClasses bootupClasses) { - List>> foundTypes = bootupClasses.getScalarConverters(); - for (Class> foundType : foundTypes) { + for (Class> foundType : bootupClasses.getScalarConverters()) { try { Class[] paramTypes = TypeReflectHelper.getParams(foundType, ScalarTypeConverter.class); if (paramTypes.length != 2) { @@ -745,8 +705,7 @@ public final class DefaultTypeManager implements TypeManager { @SuppressWarnings({"unchecked", "rawtypes"}) private void initialiseAttributeConverters(BootupClasses bootupClasses) { - List>> foundTypes = bootupClasses.getAttributeConverters(); - for (Class> foundType : foundTypes) { + for (Class> foundType : bootupClasses.getAttributeConverters()) { try { Class[] paramTypes = TypeReflectHelper.getParams(foundType, AttributeConverter.class); if (paramTypes.length != 2) { @@ -855,23 +814,16 @@ public final class DefaultTypeManager implements TypeManager { nativeMap.put(DbPlatformType.HSTORE, hstoreType); - ScalarType utilDateType = extraTypeFactory.createUtilDate(jsonDateTime, jsonDate); - addType(java.util.Date.class, utilDateType); - - ScalarType calType = extraTypeFactory.createCalendar(jsonDateTime); - addType(Calendar.class, calType); - - ScalarType mathBigIntType = extraTypeFactory.createMathBigInteger(); - addType(BigInteger.class, mathBigIntType); + addType(java.util.Date.class, extraTypeFactory.createUtilDate(jsonDateTime, jsonDate)); + addType(Calendar.class, extraTypeFactory.createCalendar(jsonDateTime)); + addType(BigInteger.class, extraTypeFactory.createMathBigInteger()); ScalarTypeBool booleanType = extraTypeFactory.createBoolean(); addType(Boolean.class, booleanType); addType(boolean.class, booleanType); - // register the boolean literals to the platform for DDL default values databasePlatform.setDbTrueLiteral(booleanType.getDbTrueLiteral()); databasePlatform.setDbFalseLiteral(booleanType.getDbFalseLiteral()); - // always register Types.BOOLEAN to our boolean type nativeMap.put(Types.BOOLEAN, booleanType); if (booleanType.getJdbcType() == Types.BIT) { @@ -880,7 +832,6 @@ public final class DefaultTypeManager implements TypeManager { } PlatformConfig.DbUuid dbUuid = config.getPlatformConfig().getDbUuid(); - if (offlineMigrationGeneration || (databasePlatform.isNativeUuidType() && dbUuid.useNativeType())) { addType(UUID.class, new ScalarTypeUUIDNative()); } else { From 8ba66af878a4e788a16d77011ebdcc298d1c61d3 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Thu, 10 Jun 2021 22:11:45 +1200 Subject: [PATCH 48/72] Modify test TestQueryDefaultBatchSize to show use of ProfileLocation --- .../tests/query/lazy/TestQueryDefaultBatchSize.java | 10 ++++++++++ ebean-core/src/test/resources/ebean.properties | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/ebean-core/src/test/java/org/tests/query/lazy/TestQueryDefaultBatchSize.java b/ebean-core/src/test/java/org/tests/query/lazy/TestQueryDefaultBatchSize.java index a8685f985..e1d9afd02 100644 --- a/ebean-core/src/test/java/org/tests/query/lazy/TestQueryDefaultBatchSize.java +++ b/ebean-core/src/test/java/org/tests/query/lazy/TestQueryDefaultBatchSize.java @@ -2,6 +2,7 @@ package org.tests.query.lazy; import io.ebean.BaseTestCase; import io.ebean.Ebean; +import io.ebean.ProfileLocation; import org.tests.model.basic.Customer; import org.tests.model.basic.Order; import org.tests.model.basic.OrderDetail; @@ -13,12 +14,18 @@ import java.util.List; public class TestQueryDefaultBatchSize extends BaseTestCase { + private static final ProfileLocation loc0 = ProfileLocation.create(); + private static final ProfileLocation loc1 = ProfileLocation.create(); + private static final ProfileLocation loc2 = ProfileLocation.create(); + private static final ProfileLocation loc3 = ProfileLocation.create(); + @Test public void test_findEach() { ResetBasicData.reset(); Ebean.find(Order.class) + .setProfileLocation(loc0) .setLazyLoadBatchSize(2) .findEach(bean -> doStuff(bean)); } @@ -29,6 +36,7 @@ public class TestQueryDefaultBatchSize extends BaseTestCase { ResetBasicData.reset(); Ebean.find(Order.class) + .setProfileLocation(loc1) .fetch("details", "id") .fetch("details.product", "sku") .fetch("customer") @@ -44,6 +52,7 @@ public class TestQueryDefaultBatchSize extends BaseTestCase { List orders = Ebean.find(Order.class) + .setProfileLocation(loc2) .setLazyLoadBatchSize(2) .findList(); @@ -59,6 +68,7 @@ public class TestQueryDefaultBatchSize extends BaseTestCase { List orders = Ebean.find(Order.class) + .setProfileLocation(loc3) .fetch("details", "id") .fetch("details.product", "sku") .fetch("customer") diff --git a/ebean-core/src/test/resources/ebean.properties b/ebean-core/src/test/resources/ebean.properties index aa1210739..49aae2a31 100644 --- a/ebean-core/src/test/resources/ebean.properties +++ b/ebean-core/src/test/resources/ebean.properties @@ -26,7 +26,7 @@ datasource.default=h2 #datasource.h2.capturestacktrace=true ebean.dumpMetricsOnShutdown=true -ebean.dumpMetricsOptions=sql,hash +ebean.dumpMetricsOptions=sql,hash,loc #ebean.queryPlan.thresholdMicros=1000 ebean.queryPlan.enable=true From 4e088a40d9d6fb6df72bbe45e944dc6611c6ea0b Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 11 Jun 2021 00:08:03 +1200 Subject: [PATCH 49/72] Refactor DbJson Jackson handling adding DatabaseConfig.setJsonDirtyByDefault() Adds the ability to change the default "jsonDirtyByDefault" configuration setting used with DbJson Jackson properties. Currently these default to being assumed dirty and this allows us to change that to be assumed not dirty (which is very likely the better default). --- .../java/io/ebean/config/DatabaseConfig.java | 29 +++++- .../server/type/CheckMarkedDirty.java | 26 ----- .../server/type/DefaultTypeManager.java | 7 +- .../server/type/ScalarTypeJsonCollection.java | 2 +- .../server/type/ScalarTypeJsonMap.java | 2 +- .../type/ScalarTypeJsonObjectMapper.java | 67 +++++-------- .../server/type/ScalarTypePostgresHstore.java | 2 +- .../server/type/TypeJsonManager.java | 98 +++++++++++++++++++ .../io/ebean/config/ServerConfigTest.java | 5 + .../org/tests/json/TestDbJson_Jackson3.java | 3 + 10 files changed, 166 insertions(+), 75 deletions(-) delete mode 100644 ebean-core/src/main/java/io/ebeaninternal/server/type/CheckMarkedDirty.java create mode 100644 ebean-core/src/main/java/io/ebeaninternal/server/type/TypeJsonManager.java diff --git a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java index be2ac6c1f..bc3ae587f 100644 --- a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java +++ b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java @@ -193,6 +193,12 @@ public class DatabaseConfig { */ private JsonConfig.Include jsonInclude = JsonConfig.Include.ALL; + /** + * When true then by default DbJson beans are assumed to be dirty. + * I believe we want to change this default to false in the future. + */ + private boolean jsonDirtyByDefault = true; + /** * The database platform name. Used to imply a DatabasePlatform to use. */ @@ -737,6 +743,26 @@ public class DatabaseConfig { this.jsonInclude = jsonInclude; } + /** + * Return true if DbJson beans are assumed dirty by default. + *

+ * That is, when true beans that do not implement ModifyAwareType are by + * default assumed to be dirty and included in updates. + */ + public boolean isJsonDirtyByDefault() { + return jsonDirtyByDefault; + } + + /** + * Set to false if we want DbJson beans to not be assumed to be dirty. + *

+ * That is, when true beans that do not implement ModifyAwareType are by + * default assumed to be dirty and included in updates. + */ + public void setJsonDirtyByDefault(boolean jsonDirtyByDefault) { + this.jsonDirtyByDefault = jsonDirtyByDefault; + } + /** * Return the name of the Database. */ @@ -2909,6 +2935,7 @@ public class DatabaseConfig { jsonInclude = p.getEnum(JsonConfig.Include.class, "jsonInclude", jsonInclude); jsonDateTime = p.getEnum(JsonConfig.DateTime.class, "jsonDateTime", jsonDateTime); jsonDate = p.getEnum(JsonConfig.Date.class, "jsonDate", jsonDate); + jsonDirtyByDefault = p.getBoolean("jsonDirtyByDefault", jsonDirtyByDefault); runMigration = p.getBoolean("migration.run", runMigration); ddlGenerate = p.getBoolean("ddl.generate", ddlGenerate); @@ -3369,7 +3396,7 @@ public class DatabaseConfig { this.loadModuleInfo = loadModuleInfo; } - public enum UuidVersion { + public enum UuidVersion { VERSION4, VERSION1, VERSION1RND diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/CheckMarkedDirty.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/CheckMarkedDirty.java deleted file mode 100644 index 321a35fec..000000000 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/CheckMarkedDirty.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.ebeaninternal.server.type; - -import io.ebean.ModifyAwareType; - -/** - * Check dirty state of json value which might be modify aware. - */ -class CheckMarkedDirty { - - /** - * Return true if the value should be considered dirty (and included in an update). - */ - static boolean isDirty(Object value) { - if (value instanceof ModifyAwareType) { - ModifyAwareType modifyAware = (ModifyAwareType) value; - if (modifyAware.isMarkedDirty()) { - // reset the dirty state (consider not dirty after update) - modifyAware.setMarkedDirty(false); - return true; - } else { - return false; - } - } - return true; - } -} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index 5f8a64699..82c3ff46a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -92,6 +92,7 @@ public final class DefaultTypeManager implements TypeManager { private final Object objectMapper; private final boolean objectMapperPresent; private final boolean postgres; + private final TypeJsonManager jsonManager; private final boolean offlineMigrationGeneration; private final EnumType defaultEnumType; @@ -131,11 +132,11 @@ public final class DefaultTypeManager implements TypeManager { this.typeMap = new ConcurrentHashMap<>(); this.nativeMap = new ConcurrentHashMap<>(); this.logicalMap = new ConcurrentHashMap<>(); + this.postgres = isPostgres(config.getDatabasePlatform()); this.objectMapperPresent = config.getClassLoadConfig().isJacksonObjectMapperPresent(); this.objectMapper = (objectMapperPresent) ? initObjectMapper(config) : null; - + this.jsonManager = (objectMapperPresent) ? new TypeJsonManager(postgres, objectMapper, config.isJsonDirtyByDefault()) : null; this.extraTypeFactory = new DefaultTypeFactory(config); - this.postgres = isPostgres(config.getDatabasePlatform()); this.arrayTypeListFactory = arrayTypeListFactory(config.getDatabasePlatform()); this.arrayTypeSetFactory = arrayTypeSetFactory(config.getDatabasePlatform()); this.offlineMigrationGeneration = DbOffline.isGenerateMigration(); @@ -425,7 +426,7 @@ public final class DefaultTypeManager implements TypeManager { if (objectMapper == null) { throw new IllegalArgumentException("Type [" + type + "] unsupported for @DbJson mapping - Jackson ObjectMapper not present"); } - return ScalarTypeJsonObjectMapper.createTypeFor(postgres, (AnnotatedField) prop.getJacksonField(), (ObjectMapper) objectMapper, dbType, docType); + return ScalarTypeJsonObjectMapper.createTypeFor(jsonManager, (AnnotatedField) prop.getJacksonField(), dbType, docType); } /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonCollection.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonCollection.java index a6c51ff05..e40e1a9ae 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonCollection.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonCollection.java @@ -61,7 +61,7 @@ abstract class ScalarTypeJsonCollection extends ScalarTypeBase implements */ @Override public boolean isDirty(Object value) { - return CheckMarkedDirty.isDirty(value); + return TypeJsonManager.checkIsDirty(value); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMap.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMap.java index f0714c467..6dd4f900b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMap.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMap.java @@ -123,7 +123,7 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { */ @Override public boolean isDirty(Object value) { - return CheckMarkedDirty.isDirty(value); + return TypeJsonManager.checkIsDirty(value); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java index 3cf05d9c2..5e24412b5 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.introspect.AnnotatedField; -import io.ebean.config.dbplatform.DbPlatformType; import io.ebean.core.type.DataBinder; import io.ebean.core.type.DataReader; import io.ebean.core.type.DocPropertyType; @@ -30,38 +29,23 @@ import java.util.Set; /** * Supports @DbJson properties using Jackson ObjectMapper. */ -public class ScalarTypeJsonObjectMapper { +class ScalarTypeJsonObjectMapper { /** * Create and return the appropriate ScalarType. */ - public static ScalarType createTypeFor(boolean postgres, AnnotatedField field, ObjectMapper objectMapper, - int dbType, DocPropertyType docType) { - + static ScalarType createTypeFor(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) { Class type = field.getRawType(); - String pgType = getPostgresType(postgres, dbType); if (Set.class.equals(type)) { - return new OmSet(objectMapper, field, dbType, pgType, docType); + return new OmSet(jsonManager, field, dbType, docType); } if (List.class.equals(type)) { - return new OmList(objectMapper, field, dbType, pgType, docType); + return new OmList(jsonManager, field, dbType, docType); } if (Map.class.equals(type)) { - return new OmMap(objectMapper, field, dbType, pgType); + return new OmMap(jsonManager, field, dbType); } - return new GenericObject(objectMapper, field, dbType, pgType); - } - - private static String getPostgresType(boolean postgres, int dbType) { - if (postgres) { - switch (dbType) { - case DbPlatformType.JSON: - return PostgresHelper.JSON_TYPE; - case DbPlatformType.JSONB: - return PostgresHelper.JSONB_TYPE; - } - } - return null; + return new GenericObject(jsonManager, field, dbType, type); } /** @@ -69,8 +53,8 @@ public class ScalarTypeJsonObjectMapper { */ private static class GenericObject extends Base { - public GenericObject(ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType) { - super(Object.class, objectMapper, field, dbType, pgType, DocPropertyType.OBJECT); + GenericObject(TypeJsonManager jsonManager, AnnotatedField field, int dbType, Class rawType) { + super(Object.class, jsonManager, field, dbType, DocPropertyType.OBJECT, rawType); } } @@ -80,8 +64,8 @@ public class ScalarTypeJsonObjectMapper { @SuppressWarnings("rawtypes") private static class OmSet extends Base { - public OmSet(ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType, DocPropertyType docType) { - super(Set.class, objectMapper, field, dbType, pgType, docType); + OmSet(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) { + super(Set.class, jsonManager, field, dbType, docType); } @Override @@ -98,8 +82,8 @@ public class ScalarTypeJsonObjectMapper { @SuppressWarnings("rawtypes") private static class OmList extends Base { - public OmList(ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType, DocPropertyType docType) { - super(List.class, objectMapper, field, dbType, pgType, docType); + OmList(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) { + super(List.class, jsonManager, field, dbType, docType); } @Override @@ -116,8 +100,8 @@ public class ScalarTypeJsonObjectMapper { @SuppressWarnings("rawtypes") private static class OmMap extends Base { - public OmMap(ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType) { - super(Map.class, objectMapper, field, dbType, pgType, DocPropertyType.OBJECT); + OmMap(TypeJsonManager jsonManager, AnnotatedField field, int dbType) { + super(Map.class, jsonManager, field, dbType, DocPropertyType.OBJECT); } @Override @@ -135,24 +119,23 @@ public class ScalarTypeJsonObjectMapper { private static abstract class Base extends ScalarTypeBase { private final ObjectWriter objectWriter; - private final ObjectMapper objectReader; - private final JavaType deserType; - private final String pgType; - private final DocPropertyType docType; + private final TypeJsonManager.DirtyHandler dirtyHandler; - /** - * Construct given the object mapper, property type and DB type for storage. - */ - public Base(Class cls, ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType, DocPropertyType docType) { + Base(Class cls, TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) { + this(cls, jsonManager, field, dbType, docType, cls); + } + + Base(Class cls, TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType, Class rawType) { super(cls, false, dbType); - this.pgType = pgType; + this.objectReader = jsonManager.objectMapper(); + this.pgType = jsonManager.postgresType(dbType); this.docType = docType; - this.objectReader = objectMapper; - final JacksonTypeHelper helper = new JacksonTypeHelper(field, objectMapper); + this.dirtyHandler = jsonManager.dirtyHandler(cls, rawType); + final JacksonTypeHelper helper = new JacksonTypeHelper(field, objectReader); this.deserType = helper.type(); this.objectWriter = helper.objectWriter(); } @@ -170,7 +153,7 @@ public class ScalarTypeJsonObjectMapper { */ @Override public boolean isDirty(Object value) { - return CheckMarkedDirty.isDirty(value); + return dirtyHandler.isDirty(value); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypePostgresHstore.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypePostgresHstore.java index 810504411..14d2c50d3 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypePostgresHstore.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypePostgresHstore.java @@ -33,7 +33,7 @@ public class ScalarTypePostgresHstore extends ScalarTypeBase { @Override public boolean isDirty(Object value) { - return CheckMarkedDirty.isDirty(value); + return TypeJsonManager.checkIsDirty(value); } @SuppressWarnings("unchecked") diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/TypeJsonManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/TypeJsonManager.java new file mode 100644 index 000000000..baea26dec --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/TypeJsonManager.java @@ -0,0 +1,98 @@ +package io.ebeaninternal.server.type; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.ebean.ModifyAwareType; +import io.ebean.config.DatabaseConfig; +import io.ebean.config.dbplatform.DbPlatformType; + +class TypeJsonManager { + + interface DirtyHandler { + boolean isDirty(Object value); + } + + private final boolean postgres; + private final ObjectMapper objectMapper; + private final DirtyHandler defaultHandler; + private final DirtyHandler modifyAwareHandler; + + TypeJsonManager(boolean postgres, Object objectMapper, boolean defaultDirty) { + this.postgres = postgres; + this.objectMapper = (ObjectMapper) objectMapper; + this.defaultHandler = new DefaultHandler(defaultDirty); + this.modifyAwareHandler = new ModifyAwareHandler(); + } + + ObjectMapper objectMapper() { + return objectMapper; + } + + String postgresType(int dbType) { + if (postgres) { + switch (dbType) { + case DbPlatformType.JSON: + return PostgresHelper.JSON_TYPE; + case DbPlatformType.JSONB: + return PostgresHelper.JSONB_TYPE; + } + } + return null; + } + + /** + * Return the DirtyHandler to use. + */ + DirtyHandler dirtyHandler(Class cls, Class rawType) { + if (!Object.class.equals(cls) || ModifyAwareType.class.isAssignableFrom(rawType)) { + // Set, List and Map are modify aware + return modifyAwareHandler; + } + return defaultHandler; + } + + /** + * Return true if the value should be considered dirty (and included in an update). + */ + static boolean checkIsDirty(Object value) { + if (value instanceof ModifyAwareType) { + return checkModifyAware(value); + } + return true; + } + + private static boolean checkModifyAware(Object value) { + ModifyAwareType modifyAware = (ModifyAwareType) value; + if (modifyAware.isMarkedDirty()) { + // reset the dirty state (consider not dirty after update) + modifyAware.setMarkedDirty(false); + return true; + } else { + return false; + } + } + + static final class ModifyAwareHandler implements DirtyHandler { + @Override + public boolean isDirty(Object value) { + return checkModifyAware(value); + } + } + + /** + * Effectively constant based on {@link DatabaseConfig#isJsonDirtyByDefault()} + */ + static final class DefaultHandler implements DirtyHandler { + + private final boolean dirty; + + DefaultHandler(boolean dirty) { + this.dirty = dirty; + } + + @Override + public boolean isDirty(Object value) { + return dirty; + } + } + +} diff --git a/ebean-core/src/test/java/io/ebean/config/ServerConfigTest.java b/ebean-core/src/test/java/io/ebean/config/ServerConfigTest.java index 4e92798d6..dde2cb216 100644 --- a/ebean-core/src/test/java/io/ebean/config/ServerConfigTest.java +++ b/ebean-core/src/test/java/io/ebean/config/ServerConfigTest.java @@ -62,6 +62,7 @@ public class ServerConfigTest { props.setProperty("dbOffline", "true"); props.setProperty("jsonDateTime", "MILLIS"); props.setProperty("jsonDate", "MILLIS"); + props.setProperty("jsonDirtyByDefault", "false"); props.setProperty("autoReadOnlyDataSource", "true"); props.setProperty("disableL2Cache", "true"); props.setProperty("notifyL2CacheInForeground", "true"); @@ -103,6 +104,9 @@ public class ServerConfigTest { assertEquals(PlatformConfig.DbUuid.BINARY, serverConfig.getPlatformConfig().getDbUuid()); assertEquals(JsonConfig.DateTime.MILLIS, serverConfig.getJsonDateTime()); assertEquals(JsonConfig.Date.MILLIS, serverConfig.getJsonDate()); + assertFalse(serverConfig.isJsonDirtyByDefault()); + serverConfig.setJsonDirtyByDefault(true); + assertTrue(serverConfig.isJsonDirtyByDefault()); assertEquals("r0,users,orgs", serverConfig.getEnabledL2Regions()); @@ -155,6 +159,7 @@ public class ServerConfigTest { assertFalse(serverConfig.isIdGeneratorAutomatic()); assertEquals(JsonConfig.DateTime.ISO8601, serverConfig.getJsonDateTime()); assertEquals(JsonConfig.Date.ISO8601, serverConfig.getJsonDate()); + assertTrue(serverConfig.isJsonDirtyByDefault()); assertTrue(serverConfig.getPlatformConfig().isCaseSensitiveCollation()); assertTrue(serverConfig.isAutoLoadModuleInfo()); diff --git a/ebean-core/src/test/java/org/tests/json/TestDbJson_Jackson3.java b/ebean-core/src/test/java/org/tests/json/TestDbJson_Jackson3.java index 74f004bca..747f01426 100644 --- a/ebean-core/src/test/java/org/tests/json/TestDbJson_Jackson3.java +++ b/ebean-core/src/test/java/org/tests/json/TestDbJson_Jackson3.java @@ -9,6 +9,7 @@ import org.tests.model.json.EBasicJsonList; import org.tests.model.json.PlainBean; import org.tests.model.json.PlainBeanDirtyAware; +import java.util.Arrays; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -59,12 +60,14 @@ public class TestDbJson_Jackson3 extends BaseTestCase { EBasicJsonList bean = new EBasicJsonList(); bean.setName("p1"); bean.setPlainBean(contentBean); + bean.setBeanList(Arrays.asList(contentBean)); DB.save(bean); final EBasicJsonList found = DB.find(EBasicJsonList.class, bean.getId()); // json bean not modified but not aware // ideally don't load the json content if we are not going to modify it found.setName("p1-mod"); + found.setBeanList(null); LoggedSql.start(); DB.save(found); From 884dd939b732a8553c8f9ad619bbac6d1d751b89 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 11 Jun 2021 00:19:20 +1200 Subject: [PATCH 50/72] #2249 - Bug with DbJson Jackson property that is null is updated as null (unnecessarily included in update) When the value is null and not changed then is was null before and should not be treated as dirty. Hence only check for dirty when value != null in BeanDescriptor 3182 --- .../java/io/ebeaninternal/server/deploy/BeanDescriptor.java | 2 +- .../src/test/java/org/tests/json/TestDbJson_Jackson3.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index a6841d449..68401b5e3 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -3179,7 +3179,7 @@ public class BeanDescriptor implements BeanType, STreeType { int propertyIndex = beanProperty.getPropertyIndex(); if (!ebi.isDirtyProperty(propertyIndex) && ebi.isLoadedProperty(propertyIndex)) { Object value = beanProperty.getValue(ebi.getOwner()); - if (value == null || beanProperty.isDirtyValue(value)) { + if (value != null && beanProperty.isDirtyValue(value)) { // mutable scalar value which is considered dirty so mark // it as such so that it is included in an update ebi.markPropertyAsChanged(propertyIndex); diff --git a/ebean-core/src/test/java/org/tests/json/TestDbJson_Jackson3.java b/ebean-core/src/test/java/org/tests/json/TestDbJson_Jackson3.java index 747f01426..106beb9f2 100644 --- a/ebean-core/src/test/java/org/tests/json/TestDbJson_Jackson3.java +++ b/ebean-core/src/test/java/org/tests/json/TestDbJson_Jackson3.java @@ -74,6 +74,6 @@ public class TestDbJson_Jackson3 extends BaseTestCase { final List sql = LoggedSql.stop(); assertThat(sql).hasSize(1); - assertThat(sql.get(0)).contains("update ebasic_json_list set name=?, beans=?, bean_list=?, plain_bean=?, version=? where id=?"); + assertThat(sql.get(0)).contains("update ebasic_json_list set name=?, bean_list=?, plain_bean=?, version=? where id=?"); } } From fc757c087a857aee80873c76dfe810711f0c17e8 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 11 Jun 2021 00:40:55 +1200 Subject: [PATCH 51/72] [maven-release-plugin] prepare release ebean-parent-12.9.2 --- ebean-api/pom.xml | 2 +- ebean-autotune/pom.xml | 6 +++--- ebean-bom/pom.xml | 30 +++++++++++++++--------------- ebean-core-type/pom.xml | 4 ++-- ebean-core/pom.xml | 10 +++++----- ebean-ddl-generator/pom.xml | 6 +++--- ebean-externalmapping-api/pom.xml | 2 +- ebean-externalmapping-xml/pom.xml | 10 +++++----- ebean-postgis/pom.xml | 6 +++--- ebean-querybean/pom.xml | 10 +++++----- ebean-redis/pom.xml | 12 ++++++------ ebean-test/pom.xml | 6 +++--- ebean/pom.xml | 8 ++++---- kotlin-querybean-generator/pom.xml | 8 ++++---- pom.xml | 4 ++-- querybean-generator/pom.xml | 2 +- 16 files changed, 63 insertions(+), 63 deletions(-) diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index 1d382e05c..b131ef4de 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2-SNAPSHOT + 12.9.2 ebean api diff --git a/ebean-autotune/pom.xml b/ebean-autotune/pom.xml index b8908e306..bb50c0fb1 100644 --- a/ebean-autotune/pom.xml +++ b/ebean-autotune/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2-SNAPSHOT + 12.9.2 @@ -14,7 +14,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.8.0 + ebean-parent-12.9.2 ebean autotune @@ -26,7 +26,7 @@ io.ebean ebean-core - 12.9.2-SNAPSHOT + 12.9.2 provided diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index 94223e4be..9489c971f 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2-SNAPSHOT + 12.9.2 ebean bom @@ -81,88 +81,88 @@ io.ebean ebean - 12.9.2-SNAPSHOT + 12.9.2 io.ebean ebean-api - 12.9.2-SNAPSHOT + 12.9.2 io.ebean ebean-core - 12.9.2-SNAPSHOT + 12.9.2 io.ebean ebean-core-type - 12.9.2-SNAPSHOT + 12.9.2 io.ebean ebean-ddl-generator - 12.9.2-SNAPSHOT + 12.9.2 io.ebean ebean-externalmapping-api - 12.9.2-SNAPSHOT + 12.9.2 io.ebean ebean-externalmapping-xml - 12.9.2-SNAPSHOT + 12.9.2 io.ebean ebean-autotune - 12.9.2-SNAPSHOT + 12.9.2 io.ebean ebean-querybean - 12.9.2-SNAPSHOT + 12.9.2 io.ebean querybean-generator - 12.9.2-SNAPSHOT + 12.9.2 provided io.ebean kotlin-querybean-generator - 12.9.2-SNAPSHOT + 12.9.2 provided io.ebean ebean-test - 12.9.2-SNAPSHOT + 12.9.2 test io.ebean ebean-postgis - 12.9.2-SNAPSHOT + 12.9.2 io.ebean ebean-redis - 12.9.2-SNAPSHOT + 12.9.2 diff --git a/ebean-core-type/pom.xml b/ebean-core-type/pom.xml index d7ea19e6f..bba66ea39 100644 --- a/ebean-core-type/pom.xml +++ b/ebean-core-type/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2-SNAPSHOT + 12.9.2 ebean-core-type @@ -16,7 +16,7 @@ io.ebean ebean-api - 12.9.2-SNAPSHOT + 12.9.2 diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 8f0b27243..331d202aa 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -3,7 +3,7 @@ ebean-parent io.ebean - 12.9.2-SNAPSHOT + 12.9.2 ebean-core @@ -15,7 +15,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.8.0 + ebean-parent-12.9.2 @@ -87,19 +87,19 @@ io.ebean ebean-api - 12.9.2-SNAPSHOT + 12.9.2 io.ebean ebean-core-type - 12.9.2-SNAPSHOT + 12.9.2 io.ebean ebean-externalmapping-api - 12.9.2-SNAPSHOT + 12.9.2 diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml index 6397c239b..6111caebe 100644 --- a/ebean-ddl-generator/pom.xml +++ b/ebean-ddl-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2-SNAPSHOT + 12.9.2 ebean ddl generation @@ -28,14 +28,14 @@ io.ebean ebean-core-type - 12.9.2-SNAPSHOT + 12.9.2 provided io.ebean ebean-core - 12.9.2-SNAPSHOT + 12.9.2 provided diff --git a/ebean-externalmapping-api/pom.xml b/ebean-externalmapping-api/pom.xml index 45e433741..2ea48bd8f 100644 --- a/ebean-externalmapping-api/pom.xml +++ b/ebean-externalmapping-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2-SNAPSHOT + 12.9.2 ebean external mapping api diff --git a/ebean-externalmapping-xml/pom.xml b/ebean-externalmapping-xml/pom.xml index 17aa58fdb..a232a6b55 100644 --- a/ebean-externalmapping-xml/pom.xml +++ b/ebean-externalmapping-xml/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2-SNAPSHOT + 12.9.2 @@ -14,7 +14,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.8.0 + ebean-parent-12.9.2 ebean external mapping xml @@ -33,7 +33,7 @@ io.ebean ebean-externalmapping-api - 12.9.2-SNAPSHOT + 12.9.2 @@ -59,14 +59,14 @@ io.ebean ebean-core - 12.9.2-SNAPSHOT + 12.9.2 test io.ebean ebean-ddl-generator - 12.9.2-SNAPSHOT + 12.9.2 test diff --git a/ebean-postgis/pom.xml b/ebean-postgis/pom.xml index 74ea548c1..4b58bfc4e 100644 --- a/ebean-postgis/pom.xml +++ b/ebean-postgis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2-SNAPSHOT + 12.9.2 ebean postgis @@ -23,7 +23,7 @@ io.ebean ebean-core - 12.9.2-SNAPSHOT + 12.9.2 provided @@ -74,7 +74,7 @@ io.ebean ebean-test - 12.9.2-SNAPSHOT + 12.9.2 test diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml index 2994c396d..08ef181ce 100644 --- a/ebean-querybean/pom.xml +++ b/ebean-querybean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2-SNAPSHOT + 12.9.2 ebean querybean @@ -17,7 +17,7 @@ io.ebean ebean-core - 12.9.2-SNAPSHOT + 12.9.2 provided @@ -57,21 +57,21 @@ io.ebean ebean-ddl-generator - 12.9.2-SNAPSHOT + 12.9.2 test io.ebean querybean-generator - 12.9.2-SNAPSHOT + 12.9.2 test io.ebean ebean-test - 12.9.2-SNAPSHOT + 12.9.2 test diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml index 12187d225..181170146 100644 --- a/ebean-redis/pom.xml +++ b/ebean-redis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2-SNAPSHOT + 12.9.2 ebean-redis @@ -22,35 +22,35 @@ io.ebean ebean-api - 12.9.2-SNAPSHOT + 12.9.2 provided io.ebean ebean-core - 12.9.2-SNAPSHOT + 12.9.2 provided io.ebean ebean-querybean - 12.9.2-SNAPSHOT + 12.9.2 test io.ebean querybean-generator - 12.9.2-SNAPSHOT + 12.9.2 test io.ebean ebean-test - 12.9.2-SNAPSHOT + 12.9.2 test diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml index 66aeb2e78..8887c95f9 100644 --- a/ebean-test/pom.xml +++ b/ebean-test/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2-SNAPSHOT + 12.9.2 ebean test @@ -29,14 +29,14 @@ io.ebean ebean-core - 12.9.2-SNAPSHOT + 12.9.2 provided io.ebean ebean-ddl-generator - 12.9.2-SNAPSHOT + 12.9.2 diff --git a/ebean/pom.xml b/ebean/pom.xml index c8f3462d4..8e2db6fbc 100644 --- a/ebean/pom.xml +++ b/ebean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2-SNAPSHOT + 12.9.2 ebean composite @@ -22,20 +22,20 @@ io.ebean ebean-api - 12.9.2-SNAPSHOT + 12.9.2 io.ebean ebean-core - 12.9.2-SNAPSHOT + 12.9.2 io.ebean ebean-querybean - 12.9.2-SNAPSHOT + 12.9.2 diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml index 20bf130a3..58439c308 100644 --- a/kotlin-querybean-generator/pom.xml +++ b/kotlin-querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2-SNAPSHOT + 12.9.2 kotlin querybean generator @@ -29,7 +29,7 @@ io.ebean ebean-querybean - 12.9.2-SNAPSHOT + 12.9.2 test @@ -43,7 +43,7 @@ io.ebean ebean-core - 12.9.2-SNAPSHOT + 12.9.2 test @@ -64,7 +64,7 @@ io.ebean ebean-ddl-generator - 12.9.2-SNAPSHOT + 12.9.2 test diff --git a/pom.xml b/pom.xml index af7bc24b2..8f20cb117 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.ebean ebean-parent - 12.9.2-SNAPSHOT + 12.9.2 pom ebean parent @@ -18,7 +18,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.8.0 + ebean-parent-12.9.2 diff --git a/querybean-generator/pom.xml b/querybean-generator/pom.xml index 1e59c8065..13c1582b4 100644 --- a/querybean-generator/pom.xml +++ b/querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2-SNAPSHOT + 12.9.2 querybean generator From 4ee784c68f40fb95f594fd943ddb11f70120a5b1 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 11 Jun 2021 00:41:06 +1200 Subject: [PATCH 52/72] [maven-release-plugin] prepare for next development iteration --- ebean-api/pom.xml | 2 +- ebean-autotune/pom.xml | 6 +++--- ebean-bom/pom.xml | 30 +++++++++++++++--------------- ebean-core-type/pom.xml | 4 ++-- ebean-core/pom.xml | 10 +++++----- ebean-ddl-generator/pom.xml | 6 +++--- ebean-externalmapping-api/pom.xml | 2 +- ebean-externalmapping-xml/pom.xml | 10 +++++----- ebean-postgis/pom.xml | 6 +++--- ebean-querybean/pom.xml | 10 +++++----- ebean-redis/pom.xml | 12 ++++++------ ebean-test/pom.xml | 6 +++--- ebean/pom.xml | 8 ++++---- kotlin-querybean-generator/pom.xml | 8 ++++---- pom.xml | 4 ++-- querybean-generator/pom.xml | 2 +- 16 files changed, 63 insertions(+), 63 deletions(-) diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index b131ef4de..a96321a13 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2 + 12.9.3-SNAPSHOT ebean api diff --git a/ebean-autotune/pom.xml b/ebean-autotune/pom.xml index bb50c0fb1..b5fe45223 100644 --- a/ebean-autotune/pom.xml +++ b/ebean-autotune/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2 + 12.9.3-SNAPSHOT @@ -14,7 +14,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.9.2 + ebean-parent-12.8.0 ebean autotune @@ -26,7 +26,7 @@ io.ebean ebean-core - 12.9.2 + 12.9.3-SNAPSHOT provided diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index 9489c971f..694875208 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2 + 12.9.3-SNAPSHOT ebean bom @@ -81,88 +81,88 @@ io.ebean ebean - 12.9.2 + 12.9.3-SNAPSHOT io.ebean ebean-api - 12.9.2 + 12.9.3-SNAPSHOT io.ebean ebean-core - 12.9.2 + 12.9.3-SNAPSHOT io.ebean ebean-core-type - 12.9.2 + 12.9.3-SNAPSHOT io.ebean ebean-ddl-generator - 12.9.2 + 12.9.3-SNAPSHOT io.ebean ebean-externalmapping-api - 12.9.2 + 12.9.3-SNAPSHOT io.ebean ebean-externalmapping-xml - 12.9.2 + 12.9.3-SNAPSHOT io.ebean ebean-autotune - 12.9.2 + 12.9.3-SNAPSHOT io.ebean ebean-querybean - 12.9.2 + 12.9.3-SNAPSHOT io.ebean querybean-generator - 12.9.2 + 12.9.3-SNAPSHOT provided io.ebean kotlin-querybean-generator - 12.9.2 + 12.9.3-SNAPSHOT provided io.ebean ebean-test - 12.9.2 + 12.9.3-SNAPSHOT test io.ebean ebean-postgis - 12.9.2 + 12.9.3-SNAPSHOT io.ebean ebean-redis - 12.9.2 + 12.9.3-SNAPSHOT diff --git a/ebean-core-type/pom.xml b/ebean-core-type/pom.xml index bba66ea39..98aaf160a 100644 --- a/ebean-core-type/pom.xml +++ b/ebean-core-type/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2 + 12.9.3-SNAPSHOT ebean-core-type @@ -16,7 +16,7 @@ io.ebean ebean-api - 12.9.2 + 12.9.3-SNAPSHOT diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 331d202aa..f626f3f8f 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -3,7 +3,7 @@ ebean-parent io.ebean - 12.9.2 + 12.9.3-SNAPSHOT ebean-core @@ -15,7 +15,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.9.2 + ebean-parent-12.8.0 @@ -87,19 +87,19 @@ io.ebean ebean-api - 12.9.2 + 12.9.3-SNAPSHOT io.ebean ebean-core-type - 12.9.2 + 12.9.3-SNAPSHOT io.ebean ebean-externalmapping-api - 12.9.2 + 12.9.3-SNAPSHOT diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml index 6111caebe..d3c571848 100644 --- a/ebean-ddl-generator/pom.xml +++ b/ebean-ddl-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2 + 12.9.3-SNAPSHOT ebean ddl generation @@ -28,14 +28,14 @@ io.ebean ebean-core-type - 12.9.2 + 12.9.3-SNAPSHOT provided io.ebean ebean-core - 12.9.2 + 12.9.3-SNAPSHOT provided diff --git a/ebean-externalmapping-api/pom.xml b/ebean-externalmapping-api/pom.xml index 2ea48bd8f..2faf0af3d 100644 --- a/ebean-externalmapping-api/pom.xml +++ b/ebean-externalmapping-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2 + 12.9.3-SNAPSHOT ebean external mapping api diff --git a/ebean-externalmapping-xml/pom.xml b/ebean-externalmapping-xml/pom.xml index a232a6b55..ad1d5b4aa 100644 --- a/ebean-externalmapping-xml/pom.xml +++ b/ebean-externalmapping-xml/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2 + 12.9.3-SNAPSHOT @@ -14,7 +14,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.9.2 + ebean-parent-12.8.0 ebean external mapping xml @@ -33,7 +33,7 @@ io.ebean ebean-externalmapping-api - 12.9.2 + 12.9.3-SNAPSHOT @@ -59,14 +59,14 @@ io.ebean ebean-core - 12.9.2 + 12.9.3-SNAPSHOT test io.ebean ebean-ddl-generator - 12.9.2 + 12.9.3-SNAPSHOT test diff --git a/ebean-postgis/pom.xml b/ebean-postgis/pom.xml index 4b58bfc4e..67dd8867b 100644 --- a/ebean-postgis/pom.xml +++ b/ebean-postgis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2 + 12.9.3-SNAPSHOT ebean postgis @@ -23,7 +23,7 @@ io.ebean ebean-core - 12.9.2 + 12.9.3-SNAPSHOT provided @@ -74,7 +74,7 @@ io.ebean ebean-test - 12.9.2 + 12.9.3-SNAPSHOT test diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml index 08ef181ce..7fbd226c0 100644 --- a/ebean-querybean/pom.xml +++ b/ebean-querybean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2 + 12.9.3-SNAPSHOT ebean querybean @@ -17,7 +17,7 @@ io.ebean ebean-core - 12.9.2 + 12.9.3-SNAPSHOT provided @@ -57,21 +57,21 @@ io.ebean ebean-ddl-generator - 12.9.2 + 12.9.3-SNAPSHOT test io.ebean querybean-generator - 12.9.2 + 12.9.3-SNAPSHOT test io.ebean ebean-test - 12.9.2 + 12.9.3-SNAPSHOT test diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml index 181170146..28f52f71d 100644 --- a/ebean-redis/pom.xml +++ b/ebean-redis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2 + 12.9.3-SNAPSHOT ebean-redis @@ -22,35 +22,35 @@ io.ebean ebean-api - 12.9.2 + 12.9.3-SNAPSHOT provided io.ebean ebean-core - 12.9.2 + 12.9.3-SNAPSHOT provided io.ebean ebean-querybean - 12.9.2 + 12.9.3-SNAPSHOT test io.ebean querybean-generator - 12.9.2 + 12.9.3-SNAPSHOT test io.ebean ebean-test - 12.9.2 + 12.9.3-SNAPSHOT test diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml index 8887c95f9..a99ff03cb 100644 --- a/ebean-test/pom.xml +++ b/ebean-test/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2 + 12.9.3-SNAPSHOT ebean test @@ -29,14 +29,14 @@ io.ebean ebean-core - 12.9.2 + 12.9.3-SNAPSHOT provided io.ebean ebean-ddl-generator - 12.9.2 + 12.9.3-SNAPSHOT diff --git a/ebean/pom.xml b/ebean/pom.xml index 8e2db6fbc..4606f1db7 100644 --- a/ebean/pom.xml +++ b/ebean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2 + 12.9.3-SNAPSHOT ebean composite @@ -22,20 +22,20 @@ io.ebean ebean-api - 12.9.2 + 12.9.3-SNAPSHOT io.ebean ebean-core - 12.9.2 + 12.9.3-SNAPSHOT io.ebean ebean-querybean - 12.9.2 + 12.9.3-SNAPSHOT diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml index 58439c308..94d002e74 100644 --- a/kotlin-querybean-generator/pom.xml +++ b/kotlin-querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2 + 12.9.3-SNAPSHOT kotlin querybean generator @@ -29,7 +29,7 @@ io.ebean ebean-querybean - 12.9.2 + 12.9.3-SNAPSHOT test @@ -43,7 +43,7 @@ io.ebean ebean-core - 12.9.2 + 12.9.3-SNAPSHOT test @@ -64,7 +64,7 @@ io.ebean ebean-ddl-generator - 12.9.2 + 12.9.3-SNAPSHOT test diff --git a/pom.xml b/pom.xml index 8f20cb117..ad1a85ddb 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.ebean ebean-parent - 12.9.2 + 12.9.3-SNAPSHOT pom ebean parent @@ -18,7 +18,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.9.2 + ebean-parent-12.8.0 diff --git a/querybean-generator/pom.xml b/querybean-generator/pom.xml index 13c1582b4..e8c0c9d65 100644 --- a/querybean-generator/pom.xml +++ b/querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.2 + 12.9.3-SNAPSHOT querybean generator From 55bc498f9d773737691b9c01092ddcd89c86731e Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 11 Jun 2021 10:20:47 +1200 Subject: [PATCH 53/72] No functional change - refactor QueryIterator to use Iterator.remove() default method Iterator.remove() has a default implementation that throws UnsupportedOperationException which is what we want. Remove the close() method from QueryIterator and implementations so that we just use the Iterator.remove() default implementation. --- ebean-api/src/main/java/io/ebean/QueryIterator.java | 6 ------ .../io/ebeaninternal/server/query/CQueryIteratorSimple.java | 6 +----- .../server/query/CQueryIteratorWithBuffer.java | 4 ---- .../io/ebeaninternal/server/query/DtoQueryIterator.java | 4 ---- 4 files changed, 1 insertion(+), 19 deletions(-) diff --git a/ebean-api/src/main/java/io/ebean/QueryIterator.java b/ebean-api/src/main/java/io/ebean/QueryIterator.java index 2ac789073..6ad523151 100644 --- a/ebean-api/src/main/java/io/ebean/QueryIterator.java +++ b/ebean-api/src/main/java/io/ebean/QueryIterator.java @@ -70,12 +70,6 @@ public interface QueryIterator extends Iterator, AutoCloseable { @Override T next(); - /** - * Remove is not allowed. - */ - @Override - void remove(); - /** * Close the underlying resources held by this iterator. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorSimple.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorSimple.java index fb434a6de..17cec9d15 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorSimple.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorSimple.java @@ -14,7 +14,7 @@ class CQueryIteratorSimple implements QueryIterator { private final CQuery cquery; private final OrmQueryRequest request; - + private boolean closed; CQueryIteratorSimple(CQuery cquery, OrmQueryRequest request) { @@ -54,8 +54,4 @@ class CQueryIteratorSimple implements QueryIterator { } } - @Override - public void remove() { - throw new PersistenceException("Remove not allowed"); - } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorWithBuffer.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorWithBuffer.java index 2ba431da5..e2a9bdad2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorWithBuffer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorWithBuffer.java @@ -76,8 +76,4 @@ class CQueryIteratorWithBuffer implements QueryIterator { } } - @Override - public void remove() { - throw new PersistenceException("Remove not allowed"); - } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryIterator.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryIterator.java index cb22fdd3a..bdf4e2df6 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryIterator.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryIterator.java @@ -48,8 +48,4 @@ class DtoQueryIterator implements QueryIterator { } } - @Override - public void remove() { - throw new IllegalStateException("remove() not allowed"); - } } From 6a6e8f906f9e2d106b6cb75f20b27e48a7c917e4 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Mon, 14 Jun 2021 11:09:13 +1200 Subject: [PATCH 54/72] Bump jedis dependency to 3.6.1 (from 3.6.0) --- ebean-redis/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml index 28f52f71d..6490c77d2 100644 --- a/ebean-redis/pom.xml +++ b/ebean-redis/pom.xml @@ -16,7 +16,7 @@ redis.clients jedis - 3.6.0 + 3.6.1 From 37a3a51b6bdc271c9aca0b975331cc589bdfd20d Mon Sep 17 00:00:00 2001 From: rbygrave Date: Mon, 14 Jun 2021 11:09:37 +1200 Subject: [PATCH 55/72] Bump test dependency for Postgres JDBC driver --- ebean-core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index f626f3f8f..5575c187b 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -182,7 +182,7 @@ org.postgresql postgresql - 42.2.10 + 42.2.20 true From 1fa488d870c2715e3edfe9dce564e3074461bae6 Mon Sep 17 00:00:00 2001 From: Vladimir Konkov Date: Mon, 14 Jun 2021 16:20:25 +0300 Subject: [PATCH 56/72] Test for transparent persistence of to-many associations --- .../TestTransparentPersist.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java b/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java index 75d767b3b..0ed40203f 100644 --- a/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java +++ b/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java @@ -8,6 +8,7 @@ import org.junit.Test; import org.tests.model.basic.Customer; import org.tests.model.basic.EBasicVer; import org.tests.model.basic.Order; +import org.tests.model.basic.OrderShipment; import java.util.List; @@ -181,6 +182,8 @@ public class TestTransparentPersist extends BaseTestCase { Order order = new Order(); order.setStatus(Order.Status.NEW); order.setCustomer(c0); + OrderShipment osh1 = new OrderShipment(); + order.addShipment(osh1); DB.save(order); LoggedSql.start(); @@ -194,7 +197,11 @@ public class TestTransparentPersist extends BaseTestCase { Customer c1 = new Customer(); c1.setName("newCust CascadePersist"); foundOrder.setCustomer(c1); - + foundOrder.getShipments().remove(0); + OrderShipment osh2 = new OrderShipment(); + foundOrder.addShipment(osh2); + // if this is called insert is performed but no delete + // DB.save(foundOrder); transaction.commit(); } @@ -204,13 +211,19 @@ public class TestTransparentPersist extends BaseTestCase { assertThat(checkOrder.getStatus()).isEqualTo(Order.Status.NEW); assertThat(checkOrder.getCustomer().getName()).isEqualTo("newCust CascadePersist"); + assertThat(checkOrder.getShipments().size()).isEqualTo(1); - assertThat(sql).hasSize(5); + assertThat(sql).hasSize(10); assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.order_date"); assertThat(sql.get(1)).contains("insert into o_customer"); assertThat(sql.get(2)).contains(" -- bind("); assertThat(sql.get(3)).contains("update o_order set updtime=?, kcustomer_id=? where id=? and updtime=?"); assertThat(sql.get(4)).contains(" -- bind("); + assertThat(sql.get(5)).contains("select t0.order_id, t0.id, t0.ship_time, t0.cretime, t0.updtime, t0.version, t0.order_id from or_order_ship"); + assertThat(sql.get(6)).contains("delete from or_order_ship"); + assertThat(sql.get(7)).contains(" -- bind("); + assertThat(sql.get(8)).contains("insert into or_order_ship"); + assertThat(sql.get(9)).contains(" -- bind("); DB.delete(checkOrder); DB.delete(Customer.class, checkOrder.getCustomer().getId()); From 7af4e166a4651192818634b9d9434429b68f8d9c Mon Sep 17 00:00:00 2001 From: rbygrave Date: Thu, 17 Jun 2021 13:26:34 +1200 Subject: [PATCH 57/72] #2253 - Incorrect JSON format for LocalDateTime. Includes Z suffix when it should not. --- .../server/type/ScalarTypeLocalDateTime.java | 34 +++++++++++++++-- .../type/ScalarTypeLocalDateTimeTest.java | 37 +++++++++++++++---- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeLocalDateTime.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeLocalDateTime.java index a4bc91aae..12b884e82 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeLocalDateTime.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeLocalDateTime.java @@ -1,7 +1,10 @@ package io.ebeaninternal.server.type; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; import io.ebean.config.JsonConfig; +import java.io.IOException; import java.sql.Timestamp; import java.sql.Types; import java.time.Instant; @@ -29,12 +32,37 @@ public class ScalarTypeLocalDateTime extends ScalarTypeBaseDateTime Date: Fri, 18 Jun 2021 12:37:16 +1200 Subject: [PATCH 58/72] #2254 - OraclePlatform needs columnAliasPrefix 'c' - Regression in version 12.2.1 due to #1967 --- .../dbplatform/oracle/Oracle11Platform.java | 1 - .../config/dbplatform/oracle/OraclePlatform.java | 1 + .../config/dbplatform/OraclePlatformTest.java | 15 +++++++++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/Oracle11Platform.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/Oracle11Platform.java index cb32a7470..5934b6dd4 100644 --- a/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/Oracle11Platform.java +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/Oracle11Platform.java @@ -11,7 +11,6 @@ public class Oracle11Platform extends OraclePlatform { public Oracle11Platform() { super(); this.platform = Platform.ORACLE11; - this.columnAliasPrefix = "c"; this.sqlLimiter = new OracleRownumSqlLimiter(); this.basicSqlLimiter = new OracleRownumBasicLimiter(); dbIdentity.setIdType(IdType.SEQUENCE); diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java index c12ca8c74..d6896ad3e 100644 --- a/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java @@ -22,6 +22,7 @@ public class OraclePlatform extends DatabasePlatform { public OraclePlatform() { super(); this.platform = Platform.ORACLE; + this.columnAliasPrefix = "c"; this.supportsDeleteTableAlias = true; this.maxTableNameLength = 30; this.maxConstraintNameLength = 30; diff --git a/ebean-core/src/test/java/io/ebean/config/dbplatform/OraclePlatformTest.java b/ebean-core/src/test/java/io/ebean/config/dbplatform/OraclePlatformTest.java index 667a5960b..15e9dda00 100644 --- a/ebean-core/src/test/java/io/ebean/config/dbplatform/OraclePlatformTest.java +++ b/ebean-core/src/test/java/io/ebean/config/dbplatform/OraclePlatformTest.java @@ -1,6 +1,7 @@ package io.ebean.config.dbplatform; import io.ebean.config.PlatformConfig; +import io.ebean.config.dbplatform.oracle.Oracle11Platform; import io.ebean.config.dbplatform.oracle.OraclePlatform; import org.junit.Test; @@ -8,17 +9,27 @@ import static org.assertj.core.api.Assertions.assertThat; public class OraclePlatformTest { + @Test + public void columnAliasPrefix_Oracle11Platform() { + Oracle11Platform platform11 = new Oracle11Platform(); + assertThat(platform11.columnAliasPrefix).isEqualTo("c"); + } + + @Test + public void columnAliasPrefix_OraclePlatform() { + OraclePlatform platform = new OraclePlatform(); + assertThat(platform.columnAliasPrefix).isEqualTo("c"); + } + @Test public void uuid_default() { OraclePlatform platform = new OraclePlatform(); platform.configure(new PlatformConfig(), false); DbPlatformType dbType = platform.getDbTypeMap().get(DbPlatformType.UUID); - assertThat(dbType.renderType(0, 0)).isEqualTo("varchar2(40)"); } - @Test public void uuid_as_binary() { From 8f432aa0a950d42135c961ec727a9ee4bb2b2001 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Fri, 18 Jun 2021 16:05:25 +1200 Subject: [PATCH 59/72] #2251 - Transparent persistence of to-many associations (with orphanRemoval) --- .../java/io/ebean/bean/BeanCollection.java | 18 ++++-------- .../io/ebean/bean/PersistenceContext.java | 10 ------- .../ebean/common/AbstractBeanCollection.java | 6 +++- .../io/ebeaninternal/api/SpiBeanType.java | 17 +++++++++++ .../ebeaninternal/api/SpiBeanTypeManager.java | 13 +++++++++ .../api/SpiPersistenceContext.java | 17 +++++++++++ .../io/ebeaninternal/api/SpiTransaction.java | 5 ++-- .../api/SpiTransactionProxy.java | 5 ++-- .../server/deploy/BeanDescriptor.java | 28 +++++++++++-------- .../server/deploy/BeanDescriptorManager.java | 11 +++++--- .../DefaultPersistenceContext.java | 28 ++++++++++--------- .../ImplicitReadOnlyTransaction.java | 12 +++----- .../server/transaction/JdbcTransaction.java | 22 ++++----------- .../server/transaction/NoTransaction.java | 6 ++-- .../transaction/TransactionManager.java | 15 ++-------- .../java/org/tests/model/basic/Order.java | 2 +- .../TestTransparentPersist.java | 7 +++-- 17 files changed, 122 insertions(+), 100 deletions(-) create mode 100644 ebean-core/src/main/java/io/ebeaninternal/api/SpiBeanType.java create mode 100644 ebean-core/src/main/java/io/ebeaninternal/api/SpiBeanTypeManager.java create mode 100644 ebean-core/src/main/java/io/ebeaninternal/api/SpiPersistenceContext.java diff --git a/ebean-api/src/main/java/io/ebean/bean/BeanCollection.java b/ebean-api/src/main/java/io/ebean/bean/BeanCollection.java index 3ec5163e3..6271d4051 100644 --- a/ebean-api/src/main/java/io/ebean/bean/BeanCollection.java +++ b/ebean-api/src/main/java/io/ebean/bean/BeanCollection.java @@ -13,12 +13,10 @@ import java.util.Set; * from the Map Set or List. The purpose of gathering the additions and removals * is to support persisting ManyToMany objects. The additions and removals * become inserts and deletes from the intersection table. - *

*

* Technically this is NOT an extension of * java.util.Collection. The reason being that java.util.Map is not a * Collection. I realise this makes this name confusing so I apologise for that. - *

*/ public interface BeanCollection extends Serializable { @@ -68,7 +66,6 @@ public interface BeanCollection extends Serializable { * Return true if the collection is uninitialised or is empty without any held modifications. *

* Returning true means can safely skip cascade save for this bean collection. - *

*/ boolean isSkipSave(); @@ -93,7 +90,6 @@ public interface BeanCollection extends Serializable { *

* That is, if the collection was not loaded due to filterMany predicates etc * then make sure the collection is set to empty. - *

*/ boolean checkEmptyLazyLoad(); @@ -136,10 +132,7 @@ public interface BeanCollection extends Serializable { boolean isReadOnly(); /** - * Add the bean to the collection. - *

- * This is disallowed for BeanMap. - *

+ * Add the bean to the collection. This is disallowed for BeanMap. */ void internalAdd(Object bean); @@ -168,7 +161,6 @@ public interface BeanCollection extends Serializable { * Map.Entry. *

* For maps this returns the entrySet as we need the keys of the map. - *

*/ Collection getActualEntries(); @@ -185,6 +177,11 @@ public interface BeanCollection extends Serializable { */ boolean isReference(); + /** + * Return true if the collection is modify listening and has modifications. + */ + boolean hasModifications(); + /** * Set modify listening on or off. This is used to keep track of objects that * have been added to or removed from the list set or map. @@ -192,7 +189,6 @@ public interface BeanCollection extends Serializable { * This is required only for ManyToMany collections. The additions and * deletions are used to insert or delete entries from the intersection table. * Otherwise modifyListening is false. - *

*/ void setModifyListening(ModifyListenMode modifyListenMode); @@ -206,7 +202,6 @@ public interface BeanCollection extends Serializable { *

* This will potentially end up as an insert into a intersection table for a * ManyToMany. - *

*/ void modifyAddition(E bean); @@ -215,7 +210,6 @@ public interface BeanCollection extends Serializable { *

* This will potentially end up as an delete from an intersection table for a * ManyToMany. - *

*/ void modifyRemoval(Object bean); diff --git a/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java b/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java index 8c861af1f..9d67499d1 100644 --- a/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java +++ b/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java @@ -1,13 +1,10 @@ package io.ebean.bean; -import java.util.List; - /** * Holds entity beans by there type and id. *

* This is used to ensure only one instance for a given entity type and id is * used to build object graphs from queries and lazy loading. - *

*/ public interface PersistenceContext { @@ -22,7 +19,6 @@ public interface PersistenceContext { *

* Returns an existing entity bean (if one is already there) and otherwise * returns null. - *

*/ Object putIfAbsent(Class rootType, Object id, Object bean); @@ -79,17 +75,11 @@ public interface PersistenceContext { */ boolean resetLimit(); - /** - * Return the list of dirty beans held by this persistence context. - */ - List dirtyBeans(); - /** * Wrapper on a bean to also indicate if a bean has been deleted. *

* If a bean has been deleted then for the same persistence context is should * not be able to be fetched from persistence context or L2 cache. - *

*/ class WithOption { diff --git a/ebean-api/src/main/java/io/ebean/common/AbstractBeanCollection.java b/ebean-api/src/main/java/io/ebean/common/AbstractBeanCollection.java index f1a608361..809ca13b3 100644 --- a/ebean-api/src/main/java/io/ebean/common/AbstractBeanCollection.java +++ b/ebean-api/src/main/java/io/ebean/common/AbstractBeanCollection.java @@ -135,6 +135,11 @@ abstract class AbstractBeanCollection implements BeanCollection { // Support for modify additions deletions etc - ManyToMany // --------------------------------------------------------- + @Override + public boolean hasModifications() { + return modifyHolder != null && modifyHolder.hasModifications(); + } + @Override public ModifyListenMode getModifyListening() { return modifyListenMode; @@ -145,7 +150,6 @@ abstract class AbstractBeanCollection implements BeanCollection { */ @Override public void setModifyListening(ModifyListenMode mode) { - this.modifyListenMode = mode; this.modifyListening = mode != null && ModifyListenMode.NONE != mode; if (modifyListening) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiBeanType.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiBeanType.java new file mode 100644 index 000000000..9923fff3f --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiBeanType.java @@ -0,0 +1,17 @@ +package io.ebeaninternal.api; + +import io.ebean.bean.EntityBean; + +/** + * SPI interface for underlying BeanDescriptor. + */ +public interface SpiBeanType { + + /** + * Return true if the bean contains a many property that has modifications. + *

+ * That is a ManyToMany or a OneToMany with orphan removal with additions + * or removals from the collection. + */ + boolean isToManyDirty(EntityBean bean); +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiBeanTypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiBeanTypeManager.java new file mode 100644 index 000000000..0a6b79d78 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiBeanTypeManager.java @@ -0,0 +1,13 @@ +package io.ebeaninternal.api; + +/** + * Manager of SpiBeanTypes. + */ +public interface SpiBeanTypeManager { + + /** + * Return the bean type for the given entity class. + */ + SpiBeanType getBeanType(Class entityType); + +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiPersistenceContext.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiPersistenceContext.java new file mode 100644 index 000000000..a11f9d2f4 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiPersistenceContext.java @@ -0,0 +1,17 @@ +package io.ebeaninternal.api; + +import io.ebean.bean.PersistenceContext; + +import java.util.List; + +/** + * SPI extension to PersistenceContext. + */ +public interface SpiPersistenceContext extends PersistenceContext { + + /** + * Return the list of dirty beans held by this persistence context. + */ + List dirtyBeans(SpiBeanTypeManager manager); + +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java index 8c59fa7dc..d26ab3ecc 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java @@ -3,7 +3,6 @@ package io.ebeaninternal.api; import io.ebean.ProfileLocation; import io.ebean.Transaction; import io.ebean.annotation.DocStoreMode; -import io.ebean.bean.PersistenceContext; import io.ebean.event.changelog.BeanChange; import io.ebean.event.changelog.ChangeSet; import io.ebeaninternal.server.core.PersistDeferredRelationship; @@ -196,7 +195,7 @@ public interface SpiTransaction extends Transaction { * later. This is along the lines of 'extended persistence context' * behaviour. */ - PersistenceContext getPersistenceContext(); + SpiPersistenceContext getPersistenceContext(); /** * Set the persistence context to this transaction. @@ -208,7 +207,7 @@ public interface SpiTransaction extends Transaction { * and setPersistenceContext() enable a developer to reuse a single * PersistenceContext with multiple transactions. */ - void setPersistenceContext(PersistenceContext context); + void setPersistenceContext(SpiPersistenceContext context); /** * Return the underlying Connection for internal use. diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java index 5fd2a3c6e..a63c04cc6 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java @@ -3,7 +3,6 @@ package io.ebeaninternal.api; import io.ebean.ProfileLocation; import io.ebean.TransactionCallback; import io.ebean.annotation.DocStoreMode; -import io.ebean.bean.PersistenceContext; import io.ebean.event.changelog.BeanChange; import io.ebean.event.changelog.ChangeSet; import io.ebeaninternal.server.core.PersistDeferredRelationship; @@ -379,12 +378,12 @@ public abstract class SpiTransactionProxy implements SpiTransaction { } @Override - public PersistenceContext getPersistenceContext() { + public SpiPersistenceContext getPersistenceContext() { return transaction.getPersistenceContext(); } @Override - public void setPersistenceContext(PersistenceContext context) { + public void setPersistenceContext(SpiPersistenceContext context) { transaction.setPersistenceContext(context); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index 68401b5e3..326cbae00 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -38,15 +38,7 @@ import io.ebean.plugin.BeanType; import io.ebean.plugin.ExpressionPath; import io.ebean.plugin.Property; import io.ebean.util.SplitName; -import io.ebeaninternal.api.BeanCacheResult; -import io.ebeaninternal.api.CQueryPlanKey; -import io.ebeaninternal.api.ConcurrencyMode; -import io.ebeaninternal.api.LoadBeanContext; -import io.ebeaninternal.api.LoadContext; -import io.ebeaninternal.api.SpiEbeanServer; -import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.api.SpiTransaction; -import io.ebeaninternal.api.SpiUpdatePlan; +import io.ebeaninternal.api.*; import io.ebeaninternal.api.TransactionEventTable.TableIUD; import io.ebeaninternal.api.json.SpiJsonReader; import io.ebeaninternal.api.json.SpiJsonWriter; @@ -119,7 +111,7 @@ import static io.ebeaninternal.server.persist.DmlUtil.isNullOrZero; /** * Describes Beans including their deployment information. */ -public class BeanDescriptor implements BeanType, STreeType { +public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { private static final Logger logger = LoggerFactory.getLogger(BeanDescriptor.class); @@ -1880,7 +1872,7 @@ public class BeanDescriptor implements BeanType, STreeType { if (refBean == null) { refBean = createReference(readOnly, false, id, pc); } - return (EntityBean)refBean; + return (EntityBean) refBean; } /** @@ -2937,6 +2929,20 @@ public class BeanDescriptor implements BeanType, STreeType { return false; } + @Override + public boolean isToManyDirty(EntityBean bean) { + final EntityBeanIntercept ebi = bean._ebean_getIntercept(); + for (BeanPropertyAssocMany many : propertiesManySave) { + if (ebi.isLoadedProperty(many.getPropertyIndex())) { + final BeanCollection value = (BeanCollection) many.getValue(bean); + if (value != null && value.hasModifications()) { + return true; + } + } + } + return false; + } + /** * Return true if the bean is draftable and considered a 'live' instance. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java index dca6b2b09..b660c1718 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -26,9 +26,7 @@ import io.ebean.meta.MetricVisitor; import io.ebean.meta.QueryPlanInit; import io.ebean.plugin.BeanType; import io.ebean.util.AnnotationUtil; -import io.ebeaninternal.api.ConcurrencyMode; -import io.ebeaninternal.api.SpiEbeanServer; -import io.ebeaninternal.api.TransactionEventTable; +import io.ebeaninternal.api.*; import io.ebeaninternal.server.cache.CacheChangeSet; import io.ebeaninternal.server.cache.SpiCacheManager; import io.ebeaninternal.server.core.InternString; @@ -88,7 +86,7 @@ import java.util.concurrent.TimeUnit; /** * Creates BeanDescriptors. */ -public class BeanDescriptorManager implements BeanDescriptorMap { +public class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTypeManager { private static final Logger logger = LoggerFactory.getLogger(BeanDescriptorManager.class); @@ -263,6 +261,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap { return descQueueMap.get(queueId); } + @Override + public SpiBeanType getBeanType(Class entityType) { + return getBeanDescriptor(entityType); + } + @Override @SuppressWarnings("unchecked") public BeanDescriptor getBeanDescriptor(Class entityType) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java index 27eea2e4c..1064bf8bc 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java @@ -2,6 +2,9 @@ package io.ebeaninternal.server.transaction; import io.ebean.bean.EntityBean; import io.ebean.bean.PersistenceContext; +import io.ebeaninternal.api.SpiBeanType; +import io.ebeaninternal.api.SpiBeanTypeManager; +import io.ebeaninternal.api.SpiPersistenceContext; import java.util.*; import java.util.concurrent.locks.ReentrantLock; @@ -11,19 +14,16 @@ import java.util.concurrent.locks.ReentrantLock; *

* Ensures only one instance of a bean is used according to its type and unique * id. - *

*

* PersistenceContext lives on a Transaction and as such is expected to only * have a single thread accessing it at a time. This is not expected to be used * concurrently. - *

*

* Duplicate beans are ones having the same type and unique id value. These are * considered duplicates and replaced by the bean instance that was already * loaded into the PersistenceContext. - *

*/ -public final class DefaultPersistenceContext implements PersistenceContext { +public final class DefaultPersistenceContext implements SpiPersistenceContext { /** * Map used hold caches. One cache per bean type. @@ -199,12 +199,12 @@ public final class DefaultPersistenceContext implements PersistenceContext { } @Override - public List dirtyBeans() { + public List dirtyBeans(SpiBeanTypeManager manager) { lock.lock(); try { List list = new ArrayList<>(); for (ClassContext classContext : typeCache.values()) { - classContext.dirtyBeans(list); + classContext.dirtyBeans(manager, list); } return list; } finally { @@ -223,26 +223,26 @@ public final class DefaultPersistenceContext implements PersistenceContext { } private ClassContext getClassContext(Class rootType) { - return typeCache.computeIfAbsent(rootType, k -> new ClassContext()); + return typeCache.computeIfAbsent(rootType, k -> new ClassContext(rootType)); } private static class ClassContext { private final Map map = new HashMap<>(); - + private final Class rootType; private Set deleteSet; - private int initialSize; - private ClassContext parent; - private ClassContext() { + private ClassContext(Class rootType) { + this.rootType = rootType; } /** * Create as a shallow copy. */ private ClassContext(ClassContext source, boolean initial) { + this.rootType = source.rootType; if (initial || source.isTransfer()) { parent = source.transferParent(); initialSize = parent.size(); @@ -334,9 +334,11 @@ public final class DefaultPersistenceContext implements PersistenceContext { /** * Add the dirty beans to the list. */ - void dirtyBeans(List list) { + void dirtyBeans(SpiBeanTypeManager manager, List list) { + final SpiBeanType beanType = manager.getBeanType(rootType); for (Object value : map.values()) { - if (((EntityBean) value)._ebean_getIntercept().isDirty()) { + EntityBean bean = (EntityBean) value; + if (bean._ebean_getIntercept().isDirty() || beanType.isToManyDirty(bean)) { list.add(value); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java index 5f2b5d195..e4e36e337 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java @@ -3,13 +3,9 @@ package io.ebeaninternal.server.transaction; import io.ebean.ProfileLocation; import io.ebean.TransactionCallback; import io.ebean.annotation.DocStoreMode; -import io.ebean.bean.PersistenceContext; import io.ebean.event.changelog.BeanChange; import io.ebean.event.changelog.ChangeSet; -import io.ebeaninternal.api.SpiProfileTransactionEvent; -import io.ebeaninternal.api.SpiTransaction; -import io.ebeaninternal.api.TransactionEvent; -import io.ebeaninternal.api.TxnProfileEventCodes; +import io.ebeaninternal.api.*; import io.ebeaninternal.server.core.PersistDeferredRelationship; import io.ebeaninternal.server.core.PersistRequestBean; import io.ebeaninternal.server.persist.BatchControl; @@ -58,7 +54,7 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode /** * Holder of the objects fetched to ensure unique objects are used. */ - private PersistenceContext persistenceContext; + private SpiPersistenceContext persistenceContext; private Object tenantId; @@ -421,7 +417,7 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode * Return the persistence context associated with this transaction. */ @Override - public PersistenceContext getPersistenceContext() { + public SpiPersistenceContext getPersistenceContext() { return persistenceContext; } @@ -434,7 +430,7 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode *

*/ @Override - public void setPersistenceContext(PersistenceContext context) { + public void setPersistenceContext(SpiPersistenceContext context) { if (!isActive()) { throw new IllegalStateException(illegalStateMessage); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java index 9f59c44bd..b4bd7b714 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java @@ -3,20 +3,16 @@ package io.ebeaninternal.server.transaction; import io.ebean.ProfileLocation; import io.ebean.TransactionCallback; import io.ebean.annotation.DocStoreMode; -import io.ebean.bean.PersistenceContext; import io.ebean.config.DatabaseConfig; import io.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly; import io.ebean.event.changelog.BeanChange; import io.ebean.event.changelog.ChangeSet; -import io.ebeaninternal.api.SpiProfileTransactionEvent; -import io.ebeaninternal.api.SpiTransaction; -import io.ebeaninternal.api.TransactionEvent; -import io.ebeaninternal.api.TxnProfileEventCodes; +import io.ebeaninternal.api.*; import io.ebeaninternal.server.core.PersistDeferredRelationship; import io.ebeaninternal.server.core.PersistRequestBean; -import io.ebeaninternal.server.util.Str; import io.ebeaninternal.server.persist.BatchControl; import io.ebeaninternal.server.persist.BatchedSqlException; +import io.ebeaninternal.server.util.Str; import io.ebeanservice.docstore.api.DocStoreTransaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,12 +21,7 @@ import javax.persistence.PersistenceException; import javax.persistence.RollbackException; import java.sql.Connection; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Consumer; /** @@ -99,7 +90,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes { /** * Holder of the objects fetched to ensure unique objects are used. */ - private PersistenceContext persistenceContext; + private SpiPersistenceContext persistenceContext; /** * Used to give developers more control over the insert update and delete @@ -806,7 +797,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes { * Return the persistence context associated with this transaction. */ @Override - public PersistenceContext getPersistenceContext() { + public SpiPersistenceContext getPersistenceContext() { return persistenceContext; } @@ -816,10 +807,9 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes { * This could be considered similar to EJB3 Extended PersistanceContext. In * that you get the PersistanceContext from a transaction, hold onto it, and * then set it back later to a second transaction. - *

*/ @Override - public void setPersistenceContext(PersistenceContext context) { + public void setPersistenceContext(SpiPersistenceContext context) { if (!isActive()) { throw new IllegalStateException(illegalStateMessage); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java index 0d3c82cd6..d435e93fd 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java @@ -3,9 +3,9 @@ package io.ebeaninternal.server.transaction; import io.ebean.ProfileLocation; import io.ebean.TransactionCallback; import io.ebean.annotation.DocStoreMode; -import io.ebean.bean.PersistenceContext; import io.ebean.event.changelog.BeanChange; import io.ebean.event.changelog.ChangeSet; +import io.ebeaninternal.api.SpiPersistenceContext; import io.ebeaninternal.api.SpiProfileTransactionEvent; import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.api.TransactionEvent; @@ -349,12 +349,12 @@ class NoTransaction implements SpiTransaction { } @Override - public PersistenceContext getPersistenceContext() { + public SpiPersistenceContext getPersistenceContext() { return null; } @Override - public void setPersistenceContext(PersistenceContext context) { + public void setPersistenceContext(SpiPersistenceContext context) { } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java index 0728eebac..e3fbdd425 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java @@ -5,7 +5,6 @@ import io.ebean.ProfileLocation; import io.ebean.TxScope; import io.ebean.annotation.PersistBatch; import io.ebean.annotation.TxType; -import io.ebean.bean.PersistenceContext; import io.ebean.cache.ServerCacheNotification; import io.ebean.cache.ServerCacheNotify; import io.ebean.config.CurrentTenantProvider; @@ -19,15 +18,7 @@ import io.ebean.metric.MetricFactory; import io.ebean.metric.TimedMetric; import io.ebean.metric.TimedMetricMap; import io.ebean.plugin.SpiServer; -import io.ebeaninternal.api.ScopeTrans; -import io.ebeaninternal.api.ScopedTransaction; -import io.ebeaninternal.api.SpiLogManager; -import io.ebeaninternal.api.SpiLogger; -import io.ebeaninternal.api.SpiProfileHandler; -import io.ebeaninternal.api.SpiTransaction; -import io.ebeaninternal.api.SpiTransactionManager; -import io.ebeaninternal.api.TransactionEvent; -import io.ebeaninternal.api.TransactionEventTable; +import io.ebeaninternal.api.*; import io.ebeaninternal.api.TransactionEventTable.TableIUD; import io.ebeaninternal.server.cache.CacheChangeSet; import io.ebeaninternal.server.cluster.ClusterManager; @@ -770,8 +761,8 @@ public class TransactionManager implements SpiTransactionManager { /** * Experimental - find dirty beans in the persistence context and persist them. */ - public void flushTransparent(PersistenceContext persistenceContext, SpiTransaction transaction) { - List dirtyBeans = persistenceContext.dirtyBeans(); + public void flushTransparent(SpiPersistenceContext persistenceContext, SpiTransaction transaction) { + List dirtyBeans = persistenceContext.dirtyBeans(beanDescriptorManager); if (!dirtyBeans.isEmpty()) { server.updateAll(dirtyBeans, transaction); } diff --git a/ebean-core/src/test/java/org/tests/model/basic/Order.java b/ebean-core/src/test/java/org/tests/model/basic/Order.java index b15765b51..cea0a6e00 100644 --- a/ebean-core/src/test/java/org/tests/model/basic/Order.java +++ b/ebean-core/src/test/java/org/tests/model/basic/Order.java @@ -101,7 +101,7 @@ public class Order implements Serializable { @DocEmbedded List details; - @OneToMany(cascade = CascadeType.ALL, mappedBy = "order") + @OneToMany(cascade = CascadeType.ALL, mappedBy = "order", orphanRemoval = true) List shipments; @Override diff --git a/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java b/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java index 0ed40203f..8a870f8ab 100644 --- a/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java +++ b/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java @@ -2,9 +2,11 @@ package org.tests.transparentpersist; import io.ebean.*; import io.ebean.annotation.*; +import io.ebeaninternal.api.SpiBeanTypeManager; import io.ebeaninternal.api.SpiTransaction; import io.ebeantest.LoggedSql; import org.junit.Test; +import org.mockito.Mockito; import org.tests.model.basic.Customer; import org.tests.model.basic.EBasicVer; import org.tests.model.basic.Order; @@ -200,8 +202,6 @@ public class TestTransparentPersist extends BaseTestCase { foundOrder.getShipments().remove(0); OrderShipment osh2 = new OrderShipment(); foundOrder.addShipment(osh2); - // if this is called insert is performed but no delete - // DB.save(foundOrder); transaction.commit(); } @@ -305,7 +305,8 @@ public class TestTransparentPersist extends BaseTestCase { } private List getDirtyBeansFromPersistenceContext(Transaction transaction) { - return ((SpiTransaction)transaction).getPersistenceContext().dirtyBeans(); + final SpiBeanTypeManager mgr = Mockito.mock(SpiBeanTypeManager.class); + return ((SpiTransaction)transaction).getPersistenceContext().dirtyBeans(mgr); } } From 66cb6d5e7d933ab71c5df78566ff7138aef498c4 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Mon, 21 Jun 2021 14:50:15 +0200 Subject: [PATCH 60/72] Imporved @Where support, especially for Many-to-many tables --- ebean-core/pom.xml | 2 +- .../io/ebeaninternal/api/LoadManyRequest.java | 2 +- .../server/deploy/BeanDescriptor.java | 7 + .../server/deploy/BeanDescriptorManager.java | 6 + .../server/deploy/BeanDescriptorMap.java | 6 + .../server/deploy/BeanPropertyAssocMany.java | 25 ++- .../server/deploy/DbSqlContext.java | 2 +- .../server/deploy/TableJoin.java | 6 +- .../deploy/meta/DeployBeanPropertyAssoc.java | 1 + .../server/deploy/meta/DeployTableJoin.java | 14 ++ .../deploy/parse/AnnotationAssocManys.java | 2 +- .../deploy/parse/AnnotationAssocOnes.java | 2 +- .../server/deploy/parse/AnnotationFields.java | 4 +- .../server/deploy/parse/AnnotationParser.java | 7 + .../server/query/DefaultDbSqlContext.java | 10 +- .../server/query/SqlTreeNodeManyRoot.java | 10 -- .../java/org/tests/model/m2m/MnyEdge.java | 57 +++++++ .../java/org/tests/model/m2m/MnyNode.java | 129 ++++++++++++++ .../org/tests/model/m2m/TestM2MWithWhere.java | 159 ++++++++++++++++++ .../java/org/tests/o2m/OmBasicParent.java | 16 ++ .../org/tests/o2m/TestOneToManyWhere.java | 44 +++++ .../TestImplicitJoinOnParentRelationship.java | 12 +- .../org/tests/query/TestManyWhereJoin.java | 2 +- .../org/tests/query/TestQueryFilterMany.java | 2 +- .../java/org/tests/query/TestQueryIsNull.java | 41 ++--- .../other/TestQueryRawExpressionMany.java | 2 +- .../other/TestQueryRowCountWithMany.java | 4 +- .../query/other/TestQuerySingleAttribute.java | 2 +- .../build/ModelBuildPropertyVisitor.java | 2 +- 29 files changed, 525 insertions(+), 53 deletions(-) create mode 100644 ebean-core/src/test/java/org/tests/model/m2m/MnyEdge.java create mode 100644 ebean-core/src/test/java/org/tests/model/m2m/MnyNode.java create mode 100644 ebean-core/src/test/java/org/tests/model/m2m/TestM2MWithWhere.java create mode 100644 ebean-core/src/test/java/org/tests/o2m/TestOneToManyWhere.java diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 5575c187b..8e3d4b068 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -72,7 +72,7 @@ io.ebean ebean-ddl-generator - 12.9.0 + 12.9.3a test diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/LoadManyRequest.java b/ebean-core/src/main/java/io/ebeaninternal/api/LoadManyRequest.java index aeb9ff7dd..0343b8579 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/LoadManyRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/LoadManyRequest.java @@ -119,7 +119,7 @@ public class LoadManyRequest extends LoadRequest { if (extraWhere != null) { // replace special ${ta} placeholder with the base table alias // which is always t0 and add the extra where clause - query.where().raw(extraWhere.replace("${ta}", "t0")); + query.where().raw(extraWhere.replace("${ta}", "t0").replace("${mta}", "int_")); } query.setLazyLoadForParents(many); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index 326cbae00..9b8e7768c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -2009,6 +2009,13 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { return owner.getBeanDescriptor(otherType); } + /** + * Returns true, if the table is managed (i.e. an existing m2m relation). + */ + public boolean isTableManaged(String tableName) { + return owner.isTableManaged(tableName); + } + /** * Return the order column property. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java index b660c1718..84b412513 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -445,6 +445,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTypeMana return tableToDescMap.get(tableName.toLowerCase()); } + @Override + public boolean isTableManaged(String tableName) { + return tableToDescMap.get(tableName.toLowerCase()) != null + || tableToViewDescMap.get(tableName.toLowerCase()) != null; + } + /** * Invalidate entity beans based on views via their dependent tables. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorMap.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorMap.java index 7c7dd6e0c..94a9fded9 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorMap.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorMap.java @@ -76,4 +76,10 @@ public interface BeanDescriptorMap { * Return true if Jackson core is present on the classpath. */ boolean isJacksonCorePresent(); + + /** + * Returns true, if the given table (or view) is managed by ebean + * (= an entity exists) + */ + boolean isTableManaged(String tableName); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java index 8e1ac1a14..c823e9a7e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java @@ -439,6 +439,7 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc implements ST @Override public String getAssocIsEmpty(SpiExpressionRequest request, String path) { boolean softDelete = targetDescriptor.isSoftDelete(); + boolean needsX2Table = softDelete || getExtraWhere() != null; StringBuilder sb = new StringBuilder(50); SpiQuery query = request.getQueryRequest().getQuery(); if (hasJoinTable()) { @@ -446,7 +447,7 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc implements ST } else { sb.append(targetDescriptor.getBaseTable(query.getTemporalMode())); } - if (softDelete && hasJoinTable()) { + if (needsX2Table && hasJoinTable()) { sb.append(" x join "); sb.append(targetDescriptor.getBaseTable(query.getTemporalMode())); sb.append(" x2 on "); @@ -461,6 +462,16 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc implements ST } exportedProperties[i].appendWhere(sb, "x.", path); } + if (getExtraWhere() != null) { + sb.append(" and "); + if (hasJoinTable()) { + sb.append(getExtraWhere().replace("${ta}", "x2").replace("${mta}", "x")); + } else { + sb.append(getExtraWhere().replace("${ta}", "x")); + } + } + + if (softDelete) { String alias = hasJoinTable() ? "x2" : "x"; sb.append(" and ").append(targetDescriptor.getSoftDeletePredicate(alias)); @@ -1061,4 +1072,16 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc implements ST public void bindElementValue(SqlUpdate insert, Object value) { targetDescriptor.bindElementValue(insert, value); } + + /** + * Returns true, if we must create a m2m join table. + */ + public boolean createJoinTable() { + if (hasJoinTable() && getMappedBy() == null) { + // only create on other 'owning' side + return !descriptor.isTableManaged(intersectionJoin.getTable()); + } else { + return false; + } + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DbSqlContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DbSqlContext.java index 257893820..1e078c0fd 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DbSqlContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DbSqlContext.java @@ -10,7 +10,7 @@ public interface DbSqlContext { /** * Add a join to the sql query. */ - void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2); + void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2, String extraWhere); /** * Push the current table alias onto the stack. diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/TableJoin.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/TableJoin.java index e88e56034..8c1624d05 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/TableJoin.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/TableJoin.java @@ -34,6 +34,8 @@ public final class TableJoin { private final int queryHash; private final PropertyForeignKey foreignKey; + + private final String extraWhere; public TableJoin(DeployTableJoin deploy) { this(deploy, null); @@ -44,6 +46,7 @@ public final class TableJoin { */ public TableJoin(DeployTableJoin deploy, PropertyForeignKey foreignKey) { this.foreignKey = foreignKey; + this.extraWhere = deploy.getExtraWhere(); this.table = InternString.intern(deploy.getTable()); this.type = deploy.getType(); this.inheritInfo = deploy.getInheritInfo(); @@ -57,6 +60,7 @@ public final class TableJoin { private TableJoin(TableJoin source, String overrideColumn) { this.foreignKey = null; + this.extraWhere = source.extraWhere; this.table = source.table; this.type = source.type; this.inheritInfo = source.inheritInfo; @@ -146,7 +150,7 @@ public final class TableJoin { public SqlJoinType addJoin(SqlJoinType joinType, String a1, String a2, DbSqlContext ctx) { String joinLiteral = joinType.getLiteral(type); - ctx.addJoin(joinLiteral, table, columns(), a1, a2); + ctx.addJoin(joinLiteral, table, columns(), a1, a2, extraWhere); return joinType.autoToOuter(type); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java index 472a71db3..be4b77aa0 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java @@ -86,6 +86,7 @@ public abstract class DeployBeanPropertyAssoc extends DeployBeanProperty { * collection. */ public void setExtraWhere(String extraWhere) { + this.tableJoin.setExtraWhere(extraWhere); this.extraWhere = extraWhere; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployTableJoin.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployTableJoin.java index d24024774..c3451f9f4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployTableJoin.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployTableJoin.java @@ -33,6 +33,8 @@ public class DeployTableJoin { private ArrayList columns = new ArrayList<>(4); private InheritInfo inheritInfo; + + private String extraWhere; /** * Create a DeployTableJoin. @@ -137,6 +139,18 @@ public class DeployTableJoin { this.type = type; } + /** + * Returns the clause of an extra @Where annotation. + * @return + */ + public String getExtraWhere() { + return extraWhere; + } + + public void setExtraWhere(String extraWhere) { + this.extraWhere = extraWhere; + } + public DeployTableJoin createInverse(String tableName) { DeployTableJoin inverse = new DeployTableJoin(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java index b47612d65..1ca59dbc8 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java @@ -118,7 +118,7 @@ class AnnotationAssocManys extends AnnotationAssoc { Where where = prop.getMetaAnnotationWhere(platform); if (where != null) { - prop.setExtraWhere(where.clause()); + prop.setExtraWhere(processFormula(where.clause())); } FetchPreference fetchPreference = get(prop, FetchPreference.class); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java index b5929d6a0..36fa99b57 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java @@ -96,7 +96,7 @@ public class AnnotationAssocOnes extends AnnotationAssoc { Where where = prop.getMetaAnnotationWhere(platform); if (where != null) { // not expecting this to be used on assoc one properties - prop.setExtraWhere(where.clause()); + prop.setExtraWhere(processFormula(where.clause())); } PrimaryKeyJoinColumn primaryKeyJoin = get(prop, PrimaryKeyJoinColumn.class); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java index 8079f156f..5a0caa287 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -153,7 +153,7 @@ public class AnnotationFields extends AnnotationParser { Formula formula = prop.getMetaAnnotationFormula(platform); if (formula != null) { - prop.setSqlFormula(formula.select(), formula.join()); + prop.setSqlFormula(processFormula(formula.select()), processFormula(formula.join())); } initWhoProperties(prop); @@ -334,7 +334,7 @@ public class AnnotationFields extends AnnotationParser { } Formula formula = prop.getMetaAnnotationFormula(platform); if (formula != null) { - prop.setSqlFormula(formula.select(), formula.join()); + prop.setSqlFormula(processFormula(formula.select()), processFormula(formula.join())); } final Aggregation aggregation = prop.getMetaAnnotation(Aggregation.class); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java index af015e1f5..ea880a18b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java @@ -129,4 +129,11 @@ public abstract class AnnotationParser extends AnnotationBase { } return columnNames; } + + /** + * Process any formula from @Formula or @Where. + */ + protected String processFormula(String source) { + return source == null ? null : source.replace("${dbTableName}", descriptor.getBaseTable()); + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultDbSqlContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultDbSqlContext.java index 981abae74..9ee2c253c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultDbSqlContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultDbSqlContext.java @@ -111,7 +111,7 @@ class DefaultDbSqlContext implements DbSqlContext { } @Override - public void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2) { + public void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2, String extraWhere) { if (tableJoins == null) { tableJoins = new HashSet<>(); @@ -164,6 +164,14 @@ class DefaultDbSqlContext implements DbSqlContext { if (addAsOfOnClause) { sb.append(" and ").append(historySupport.getAsOfPredicate(a2)); } + + if (extraWhere != null && !extraWhere.isEmpty()) { + sb.append(" and "); + // we will also need a many-table alias here + sb.append(extraWhere.replace(tableAliasPlaceHolder, a2).replace("${mta}", a1)); + } + + } private void appendTable(String table, String draftTable) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java index 8c5517a76..d920b57ef 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java @@ -47,16 +47,6 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean { ctx.popTableAlias(); } - /** - * append extraWhere to the join. - */ - @Override - protected SqlJoinType appendFromAsJoin(DbSqlContext ctx, SqlJoinType joinType) { - SqlJoinType join = super.appendFromAsJoin(ctx, joinType); - super.appendExtraWhere(ctx); - return join; - } - @Override protected void appendExtraWhere(DbSqlContext ctx) { // extraWhere is already appended to the tableJoin diff --git a/ebean-core/src/test/java/org/tests/model/m2m/MnyEdge.java b/ebean-core/src/test/java/org/tests/model/m2m/MnyEdge.java new file mode 100644 index 000000000..2e86cb9df --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/m2m/MnyEdge.java @@ -0,0 +1,57 @@ +package org.tests.model.m2m; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import io.ebean.annotation.Index; + +@Entity +@Index(unique = true, columnNames = { "from_id", "to_id" }) +@Index(unique = true, columnNames = { "to_id", "from_id" }) +public class MnyEdge { + + @Id + private Integer id; + + @ManyToOne + private MnyNode from; + + @ManyToOne + private MnyNode to; + + private int flags; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public MnyNode getFrom() { + return from; + } + + public void setFrom(MnyNode from) { + this.from = from; + } + + public MnyNode getTo() { + return to; + } + + public void setTo(MnyNode to) { + this.to = to; + } + + public int getFlags() { + return flags; + } + + public void setFlags(int flags) { + this.flags = flags; + } + +} diff --git a/ebean-core/src/test/java/org/tests/model/m2m/MnyNode.java b/ebean-core/src/test/java/org/tests/model/m2m/MnyNode.java new file mode 100644 index 000000000..e51c5f417 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/m2m/MnyNode.java @@ -0,0 +1,129 @@ +package org.tests.model.m2m; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; + +import io.ebean.annotation.Platform; +import io.ebean.annotation.Where; + +import java.util.List; + +@Entity +public class MnyNode { + + @Id + Integer id; + + String name; + + @ManyToMany + @JoinTable(name = "mny_edge", + joinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"), + inverseJoinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id")) + List allRelations; + + @ManyToMany + @JoinTable(name = "mny_edge", + joinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"), + inverseJoinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id")) + List allReverseRelations; + + @ManyToMany + @JoinTable(name = "mny_edge", + joinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"), + inverseJoinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id")) + @Where(clause = "${mta}.flags & 1 != 0") + @Where(clause = "BITAND(${mta}.flags, 1) != 0", platforms = Platform.H2) + List bit1Relations; + + @ManyToMany + @JoinTable(name = "mny_edge", + joinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"), + inverseJoinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id")) + @Where(clause = "${mta}.flags & 1 != 0") + @Where(clause = "BITAND(${mta}.flags, 1) != 0", platforms = Platform.H2) + List bit1ReverseRelations; + + @ManyToMany + @JoinTable(name = "mny_edge", + joinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"), + inverseJoinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id")) + @Where(clause = "${mta}.flags & 2 != 0") + @Where(clause = "BITAND(${mta}.flags, 2) != 0", platforms = Platform.H2) + List bit2Relations; + + @ManyToMany + @JoinTable(name = "mny_edge", + joinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"), + inverseJoinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id")) + @Where(clause = "${mta}.flags & 2 != 0") + @Where(clause = "BITAND(${mta}.flags, 2) != 0", platforms = Platform.H2) + List bit2ReverseRelations; + + @ManyToMany + @JoinTable(name = "mny_edge", + joinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"), + inverseJoinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id")) + @Where(clause = "'${dbTableName}' = ${ta}.name") + List withDbTableName; + + public MnyNode() { + + } + + public MnyNode(String name) { + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getAllRelations() { + return allRelations; + } + + public List getAllReverseRelations() { + return allReverseRelations; + } + + public List getBit1Relations() { + return bit1Relations; + } + + public List getBit1ReverseRelations() { + return bit1ReverseRelations; + } + + public List getBit2Relations() { + return bit2Relations; + } + + public List getBit2ReverseRelations() { + return bit2ReverseRelations; + } + + public List getWithDbTableName() { + return withDbTableName; + } + + public void setWithDbTableName(List withDbTableName) { + this.withDbTableName = withDbTableName; + } + +} diff --git a/ebean-core/src/test/java/org/tests/model/m2m/TestM2MWithWhere.java b/ebean-core/src/test/java/org/tests/model/m2m/TestM2MWithWhere.java new file mode 100644 index 000000000..da8d83dd5 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/m2m/TestM2MWithWhere.java @@ -0,0 +1,159 @@ +package org.tests.model.m2m; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.junit.Test; +import org.tests.model.m2m.MnyEdge; +import org.tests.model.m2m.MnyNode; + +import io.ebean.BaseTestCase; +import io.ebean.DB; +import io.ebeantest.LoggedSql; + +/** + * Tests M2M with complex where queries. + * + * @author Roland Praml, FOCONIS AG + * + */ +public class TestM2MWithWhere extends BaseTestCase { + + @Test + public void testQuery() throws Exception { + createTestData(); + MnyNode node = DB.find(MnyNode.class, 1); + + + List result = DB.find(MnyNode.class).where().eq("allRelations", node).findList(); + assertThat(result).extracting(MnyNode::getId).containsExactly(1, 2, 3, 4, 5); + + result = DB.find(MnyNode.class).where().eq("allReverseRelations", node).findList(); + assertThat(result).extracting(MnyNode::getId).containsExactly(1, 2, 3, 4, 5); + + result = DB.find(MnyNode.class).where().eq("bit1Relations", node).findList(); + assertThat(result).isEmpty(); // -> to = 1 column: 2 0 2 0 2 + + result = DB.find(MnyNode.class).where().eq("bit2Relations", node).findList(); + assertThat(result).extracting(MnyNode::getId).containsExactly(1, 3, 5); + + result = DB.find(MnyNode.class).where().eq("bit1ReverseRelations", node).findList(); + // -> from = 1 column: 2 1 3 1 3 + assertThat(result).extracting(MnyNode::getId).containsExactly(2, 3, 4, 5); + + result = DB.find(MnyNode.class).where().eq("bit2ReverseRelations", node).findList(); + assertThat(result).hasSize(3).extracting(MnyNode::getId).containsExactly(1, 3, 5); + + result = DB.find(MnyNode.class).where().eq("bit2ReverseRelations", node).findList(); + assertThat(result).hasSize(3).extracting(MnyNode::getId).containsExactly(1, 3, 5); + } + + @Test + public void testGetter() throws Exception { + createTestData(); + MnyNode node = DB.find(MnyNode.class, 3); + + assertThat(node.getAllRelations()).extracting(MnyNode::getId).containsExactly(1, 2, 3, 4, 5); + + assertThat(node.getAllReverseRelations()).extracting(MnyNode::getId).containsExactly(1, 2, 3, 4, 5); + + assertThat(node.getBit1Relations()).extracting(MnyNode::getId).containsExactly(4, 5); + + assertThat(node.getBit1ReverseRelations()).extracting(MnyNode::getId).containsExactly(1, 2); + + assertThat(node.getBit2Relations()).extracting(MnyNode::getId).containsExactly(1, 3, 5); + + LoggedSql.start(); + assertThat(node.getBit2ReverseRelations()).extracting(MnyNode::getId).containsExactly(1, 3, 5); + List sqls = LoggedSql.stop(); + assertThat(sqls).hasSize(1); // lazy load + + // prefetch everything + LoggedSql.start(); + node = DB.find(MnyNode.class) + .fetch("bit1Relations","*") + .fetch("bit1ReverseRelations","*") + .where().idEq(3).findOne(); + sqls = LoggedSql.stop(); + assertThat(sqls).hasSize(2); + + // no lazyLoad expected + LoggedSql.start(); + assertThat(node.getBit1Relations()).extracting(MnyNode::getId).containsExactly(4, 5); + assertThat(node.getBit1ReverseRelations()).extracting(MnyNode::getId).containsExactly(1, 2); + sqls = LoggedSql.stop(); + assertThat(sqls).hasSize(0); + + } + + // to = | 1 2 3 4 5 + // ---------+--------------- + // from = 1 | 2 1 3 1 3 + // from = 2 | 0 2 1 3 1 + // from = 3 | 2 0 2 1 3 + // from = 4 | 0 2 0 2 1 + // from = 5 | 2 0 2 0 2 + private void createTestData() { + DB.find(MnyEdge.class).delete(); + DB.find(MnyNode.class).delete(); + for (int i = 1; i <= 5; i++) { + MnyNode node = new MnyNode(); + node.setId(i); + node.setName("Node #" + i); + DB.save(node); + } + StringBuilder sb = new StringBuilder(); + for (int from = 1; from <= 5; from++) { + sb.append("from = ").append(from).append(" |"); + for (int to = 1; to <= 5; to++) { + MnyEdge edge = new MnyEdge(); + edge.setFrom(DB.getReference(MnyNode.class, from)); + edge.setTo(DB.getReference(MnyNode.class, to)); + int flags = 0; + if (from < to) { + flags |= 1; + } + if ((from + to) % 2 == 0) { + flags |= 2; + } + edge.setFlags(flags); + DB.save(edge); + sb.append(" ").append(flags); + } + sb.append('\n'); + } + // System.out.println(sb); dump the table + } + + + @Test + public void testWithDbTableName() { + LoggedSql.start(); + DB.find(MnyNode.class).where().isNotNull("withDbTableName.name").findList(); + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("'mny_node' = u1.name"); + + LoggedSql.start(); + DB.find(MnyNode.class).where().isNotEmpty("withDbTableName").findList(); + sql = LoggedSql.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("'mny_node' = x2.name"); + } + + @Test + public void testLazyLoad() throws Exception { + MnyNode el = new MnyNode("testLazyLoad"); + DB.save(el); + LoggedSql.start(); + el = DB.find(MnyNode.class).select("name").where().eq("name", "testLazyLoad").findOne(); + el.getWithDbTableName().size(); // trigger Lazy load + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(2); + assertThat(sql.get(0)).contains("select t0.id, t0.name from mny_node"); + assertThat(sql.get(1)).contains("where 'mny_node' = t0.name"); + DB.delete(el); + } + +} diff --git a/ebean-core/src/test/java/org/tests/o2m/OmBasicParent.java b/ebean-core/src/test/java/org/tests/o2m/OmBasicParent.java index 76faeca0e..15b117bd0 100644 --- a/ebean-core/src/test/java/org/tests/o2m/OmBasicParent.java +++ b/ebean-core/src/test/java/org/tests/o2m/OmBasicParent.java @@ -4,6 +4,9 @@ import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Version; + +import io.ebean.annotation.Where; + import java.util.List; import static javax.persistence.CascadeType.ALL; @@ -26,6 +29,10 @@ public class OmBasicParent { @OneToMany(cascade = ALL, mappedBy = "parent") private List children; + @OneToMany(cascade = ALL, mappedBy = "parent") + @Where(clause = "'${dbTableName}' = ${ta}.name") + private List childrenWithWhere; + public OmBasicParent(String name) { this.name = name; } @@ -57,4 +64,13 @@ public class OmBasicParent { public void setChildren(List children) { this.children = children; } + + public List getChildrenWithWhere() { + return childrenWithWhere; + } + + public void setChildrenWithWhere(List childrenWithWhere) { + this.childrenWithWhere = childrenWithWhere; + } + } diff --git a/ebean-core/src/test/java/org/tests/o2m/TestOneToManyWhere.java b/ebean-core/src/test/java/org/tests/o2m/TestOneToManyWhere.java new file mode 100644 index 000000000..e2dca664a --- /dev/null +++ b/ebean-core/src/test/java/org/tests/o2m/TestOneToManyWhere.java @@ -0,0 +1,44 @@ +package org.tests.o2m; + +import io.ebean.BaseTestCase; +import io.ebean.DB; +import io.ebeantest.LoggedSql; + +import org.junit.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestOneToManyWhere extends BaseTestCase { + + @Test + public void testWithDbTableName() { + LoggedSql.start(); + DB.find(OmBasicParent.class).where().isNotNull("childrenWithWhere.name").findList(); + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("'om_basic_parent' = u1.name"); + + LoggedSql.start(); + DB.find(OmBasicParent.class).where().isNotEmpty("childrenWithWhere").findList(); + sql = LoggedSql.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("'om_basic_parent' = x.name"); + } + + @Test + public void testLazyLoad() throws Exception { + OmBasicParent el = new OmBasicParent("testLazyLoad"); + DB.save(el); + LoggedSql.start(); + el = DB.find(OmBasicParent.class).select("name").where().eq("name", "testLazyLoad").findOne(); + el.getChildrenWithWhere().size(); // trigger Lazy load + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(2); + assertThat(sql.get(0)).contains("select t0.id, t0.name from om_basic_parent"); + assertThat(sql.get(1)).contains("where 'om_basic_parent' = t0.name"); + DB.delete(el); + } + +} diff --git a/ebean-core/src/test/java/org/tests/query/TestImplicitJoinOnParentRelationship.java b/ebean-core/src/test/java/org/tests/query/TestImplicitJoinOnParentRelationship.java index c4aa242eb..eac3303fb 100644 --- a/ebean-core/src/test/java/org/tests/query/TestImplicitJoinOnParentRelationship.java +++ b/ebean-core/src/test/java/org/tests/query/TestImplicitJoinOnParentRelationship.java @@ -24,11 +24,11 @@ public class TestImplicitJoinOnParentRelationship extends BaseTestCase { query.findList(); if (isPostgres()) { - String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id join o_order_detail u2 on u2.order_id = u1.id join o_product u3 on u3.id = u2.product_id where u3.name = ?"; + String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 join o_product u3 on u3.id = u2.product_id where u3.name = ?"; assertThat(sqlOf(query, 1)).contains(expectedSql); } else { - String expectedSql = "select distinct t0.id, t0.name from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id join o_order_detail u2 on u2.order_id = u1.id join o_product u3 on u3.id = u2.product_id where u3.name = ?"; + String expectedSql = "select distinct t0.id, t0.name from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 join o_product u3 on u3.id = u2.product_id where u3.name = ?"; assertThat(sqlOf(query, 1)).contains(expectedSql); } @@ -55,10 +55,10 @@ public class TestImplicitJoinOnParentRelationship extends BaseTestCase { query.findList(); if (isPostgres()) { - String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id left join o_order_detail u2 on u2.order_id = u1.id left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)"; + String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null left join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)"; assertThat(sqlOf(query, 1)).contains(expectedSql); } else { - String expectedSql = "select distinct t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id left join o_order_detail u2 on u2.order_id = u1.id left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)"; + String expectedSql = "select distinct t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null left join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)"; assertThat(sqlOf(query, 1)).contains(expectedSql); } } @@ -76,11 +76,11 @@ public class TestImplicitJoinOnParentRelationship extends BaseTestCase { query.findList(); if (isPostgres()) { - String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id left join o_order_detail u2 on u2.order_id = u1.id left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)"; + String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null left join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)"; assertThat(sqlOf(query, 1)).contains(expectedSql); } else { - String expectedSql = "select distinct t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id left join o_order_detail u2 on u2.order_id = u1.id left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)"; + String expectedSql = "select distinct t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null left join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)"; assertThat(sqlOf(query, 1)).contains(expectedSql); } } diff --git a/ebean-core/src/test/java/org/tests/query/TestManyWhereJoin.java b/ebean-core/src/test/java/org/tests/query/TestManyWhereJoin.java index 250a32709..b90751c83 100644 --- a/ebean-core/src/test/java/org/tests/query/TestManyWhereJoin.java +++ b/ebean-core/src/test/java/org/tests/query/TestManyWhereJoin.java @@ -41,7 +41,7 @@ public class TestManyWhereJoin extends BaseTestCase { } assertThat(sql).contains("join o_order "); assertThat(sql).contains(".status = ?"); - assertThat(sql).contains("t0.id, t0.status from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id where u1.status = ?"); + assertThat(sql).contains("t0.id, t0.status from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null where u1.status = ?"); } @Test diff --git a/ebean-core/src/test/java/org/tests/query/TestQueryFilterMany.java b/ebean-core/src/test/java/org/tests/query/TestQueryFilterMany.java index be58fb8c4..01275cfa0 100644 --- a/ebean-core/src/test/java/org/tests/query/TestQueryFilterMany.java +++ b/ebean-core/src/test/java/org/tests/query/TestQueryFilterMany.java @@ -183,7 +183,7 @@ public class TestQueryFilterMany extends BaseTestCase { List sqlList = LoggedSqlCollector.stop(); assertEquals(1, sqlList.size()); - assertThat(sqlList.get(0)).contains("from o_customer t0 left join o_order t1 on t1.kcustomer_id = t0.id and t1.order_date is not null left join o_customer t2 on t2.id = t1.kcustomer_id where exists (select 1 from o_order x where x.kcustomer_id = t0.id) and 1=0 order by t0.id"); + assertThat(sqlList.get(0)).contains("from o_customer t0 left join o_order t1 on t1.kcustomer_id = t0.id and t1.order_date is not null left join o_customer t2 on t2.id = t1.kcustomer_id where exists (select 1 from o_order x where x.kcustomer_id = t0.id and x.order_date is not null) and 1=0 order by t0.id"); } @Test diff --git a/ebean-core/src/test/java/org/tests/query/TestQueryIsNull.java b/ebean-core/src/test/java/org/tests/query/TestQueryIsNull.java index d2689337b..f80aee9f5 100644 --- a/ebean-core/src/test/java/org/tests/query/TestQueryIsNull.java +++ b/ebean-core/src/test/java/org/tests/query/TestQueryIsNull.java @@ -1,14 +1,15 @@ package org.tests.query; import io.ebean.BaseTestCase; -import io.ebean.Ebean; +import io.ebean.DB; import io.ebean.Query; import org.tests.model.basic.Order; import org.tests.model.basic.ResetBasicData; import org.tests.model.m2m.Role; import org.junit.Test; -import static org.junit.Assert.assertTrue; + +import static org.assertj.core.api.Assertions.assertThat; public class TestQueryIsNull extends BaseTestCase { @@ -17,90 +18,90 @@ public class TestQueryIsNull extends BaseTestCase { public void queryShouldContainIsNullOnColumn() { ResetBasicData.reset(); - Query query = Ebean.find(Order.class).where().isNull("customerName").query(); + Query query = DB.find(Order.class).where().isNull("customerName").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains("name is null")); + assertThat(query.getGeneratedSql()).contains("name is null"); } @Test public void isNotNull_when_OneToMany_expect_existsSubquery() { ResetBasicData.reset(); - Query query = Ebean.find(Order.class).where().isNotNull("details").query(); + Query query = DB.find(Order.class).where().isNotNull("details").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains(" where exists (select 1 from o_order_detail x where x.order_id = t0.id)")); + assertThat(query.getGeneratedSql()).contains(" where exists (select 1 from o_order_detail x where x.order_id = t0.id and x.id > 0)"); } @Test public void isNotEmpty_when_OneToMany_expect_existsSubquery() { ResetBasicData.reset(); - Query query = Ebean.find(Order.class).where().isNotEmpty("details").query(); + Query query = DB.find(Order.class).where().isNotEmpty("details").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains(" where exists (select 1 from o_order_detail x where x.order_id = t0.id)")); + assertThat(query.getGeneratedSql()).contains(" where exists (select 1 from o_order_detail x where x.order_id = t0.id and x.id > 0)"); } @Test public void isNull_when_OneToMany_expect_notExistsSubquery() { ResetBasicData.reset(); - Query query = Ebean.find(Order.class).where().isNull("details").query(); + Query query = DB.find(Order.class).where().isNull("details").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains(" where not exists (select 1 from o_order_detail x where x.order_id = t0.id)")); + assertThat(query.getGeneratedSql()).contains(" where not exists (select 1 from o_order_detail x where x.order_id = t0.id and x.id > 0)"); } @Test public void isEmpty_when_OneToMany_expect_notExistsSubquery() { ResetBasicData.reset(); - Query query = Ebean.find(Order.class).where().isEmpty("details").query(); + Query query = DB.find(Order.class).where().isEmpty("details").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains(" where not exists (select 1 from o_order_detail x where x.order_id = t0.id)")); + assertThat(query.getGeneratedSql()).contains(" where not exists (select 1 from o_order_detail x where x.order_id = t0.id and x.id > 0)"); } @Test public void isEmpty_when_ManyToMany_expect_notExistsSubqueryAndNoJoin() { ResetBasicData.reset(); - Query query = Ebean.find(Role.class).where().isEmpty("permissions").query(); + Query query = DB.find(Role.class).where().isEmpty("permissions").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains("from mt_role t0 where not exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)")); + assertThat(query.getGeneratedSql()).contains("from mt_role t0 where not exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)"); } @Test public void isNull_when_ManyToMany_expect_notExistsSubqueryAndNoJoin() { ResetBasicData.reset(); - Query query = Ebean.find(Role.class).where().isNull("permissions").query(); + Query query = DB.find(Role.class).where().isNull("permissions").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains("from mt_role t0 where not exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)")); + assertThat(query.getGeneratedSql()).contains("from mt_role t0 where not exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)"); } @Test public void isNotEmpty_when_ManyToMany_expect_existsSubqueryAndNoJoin() { ResetBasicData.reset(); - Query query = Ebean.find(Role.class).where().isNotEmpty("permissions").query(); + Query query = DB.find(Role.class).where().isNotEmpty("permissions").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains("from mt_role t0 where exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)")); + assertThat(query.getGeneratedSql()).contains("from mt_role t0 where exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)"); } @Test public void isNotNull_when_ManyToMany_expect_existsSubqueryAndNoJoin() { ResetBasicData.reset(); - Query query = Ebean.find(Role.class).where().isNotNull("permissions").query(); + Query query = DB.find(Role.class).where().isNotNull("permissions").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains("from mt_role t0 where exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)")); + assertThat(query.getGeneratedSql()).contains("from mt_role t0 where exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)"); } } diff --git a/ebean-core/src/test/java/org/tests/query/other/TestQueryRawExpressionMany.java b/ebean-core/src/test/java/org/tests/query/other/TestQueryRawExpressionMany.java index af52302d2..55901a720 100644 --- a/ebean-core/src/test/java/org/tests/query/other/TestQueryRawExpressionMany.java +++ b/ebean-core/src/test/java/org/tests/query/other/TestQueryRawExpressionMany.java @@ -30,6 +30,6 @@ public class TestQueryRawExpressionMany extends BaseTestCase { query.findCount(); List sql = LoggedSqlCollector.stop(); - assertThat(trimSql(sql.get(0), 1)).contains("select count(*) from ( select distinct t0.id from o_order t0 left join o_order_detail t1 on t1.order_id = t0.id where t1.order_qty = ?)"); + assertThat(trimSql(sql.get(0), 1)).contains("select count(*) from ( select distinct t0.id from o_order t0 left join o_order_detail t1 on t1.order_id = t0.id and t1.id > 0 where t1.order_qty = ?)"); } } diff --git a/ebean-core/src/test/java/org/tests/query/other/TestQueryRowCountWithMany.java b/ebean-core/src/test/java/org/tests/query/other/TestQueryRowCountWithMany.java index a9d18476b..44c8a78f0 100644 --- a/ebean-core/src/test/java/org/tests/query/other/TestQueryRowCountWithMany.java +++ b/ebean-core/src/test/java/org/tests/query/other/TestQueryRowCountWithMany.java @@ -69,7 +69,7 @@ public class TestQueryRowCountWithMany extends BaseTestCase { assertEquals(list.size(), rowCount); assertEquals(2, sqlLogged.size()); assertThat(trimSql(sqlLogged.get(1), 1)).contains( - "select count(*) from ( select distinct t0.id from o_order t0 join o_order_detail u1 on u1.order_id = t0.id where u1.product_id = ?)"); + "select count(*) from ( select distinct t0.id from o_order t0 join o_order_detail u1 on u1.order_id = t0.id and u1.id > 0 where u1.product_id = ?)"); } @@ -95,7 +95,7 @@ public class TestQueryRowCountWithMany extends BaseTestCase { List sqlLogged = LoggedSqlCollector.stop(); assertEquals(1, sqlLogged.size()); - assertThat(trimSql(sqlLogged.get(0), 1)).contains("select count(*) from ( select distinct t0.id from o_order t0 join o_order_detail u1 on u1.order_id = t0.id where u1.product_id = ?)"); + assertThat(trimSql(sqlLogged.get(0), 1)).contains("select count(*) from ( select distinct t0.id from o_order t0 join o_order_detail u1 on u1.order_id = t0.id and u1.id > 0 where u1.product_id = ?)"); query.findList(); } diff --git a/ebean-core/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java b/ebean-core/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java index f476ed084..dd230b866 100644 --- a/ebean-core/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java +++ b/ebean-core/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java @@ -64,7 +64,7 @@ public class TestQuerySingleAttribute extends BaseTestCase { assertThat(sqlOf(query)).contains("select r1.attribute_, count(*) " + "from (select distinct t0.id, t0.name as attribute_ " + - "from o_customer t0 left join contact u1 on u1.customer_id = t0.id left join o_order u2 on u2.kcustomer_id = t0.id " + + "from o_customer t0 left join contact u1 on u1.customer_id = t0.id left join o_order u2 on u2.kcustomer_id = t0.id and u2.order_date is not null " + "where t0.name = ? and (u2.status = ? or u1.first_name = ?)) r1 " + "group by r1.attribute_ " + "order by count(*) desc, r1.attribute_"); diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java index 3a7457a56..6530611dd 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java @@ -149,7 +149,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor { @Override public void visitMany(BeanPropertyAssocMany p) { - if (p.hasJoinTable() && p.getMappedBy() == null) { + if (p.createJoinTable()) { // only create on other 'owning' side // build the create table and fkey constraints From 824cfa27be38a62c596e33f8363d0a1d876620a4 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Wed, 23 Jun 2021 17:11:21 +0200 Subject: [PATCH 61/72] Queries are cancelable --- .../main/java/io/ebean/CancelableQuery.java | 19 + .../src/main/java/io/ebean/DtoQuery.java | 2 +- ebean-api/src/main/java/io/ebean/Query.java | 11 +- .../src/main/java/io/ebean/SqlQuery.java | 2 +- .../main/java/io/ebean/util/JdbcClose.java | 13 + .../ebeaninternal/api/SpiCancelableQuery.java | 26 ++ .../java/io/ebeaninternal/api/SpiQuery.java | 13 +- .../io/ebeaninternal/api/SpiSqlBinding.java | 2 +- .../server/core/AbstractSqlQueryRequest.java | 56 ++- .../server/core/DefaultServer.java | 2 +- .../server/core/DtoQueryRequest.java | 2 + .../server/core/OrmQueryRequest.java | 2 +- .../server/core/RelationalQueryRequest.java | 1 + .../io/ebeaninternal/server/query/CQuery.java | 25 +- .../server/query/CQueryEngine.java | 12 +- .../query/CQueryFetchSingleAttribute.java | 48 ++- .../server/query/CQueryRowCount.java | 34 +- .../server/query/CQueryUpdate.java | 37 +- .../server/query/DtoQueryEngine.java | 7 +- .../server/querydefn/AbstractQuery.java | 56 +++ .../server/querydefn/DefaultDtoQuery.java | 2 +- .../server/querydefn/DefaultOrmQuery.java | 40 +- .../querydefn/DefaultRelationalQuery.java | 2 +- .../model/basic/MyEBasicConfigStartup.java | 6 +- .../org/tests/query/cancel/EBasicDto.java | 28 ++ .../tests/query/cancel/SlowDownEBasic.java | 57 +++ .../query/cancel/SqlQueryCancelTest.java | 362 ++++++++++++++++++ 27 files changed, 731 insertions(+), 136 deletions(-) create mode 100644 ebean-api/src/main/java/io/ebean/CancelableQuery.java create mode 100644 ebean-core/src/main/java/io/ebeaninternal/api/SpiCancelableQuery.java create mode 100644 ebean-core/src/main/java/io/ebeaninternal/server/querydefn/AbstractQuery.java create mode 100644 ebean-core/src/test/java/org/tests/query/cancel/EBasicDto.java create mode 100644 ebean-core/src/test/java/org/tests/query/cancel/SlowDownEBasic.java create mode 100644 ebean-core/src/test/java/org/tests/query/cancel/SqlQueryCancelTest.java diff --git a/ebean-api/src/main/java/io/ebean/CancelableQuery.java b/ebean-api/src/main/java/io/ebean/CancelableQuery.java new file mode 100644 index 000000000..fb3100bd2 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/CancelableQuery.java @@ -0,0 +1,19 @@ +package io.ebean; + +/** + * Defines a cancelable query. + *

+ * Typically holds a representation of the PreparedStatement to perform the + * actual cancel. + *

+ */ +public interface CancelableQuery { + + /** + * Cancel the query. + *

+ * For JDBC this translates to calling cancel on the PreparedStatement. + *

+ */ + void cancel(); +} diff --git a/ebean-api/src/main/java/io/ebean/DtoQuery.java b/ebean-api/src/main/java/io/ebean/DtoQuery.java index 95ad6eff5..69ed034ef 100644 --- a/ebean-api/src/main/java/io/ebean/DtoQuery.java +++ b/ebean-api/src/main/java/io/ebean/DtoQuery.java @@ -38,7 +38,7 @@ import java.util.stream.Stream; * * } */ -public interface DtoQuery { +public interface DtoQuery extends CancelableQuery { /** * Execute the query returning a list. diff --git a/ebean-api/src/main/java/io/ebean/Query.java b/ebean-api/src/main/java/io/ebean/Query.java index db7bf8cb9..0dc86905e 100644 --- a/ebean-api/src/main/java/io/ebean/Query.java +++ b/ebean-api/src/main/java/io/ebean/Query.java @@ -177,7 +177,7 @@ import java.util.stream.Stream; * * @param the type of Entity bean this query will fetch. */ -public interface Query { +public interface Query extends CancelableQuery { /** * The lock type (strength) to use with query FOR UPDATE row locking. @@ -291,15 +291,6 @@ public interface Query { */ UpdateQuery asUpdate(); - /** - * Cancel the query execution if supported by the underlying database and - * driver. - *

- * This must be called from a different thread to the query executor. - *

- */ - void cancel(); - /** * Return a copy of the query. *

diff --git a/ebean-api/src/main/java/io/ebean/SqlQuery.java b/ebean-api/src/main/java/io/ebean/SqlQuery.java index 46b26c90a..9a878f233 100644 --- a/ebean-api/src/main/java/io/ebean/SqlQuery.java +++ b/ebean-api/src/main/java/io/ebean/SqlQuery.java @@ -37,7 +37,7 @@ import java.util.function.Predicate; * * } */ -public interface SqlQuery extends Serializable { +public interface SqlQuery extends Serializable, CancelableQuery { /** * Execute the query returning a list. diff --git a/ebean-api/src/main/java/io/ebean/util/JdbcClose.java b/ebean-api/src/main/java/io/ebean/util/JdbcClose.java index 92b3f7e28..b72f59c4d 100644 --- a/ebean-api/src/main/java/io/ebean/util/JdbcClose.java +++ b/ebean-api/src/main/java/io/ebean/util/JdbcClose.java @@ -66,4 +66,17 @@ public class JdbcClose { logger.warn("Error on connection rollback", e); } } + + /** + * Cancels the statement + */ + public static void cancel(Statement stmt) { + try { + if (stmt != null) { + stmt.cancel(); + } + } catch (SQLException e) { + logger.warn("Error on cancelling statement", e); + } + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiCancelableQuery.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiCancelableQuery.java new file mode 100644 index 000000000..10a53b9a7 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiCancelableQuery.java @@ -0,0 +1,26 @@ +package io.ebeaninternal.api; + +import javax.persistence.PersistenceException; + +import io.ebean.CancelableQuery; + +/** + * Cancellable query, that has a delegate. + * + * @author Roland Praml, FOCONIS AG + * + */ +public interface SpiCancelableQuery extends CancelableQuery { + + /** + * Checks if the query was cancelled. + * @throws PersistenceException if query was cancelled. + */ + void checkCancelled(); + + /** + * Set the underlying cancelable query (with the PreparedStatement). + */ + void setCancelableQuery(CancelableQuery cancelableQuery); + +} 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 4686a3644..12942e7d6 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java @@ -17,7 +17,6 @@ import io.ebeaninternal.server.core.SpiOrmQueryRequest; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; import io.ebeaninternal.server.deploy.TableJoin; -import io.ebeaninternal.server.query.CancelableQuery; import io.ebeaninternal.server.querydefn.NaturalKeyBindParam; import io.ebeaninternal.server.querydefn.OrmQueryDetail; import io.ebeaninternal.server.querydefn.OrmQueryProperties; @@ -31,7 +30,7 @@ import java.util.Set; /** * Object Relational query - Internal extension to Query object. */ -public interface SpiQuery extends Query, SpiQueryFetch, TxnProfileEventCodes { +public interface SpiQuery extends Query, SpiQueryFetch, TxnProfileEventCodes, SpiCancelableQuery { enum Mode { NORMAL(false), LAZYLOAD_MANY(false), LAZYLOAD_BEAN(true), REFRESH_BEAN(true); @@ -847,16 +846,6 @@ public interface SpiQuery extends Query, SpiQueryFetch, TxnProfileEventCod */ ReadEvent getFutureFetchAudit(); - /** - * Set the underlying cancelable query (with the PreparedStatement). - */ - void setCancelableQuery(CancelableQuery cancelableQuery); - - /** - * Return true if this query has been cancelled. - */ - boolean isCancelled(); - /** * Return the base table to use if user defined on the query. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiSqlBinding.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiSqlBinding.java index 849ad0cb0..62b93d34c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiSqlBinding.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiSqlBinding.java @@ -3,7 +3,7 @@ package io.ebeaninternal.api; /** * SQL query binding (for SqlQuery and DtoQuery). */ -public interface SpiSqlBinding { +public interface SpiSqlBinding extends SpiCancelableQuery { /** * Return the named or positioned parameters. diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java index 8a5e9214d..67844c631 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.core; +import io.ebean.CancelableQuery; import io.ebean.Transaction; import io.ebean.util.JdbcClose; import io.ebeaninternal.api.*; @@ -12,11 +13,14 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.concurrent.locks.ReentrantLock; + +import javax.persistence.PersistenceException; /** * Wraps the objects involved in executing a SQL / Relational Query. */ -public abstract class AbstractSqlQueryRequest { +public abstract class AbstractSqlQueryRequest implements CancelableQuery { protected final SpiSqlBinding query; @@ -36,6 +40,8 @@ public abstract class AbstractSqlQueryRequest { protected long startNano; + private final ReentrantLock lock = new ReentrantLock(); + /** * Create the BeanFindRequest. */ @@ -43,6 +49,7 @@ public abstract class AbstractSqlQueryRequest { this.server = server; this.query = query; this.transaction = (SpiTransaction) t; + this.query.setCancelableQuery(this); } /** @@ -137,24 +144,30 @@ public abstract class AbstractSqlQueryRequest { } protected void executeAsSql(Binder binder) throws SQLException { - prepareSql(); - Connection conn = transaction.getInternalConnection(); - pstmt = conn.prepareStatement(sql); - if (query.getTimeout() > 0) { - pstmt.setQueryTimeout(query.getTimeout()); + lock.lock(); + try { + query.checkCancelled(); + prepareSql(); + Connection conn = transaction.getInternalConnection(); + pstmt = conn.prepareStatement(sql); + if (query.getTimeout() > 0) { + pstmt.setQueryTimeout(query.getTimeout()); + } + if (query.getBufferFetchSizeHint() > 0) { + pstmt.setFetchSize(query.getBufferFetchSizeHint()); + } + BindParams bindParams = query.getBindParams(); + if (!bindParams.isEmpty()) { + this.bindLog = binder.bind(bindParams, pstmt, conn); + } + if (isLogSql()) { + transaction.logSql(Str.add(TrimLogSql.trim(sql), "; --bind(", bindLog, ")")); + } + } finally { + lock.unlock(); } - if (query.getBufferFetchSizeHint() > 0) { - pstmt.setFetchSize(query.getBufferFetchSizeHint()); - } - BindParams bindParams = query.getBindParams(); - if (!bindParams.isEmpty()) { - this.bindLog = binder.bind(bindParams, pstmt, conn); - } - if (isLogSql()) { - transaction.logSql(Str.add(TrimLogSql.trim(sql), "; --bind(", bindLog, ")")); - } - setResultSet(pstmt.executeQuery(), null); + query.checkCancelled(); } /** @@ -164,4 +177,13 @@ public abstract class AbstractSqlQueryRequest { return sql; } + @Override + public void cancel() { + lock.lock(); + try { + JdbcClose.cancel(pstmt); + } finally { + lock.unlock(); + } + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 231f0b718..87580f90b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -1382,7 +1382,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Nonnull @Override public FutureList findFutureList(Query query, Transaction t) { - SpiQuery spiQuery = (SpiQuery) query; + SpiQuery spiQuery = (SpiQuery) query.copy(); spiQuery.setFutureFetch(true); // FutureList query always run in it's own persistence content spiQuery.setPersistenceContext(new DefaultPersistenceContext()); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java index f4cdbc236..2b1a55f6e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java @@ -53,6 +53,7 @@ public final class DtoQueryRequest extends AbstractSqlQueryRequest { ormQuery.setType(type); ormQuery.setManualId(); + query.setCancelableQuery(ormQuery); // execute the underlying ORM query returning the ResultSet SpiResultSet result = server.findResultSet(ormQuery, transaction); this.pstmt = result.getStatement(); @@ -117,6 +118,7 @@ public final class DtoQueryRequest extends AbstractSqlQueryRequest { } public boolean next() throws SQLException { + query.checkCancelled(); return dataReader.next(); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java index 5548c88e3..0fb83a18f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.core; import io.ebean.CacheMode; +import io.ebean.CancelableQuery; import io.ebean.OrderBy; import io.ebean.PersistenceContextScope; import io.ebean.QueryIterator; @@ -35,7 +36,6 @@ import io.ebeaninternal.server.deploy.DeployPropertyParserMap; import io.ebeaninternal.server.el.ElPropertyValue; import io.ebeaninternal.server.loadcontext.DLoadContext; import io.ebeaninternal.server.query.CQueryPlan; -import io.ebeaninternal.server.query.CancelableQuery; import io.ebeaninternal.server.transaction.DefaultPersistenceContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java index 9a0ceb153..a5130ef22 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java @@ -139,6 +139,7 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest { @Override public boolean next() throws SQLException { + query.checkCancelled(); if (!resultSet.next()) { return false; } else { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java index 0fb7e9dcf..e7674a31c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.query; +import io.ebean.CancelableQuery; import io.ebean.QueryIterator; import io.ebean.Version; import io.ebean.bean.*; @@ -139,8 +140,6 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran */ private PreparedStatement pstmt; - private boolean cancelled; - private String bindLog; private final CQueryPlan queryPlan; @@ -272,15 +271,7 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran public void cancel() { lock.lock(); try { - this.cancelled = true; - if (pstmt != null) { - try { - logger.debug("Cancelling query"); - pstmt.cancel(); - } catch (SQLException e) { - throw new PersistenceException("Error cancelling query", e); - } - } + JdbcClose.cancel(pstmt); } finally { lock.unlock(); } @@ -312,9 +303,8 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran ResultSet prepareResultSet(boolean forwardOnlyHint) throws SQLException { lock.lock(); try { - if (cancelled) { - throw new SQLException("Query cancelled"); - } + // cancelled before we started + query.checkCancelled(); startNano = System.nanoTime(); SpiTransaction t = request.getTransaction(); profileOffset = t.profileOffset(); @@ -342,10 +332,12 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran pstmt.setFetchSize(query.getBufferFetchSizeHint()); } bindLog = predicates.bind(queryPlan.bindEncryptedProperties(pstmt, conn)); - return pstmt.executeQuery(); } finally { lock.unlock(); } + ResultSet ret = pstmt.executeQuery(); + query.checkCancelled(); + return ret; } /** @@ -491,7 +483,8 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran boolean hasNext() throws SQLException { lock.lock(); try { - if (noMoreRows || cancelled) { + query.checkCancelled(); + if (noMoreRows) { return false; } if (hasNextCache) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java index c632c9aeb..a1a211c0a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java @@ -66,11 +66,13 @@ public class CQueryEngine { public int delete(OrmQueryRequest request) { CQueryUpdate query = queryBuilder.buildUpdateQuery(true, request); + request.setCancelableQuery(query); return executeUpdate(request, query); } public int update(OrmQueryRequest request) { CQueryUpdate query = queryBuilder.buildUpdateQuery(false, request); + request.setCancelableQuery(query); return executeUpdate(request, query); } @@ -97,6 +99,7 @@ public class CQueryEngine { public List findSingleAttributeList(OrmQueryRequest request) { CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchAttributeQuery(request); + request.setCancelableQuery(rcQuery); return findAttributeList(request, rcQuery); } @@ -151,6 +154,7 @@ public class CQueryEngine { public List findIds(OrmQueryRequest request) { CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchIdsQuery(request); + request.setCancelableQuery(rcQuery); return findAttributeList(request, rcQuery); } @@ -164,6 +168,7 @@ public class CQueryEngine { public int findCount(OrmQueryRequest request) { CQueryRowCount rcQuery = queryBuilder.buildRowCountQuery(request); + request.setCancelableQuery(rcQuery); try { int count = rcQuery.findCount(); @@ -235,8 +240,10 @@ public class CQueryEngine { } catch (SQLException e) { try { + PersistenceException pex = cquery.createPersistenceException(e); + // create exception before closing connection cquery.close(); - throw cquery.createPersistenceException(e); + throw pex; } finally { request.rollbackTransIfRequired(); } @@ -259,6 +266,7 @@ public class CQueryEngine { // order by lower sys period desc query.order().desc(sysPeriodLower); CQuery cquery = queryBuilder.buildQuery(request); + request.setCancelableQuery(cquery); try { cquery.prepareBindExecuteQuery(); if (request.isLogSql()) { @@ -327,6 +335,7 @@ public class CQueryEngine { */ public SpiResultSet findResultSet(OrmQueryRequest request) { CQuery cquery = queryBuilder.buildQuery(request); + request.setCancelableQuery(cquery); try { boolean fwdOnly; if (request.isFindIterate()) { @@ -411,6 +420,7 @@ public class CQueryEngine { EntityBean bean = null; CQuery cquery = queryBuilder.buildQuery(request); + request.setCancelableQuery(cquery); try { cquery.prepareBindExecuteQuery(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java index 9fd7278cc..b68813bd4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.query; +import io.ebean.CancelableQuery; import io.ebean.CountedValue; import io.ebean.core.type.ScalarDataReader; import io.ebean.util.JdbcClose; @@ -18,11 +19,12 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; /** * Base compiled query request for single attribute queries. */ -class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent { +class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent, CancelableQuery { private static final Logger logger = LoggerFactory.getLogger(CQueryFetchSingleAttribute.class); @@ -65,6 +67,8 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent { private final boolean containsCounts; private long profileOffset; + + private final ReentrantLock lock = new ReentrantLock(); /** * Create the Sql select based on the request. @@ -147,21 +151,27 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent { } private void prepareExecute() throws SQLException { - - SpiTransaction t = getTransaction(); - profileOffset = t.profileOffset(); - Connection conn = t.getInternalConnection(); - pstmt = conn.prepareStatement(sql); - - if (query.getBufferFetchSizeHint() > 0) { - pstmt.setFetchSize(query.getBufferFetchSizeHint()); + lock.lock(); + try { + query.checkCancelled(); + SpiTransaction t = getTransaction(); + profileOffset = t.profileOffset(); + Connection conn = t.getInternalConnection(); + pstmt = conn.prepareStatement(sql); + + if (query.getBufferFetchSizeHint() > 0) { + pstmt.setFetchSize(query.getBufferFetchSizeHint()); + } + if (query.getTimeout() > 0) { + pstmt.setQueryTimeout(query.getTimeout()); + } + + bindLog = predicates.bind(pstmt, conn); + } finally { + lock.unlock(); } - if (query.getTimeout() > 0) { - pstmt.setQueryTimeout(query.getTimeout()); - } - - bindLog = predicates.bind(pstmt, conn); dataReader = new RsetDataReader(request.getDataTimeZone(), pstmt.executeQuery()); + query.checkCancelled(); } /** @@ -194,4 +204,14 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent { Set getDependentTables() { return queryPlan.getDependentTables(); } + + @Override + public void cancel() { + lock.lock(); + try { + JdbcClose.cancel(pstmt); + } finally { + lock.unlock(); + } + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryRowCount.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryRowCount.java index 6a3d40a59..248fa9058 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryRowCount.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryRowCount.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.query; +import io.ebean.CancelableQuery; import io.ebean.util.JdbcClose; import io.ebeaninternal.api.SpiProfileTransactionEvent; import io.ebeaninternal.api.SpiQuery; @@ -13,11 +14,12 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; /** * Executes the select row count query. */ -class CQueryRowCount implements SpiProfileTransactionEvent { +class CQueryRowCount implements SpiProfileTransactionEvent, CancelableQuery { private final CQueryPlan queryPlan; @@ -57,6 +59,8 @@ class CQueryRowCount implements SpiProfileTransactionEvent { private int rowCount; private long profileOffset; + + private final ReentrantLock lock = new ReentrantLock(); /** * Create the Sql select based on the request. @@ -110,14 +114,22 @@ class CQueryRowCount implements SpiProfileTransactionEvent { SpiTransaction t = getTransaction(); profileOffset = t.profileOffset(); Connection conn = t.getInternalConnection(); - pstmt = conn.prepareStatement(sql); + lock.lock(); + try { + query.checkCancelled(); + pstmt = conn.prepareStatement(sql); - if (query.getTimeout() > 0) { - pstmt.setQueryTimeout(query.getTimeout()); + if (query.getTimeout() > 0) { + pstmt.setQueryTimeout(query.getTimeout()); + } + + bindLog = predicates.bind(pstmt, conn); + } finally { + lock.unlock(); } - - bindLog = predicates.bind(pstmt, conn); rset = pstmt.executeQuery(); + query.checkCancelled(); + if (!rset.next()) { throw new PersistenceException("Expecting 1 row but got none?"); } @@ -161,4 +173,14 @@ class CQueryRowCount implements SpiProfileTransactionEvent { Set getDependentTables() { return queryPlan.getDependentTables(); } + + @Override + public void cancel() { + lock.lock(); + try { + JdbcClose.cancel(pstmt); + } finally { + lock.unlock(); + } + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryUpdate.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryUpdate.java index 76858c271..eec5d2e6f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryUpdate.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryUpdate.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.query; +import io.ebean.CancelableQuery; import io.ebean.util.JdbcClose; import io.ebeaninternal.api.SpiProfileTransactionEvent; import io.ebeaninternal.api.SpiQuery; @@ -10,11 +11,12 @@ import io.ebeaninternal.server.deploy.BeanDescriptor; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.concurrent.locks.ReentrantLock; /** - * Executes the delete query. + * Executes the update query. */ -class CQueryUpdate implements SpiProfileTransactionEvent { +class CQueryUpdate implements SpiProfileTransactionEvent, CancelableQuery { private final CQueryPlan queryPlan; @@ -45,6 +47,8 @@ class CQueryUpdate implements SpiProfileTransactionEvent { private long profileOffset; + private final ReentrantLock lock = new ReentrantLock(); + /** * Create the Sql select based on the request. */ @@ -82,15 +86,22 @@ class CQueryUpdate implements SpiProfileTransactionEvent { SpiTransaction t = getTransaction(); profileOffset = t.profileOffset(); Connection conn = t.getInternalConnection(); - pstmt = conn.prepareStatement(sql); + lock.lock(); + try { + query.checkCancelled(); + pstmt = conn.prepareStatement(sql); - if (query.getTimeout() > 0) { - pstmt.setQueryTimeout(query.getTimeout()); + if (query.getTimeout() > 0) { + pstmt.setQueryTimeout(query.getTimeout()); + } + + bindLog = predicates.bind(pstmt, conn); + } finally { + lock.unlock(); } - - bindLog = predicates.bind(pstmt, conn); rowCount = pstmt.executeUpdate(); - + query.checkCancelled(); + long executionTimeMicros = (System.nanoTime() - startNano) / 1000L; request.slowQueryCheck(executionTimeMicros, rowCount); if (queryPlan.executionTime(executionTimeMicros)) { @@ -122,4 +133,14 @@ class CQueryUpdate implements SpiProfileTransactionEvent { .profileStream() .addQueryEvent(query.profileEventId(), profileOffset, desc.getName(), rowCount, query.getProfileId()); } + + @Override + public void cancel() { + lock.lock(); + try { + JdbcClose.cancel(pstmt); + } finally { + lock.unlock(); + } + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryEngine.java index 46de28b8b..526d36dee 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryEngine.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryEngine.java @@ -29,7 +29,7 @@ public class DtoQueryEngine { } return rows; - } catch (Throwable e) { + } catch (SQLException e) { throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e); } finally { request.close(); @@ -51,7 +51,7 @@ public class DtoQueryEngine { while (request.next()) { consumer.accept(request.readNextBean()); } - } catch (Exception e) { + } catch (SQLException e) { throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e); } finally { request.close(); @@ -88,9 +88,8 @@ public class DtoQueryEngine { break; } } - } catch (Exception e) { + } catch (SQLException e) { throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e); - } finally { request.close(); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/AbstractQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/AbstractQuery.java new file mode 100644 index 000000000..62fd3cdf0 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/AbstractQuery.java @@ -0,0 +1,56 @@ +package io.ebeaninternal.server.querydefn; + +import java.util.concurrent.locks.ReentrantLock; + +import javax.persistence.PersistenceException; + +import io.ebean.CancelableQuery; +import io.ebeaninternal.api.SpiCancelableQuery; + +/** + * Common code for Dto/Orm/RelationalQuery + * + * @author Roland Praml, FOCONIS AG + * + */ +public class AbstractQuery implements SpiCancelableQuery { + + private boolean cancelled; + + private CancelableQuery cancelableQuery; + + private final ReentrantLock lock = new ReentrantLock(); + + @Override + public void cancel() { + lock.lock(); + try { + if (!cancelled) { + cancelled = true; + if (cancelableQuery != null) { + cancelableQuery.cancel(); + } + } + } finally { + lock.unlock(); + } + } + + @Override + public void checkCancelled() { + if (cancelled) { + throw new PersistenceException("Query was cancelled"); + } + } + + @Override + public void setCancelableQuery(CancelableQuery cancelableQuery) { + lock.lock(); + try { + checkCancelled(); + this.cancelableQuery = cancelableQuery; + } finally { + lock.unlock(); + } + } +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultDtoQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultDtoQuery.java index 9afcb961f..c2cb02198 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultDtoQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultDtoQuery.java @@ -21,7 +21,7 @@ import java.util.stream.Stream; /** * Default implementation of DtoQuery. */ -public class DefaultDtoQuery implements SpiDtoQuery { +public class DefaultDtoQuery extends AbstractQuery implements SpiDtoQuery { private final SpiEbeanServer server; 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 71fe9ff68..0eb5e619c 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 @@ -58,7 +58,6 @@ import io.ebeaninternal.server.deploy.TableJoin; import io.ebeaninternal.server.expression.DefaultExpressionList; import io.ebeaninternal.server.expression.IdInExpression; import io.ebeaninternal.server.expression.SimpleExpression; -import io.ebeaninternal.server.query.CancelableQuery; import io.ebeaninternal.server.query.NativeSqlQueryPlanKey; import io.ebeaninternal.server.rawsql.SpiRawSql; import io.ebeaninternal.server.transaction.ExternalJdbcTransaction; @@ -81,7 +80,7 @@ import java.util.stream.Stream; /** * Default implementation of an Object Relational query. */ -public class DefaultOrmQuery implements SpiQuery { +public class DefaultOrmQuery extends AbstractQuery implements SpiQuery { private static final String DEFAULT_QUERY_NAME = "default"; @@ -113,10 +112,6 @@ public class DefaultOrmQuery implements SpiQuery { private ProfilingListener profilingListener; - private boolean cancelled; - - private CancelableQuery cancelableQuery; - private Type type; private String label; @@ -856,6 +851,7 @@ public class DefaultOrmQuery implements SpiQuery { copy.parentNode = parentNode; copy.forUpdate = forUpdate; copy.rawSql = rawSql; + setCancelableQuery(copy); // required to cancel findId query return copy; } @@ -2046,16 +2042,6 @@ public class DefaultOrmQuery implements SpiQuery { return futureFetchAudit; } - @Override - public void setCancelableQuery(CancelableQuery cancelableQuery) { - lock.lock(); - try { - this.cancelableQuery = cancelableQuery; - } finally { - lock.unlock(); - } - } - @Override public Query setBaseTable(String baseTable) { this.baseTable = baseTable; @@ -2083,28 +2069,6 @@ public class DefaultOrmQuery implements SpiQuery { return rootTableAlias != null ? rootTableAlias : defaultAlias; } - @Override - public void cancel() { - lock.lock(); - try { - if (!cancelled && cancelableQuery != null) { - cancelled = true; - cancelableQuery.cancel(); - } - } finally { - lock.unlock(); - } - } - - @Override - public boolean isCancelled() { - lock.lock(); - try { - return cancelled; - } finally { - lock.unlock(); - } - } @Override public Set validate() { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java index 182b1cfd4..6afe54a1f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java @@ -17,7 +17,7 @@ import java.util.function.Predicate; /** * Default implementation of SQuery - SQL Query. */ -public class DefaultRelationalQuery implements SpiSqlQuery { +public class DefaultRelationalQuery extends AbstractQuery implements SpiSqlQuery { private static final long serialVersionUID = -1098305779779591068L; diff --git a/ebean-core/src/test/java/org/tests/model/basic/MyEBasicConfigStartup.java b/ebean-core/src/test/java/org/tests/model/basic/MyEBasicConfigStartup.java index 72cb3e689..c27c5e79b 100644 --- a/ebean-core/src/test/java/org/tests/model/basic/MyEBasicConfigStartup.java +++ b/ebean-core/src/test/java/org/tests/model/basic/MyEBasicConfigStartup.java @@ -59,19 +59,19 @@ public class MyEBasicConfigStartup implements ServerConfigStartup { @Override public void inserted(Object bean) { insertCount.incrementAndGet(); - System.out.println("-- EBasic inserted " + ((EBasic) bean).getId()); + // System.out.println("-- EBasic inserted " + ((EBasic) bean).getId()); } @Override public void updated(Object bean, Set updatedProperties) { updateCount.incrementAndGet(); - System.out.println("-- EBasic updated " + ((EBasic) bean).getId() + " updatedProperties: " + updatedProperties); + // System.out.println("-- EBasic updated " + ((EBasic) bean).getId() + " updatedProperties: " + updatedProperties); } @Override public void deleted(Object bean) { deleteCount.incrementAndGet(); - System.out.println("-- EBasic deleted " + ((EBasic) bean).getId()); + // System.out.println("-- EBasic deleted " + ((EBasic) bean).getId()); } } diff --git a/ebean-core/src/test/java/org/tests/query/cancel/EBasicDto.java b/ebean-core/src/test/java/org/tests/query/cancel/EBasicDto.java new file mode 100644 index 000000000..49b6b157c --- /dev/null +++ b/ebean-core/src/test/java/org/tests/query/cancel/EBasicDto.java @@ -0,0 +1,28 @@ +package org.tests.query.cancel; + +import org.tests.model.basic.EBasic.Status; + +/** + * DTO for Ebasic Queries. + */ +public class EBasicDto { + private Integer id; + + private Status status; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } +} \ No newline at end of file diff --git a/ebean-core/src/test/java/org/tests/query/cancel/SlowDownEBasic.java b/ebean-core/src/test/java/org/tests/query/cancel/SlowDownEBasic.java new file mode 100644 index 000000000..68d6c038b --- /dev/null +++ b/ebean-core/src/test/java/org/tests/query/cancel/SlowDownEBasic.java @@ -0,0 +1,57 @@ +package org.tests.query.cancel; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.api.Trigger; + +import io.ebean.DB; +import io.ebean.Transaction; + +/** + * Class to artificially slow down selects on 'e_basic' table + */ +public class SlowDownEBasic implements Trigger { + + private static int wait; + + private static boolean triggerInstalled; + + @Override + public void init(final Connection conn, final String schemaName, final String triggerName, final String tableName, + final boolean before, final int type) { + } + + @Override + public void fire(final Connection conn, final Object[] oldRow, final Object[] newRow) { + try { + Thread.sleep(wait); + } catch (InterruptedException e) { + // nop + } + } + + @Override + public void close() { + } + + @Override + public void remove() { + } + + + public static void setSelectWaitMillis(final int wait) throws SQLException { + SlowDownEBasic.wait = wait; + if (triggerInstalled) { + return; + } + triggerInstalled = true; + try (Transaction txn = DB.beginTransaction(); Statement stmt = txn.getConnection().createStatement()) { + + stmt.execute("CREATE TRIGGER SLOW_DOWN_E_BASIC BEFORE SELECT ON e_basic " + "CALL \"" + + SlowDownEBasic.class.getName() + "\""); + txn.commit(); + } + } +} diff --git a/ebean-core/src/test/java/org/tests/query/cancel/SqlQueryCancelTest.java b/ebean-core/src/test/java/org/tests/query/cancel/SqlQueryCancelTest.java new file mode 100644 index 000000000..d3dda7443 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/query/cancel/SqlQueryCancelTest.java @@ -0,0 +1,362 @@ +package org.tests.query.cancel; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.sql.SQLException; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import java.util.function.Function; + +import javax.persistence.PersistenceException; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.tests.model.basic.EBasic; + +import io.ebean.BaseTestCase; +import io.ebean.DB; +import io.ebean.DtoQuery; +import io.ebean.Query; +import io.ebean.QueryIterator; +import io.ebean.SqlQuery; +import io.ebean.annotation.ForPlatform; +import io.ebean.annotation.Platform; + +/** + * Tests, if all kind of queries are cancelable. There are two ways how to + * cancel a query:
+ * At begin: + * + *

+ * query = DB.find(...)
+ * query.cancel();
+ * query.findList();
+ * 
+ * + * The query was caneled before executing. In this case we do hit the DB driver + *
+ *
+ * During run: + * + *
+ * // Thread 1:              Thread 2
+ * query = DB.find(...)
+ * query.findList();
+ *    ...finding
+ *     ...finding            query.cancel();
+ *      ...JDBC-Exception
+ * 
+ * + * The test tries to simulate a slow query by installing the + * {@link SlowDownEBasic} 'SELECT' trigger. The trigger can be configured to + * wait 3 * timing ms and a second thread will cancel the query in + * timing ms. + * + * in this case, we expect a JDBC exception from the driver.
+ *
+ * NOTE:
+ * H2 checks the cancel flag in org.h2.command.Prepared::setCurrentRowNumber + * only every 128th row. So we need at least 128 models and we cannot check + * queries like findCount or findOne, because they only return one row. + * + * @author Roland Praml, FOCONIS AG + * + */ +public class SqlQueryCancelTest extends BaseTestCase { + + private int timing = 10; + + @BeforeClass + public static void setupTestData() throws SQLException { + for (int i = 0; i < 128; i++) { + EBasic model = new EBasic("Basic " + i); + DB.save(model); + } + SlowDownEBasic.setSelectWaitMillis(0); + } + + @Test + public void cancelSqlQueryAtBegin() throws SQLException { + doCancelSqlAtBegin(SqlQuery::findList); + doCancelSqlAtBegin(SqlQuery::findOne); + doCancelSqlAtBegin(q -> q.findEach(e -> {})); + doCancelSqlAtBegin(q -> q.findEachWhile(e -> true)); + } + + @ForPlatform(Platform.H2) + @Test + public void cancelSqlDuringRun() throws SQLException { + + doCancelSqlDuringRun(SqlQuery::findList); + // doCancelSqlDuringRun(q -> q.setMaxRows(1).findOne()); + // findOne cannot be tested, as H2 does the cancel check every 128 rows only + doCancelSqlDuringRun(q -> q.findEach(e -> {})); + doCancelSqlDuringRun(q -> q.findEachWhile(e -> true)); + } + + + @Test + public void cancelOrmQueryAtBegin() throws SQLException { + doCancelOrmAtBegin(Query::findCount); + doCancelOrmAtBegin(Query::findFutureCount); + // We cannot test 'findCount' due H2 restrictions + doCancelOrmAtBegin(Query::findFutureIds); + doCancelOrmAtBegin(Query::findFutureList); + doCancelOrmAtBegin(Query::findIds); + doCancelOrmAtBegin(Query::findIterate); + doCancelOrmAtBegin(Query::findList); + doCancelOrmAtBegin(Query::findMap); + doCancelOrmAtBegin(Query::findOne); + doCancelOrmAtBegin(q -> q.setMaxRows(1000).findPagedList().getList()); // untested + doCancelOrmAtBegin(Query::findSet); + doCancelOrmAtBegin(Query::findSingleAttribute); + doCancelOrmAtBegin(Query::findSingleAttributeList); + doCancelOrmAtBegin(Query::findStream); + // testDuringRun(Query::findVersions); + // EBasic has no history support, but it should work if @History is added + doCancelOrmAtBegin(q -> q.findEach(e -> {})); + doCancelOrmAtBegin(q -> q.findEachWhile(e -> true)); + } + + @ForPlatform(Platform.H2) + @Test + public void cancelOrmDuringRun() throws Throwable { + // doCancelOrmDuringRun(Query::findCount); + // testDuringRunFuture(Query::findFutureCount); + // We cannot test 'findCount' due H2 restrictions + doCancelOrmFutureDuringRun(Query::findFutureIds); + doCancelOrmFutureDuringRun(Query::findFutureList); + doCancelOrmDuringRun(Query::findIds); + doCancelOrmDuringRun(Query::findIterate); + doCancelOrmDuringRun(Query::findList); + doCancelOrmDuringRun(Query::findMap); + // doCancelOrmDuringRun(q -> q.setMaxRows(1).findOne()); + // 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(Query::findStream); + // testDuringRun(Query::findVersions); + // EBasic has no history support, but it should work if @History is added + doCancelOrmDuringRun(q -> q.findEach(e -> {})); + doCancelOrmDuringRun(q -> q.findEachWhile(e -> true)); + } + + @Test + public void cancelOrmDuringIterate() throws SQLException { + + Query query = DB.find(EBasic.class); + + QueryIterator iter = query.findIterate(); + assertThat(iter.hasNext()).isTrue(); + query.cancel(); + assertThat(iter.next()).isNotNull(); + + // We might have 100 entities in a buffer. So we must iterate through all. + assertThatThrownBy(() -> { + while(iter.hasNext()) iter.next(); + }) + .isInstanceOf(PersistenceException.class) + .hasMessageContaining("Query was cancelled"); + } + + @Test + public void cancelOrmDtoQueryAtBegin() throws SQLException { + + doCancelOrmDtoAtBegin(DtoQuery::findIterate); + doCancelOrmDtoAtBegin(DtoQuery::findList); + doCancelOrmDtoAtBegin(DtoQuery::findOne); + doCancelOrmDtoAtBegin(DtoQuery::findStream); + doCancelOrmDtoAtBegin(q -> q.findEach(e -> {})); + doCancelOrmDtoAtBegin(q -> q.findEachWhile(e -> true)); + } + + @ForPlatform(Platform.H2) + @Test + public void cancelOrmDtoDuringRun() throws SQLException { + + doCancelOrmDtoDuringRun(DtoQuery::findIterate); + doCancelOrmDtoDuringRun(DtoQuery::findList); + // doCancelOrmDtoDuringRun(q -> q.setMaxRows(1).findOne()); + // findOne cannot be tested, as H2 does the cancel check every 128 rows only + doCancelOrmDtoDuringRun(DtoQuery::findStream); + doCancelOrmDtoDuringRun(q -> q.findEach(e -> {})); + doCancelOrmDtoDuringRun(q -> q.findEachWhile(e -> true)); + } + + @Test + public void cancelOrmDtoDuringIterate() throws SQLException { + + DtoQuery query = DB.find(EBasic.class).select("id,status").asDto(EBasicDto.class); + + QueryIterator iter = query.findIterate(); + assertThat(iter.hasNext()).isTrue(); + query.cancel(); + assertThat(iter.next()).isNotNull(); + + // We might have 100 entities in a buffer. So we must iterate through all. + assertThatThrownBy(() -> { + while(iter.hasNext()) iter.next(); + }) + .isInstanceOf(PersistenceException.class) + .hasMessageContaining("Query was cancelled"); + } + + @Test + public void cancelSqlDtoQueryAtBegin() throws SQLException { + + doCancelSqlDtoAtBegin(DtoQuery::findIterate); + doCancelSqlDtoAtBegin(DtoQuery::findList); + doCancelSqlDtoAtBegin(DtoQuery::findOne); + doCancelSqlDtoAtBegin(DtoQuery::findStream); + doCancelSqlDtoAtBegin(q -> q.findEach(e -> {})); + doCancelSqlDtoAtBegin(q -> q.findEachWhile(e -> true)); + } + + @ForPlatform(Platform.H2) + @Test + public void cancelSqlDtoDuringRun() throws SQLException { + + //doCancelSqlDtoDuringRun(DtoQuery::findIterate); + doCancelSqlDtoDuringRun(DtoQuery::findList); + // doCancelSqlDtoDuringRun(q -> q.setMaxRows(1).findOne()); + // findOne cannot be tested, as H2 does the cancel check every 128 rows only + doCancelSqlDtoDuringRun(DtoQuery::findStream); + doCancelSqlDtoDuringRun(q -> q.findEach(e -> {})); + doCancelSqlDtoDuringRun(q -> q.findEachWhile(e -> true)); + } + + @Test + public void cancelSqlDtoDuringIterate() throws SQLException { + + DtoQuery query = DB.findDto(EBasicDto.class, "select id, status from e_basic"); + + QueryIterator iter = query.findIterate(); + assertThat(iter.hasNext()).isTrue(); + query.cancel(); + assertThat(iter.next()).isNotNull(); + + // We might have 100 entities in a buffer. So we must iterate through all. + assertThatThrownBy(() -> { + while(iter.hasNext()) iter.next(); + }) + .isInstanceOf(PersistenceException.class) + .hasMessageContaining("Query was cancelled"); + } + + private void doCancelSqlAtBegin(Consumer test) throws SQLException { + SqlQuery query = DB.sqlQuery("select * from e_basic"); + query.cancel(); + assertThatThrownBy(() -> test.accept(query)) + .isInstanceOf(PersistenceException.class) + .hasMessageContaining("Query was cancelled"); + } + + private void doCancelSqlDuringRun(Consumer test) throws SQLException { + SqlQuery warmup = DB.sqlQuery("select * from e_basic"); + test.accept(warmup); + SqlQuery query = DB.sqlQuery("select * from e_basic"); + executeDelayed(query::cancel); + assertThatThrownBy(() -> test.accept(query)) + .isInstanceOf(PersistenceException.class) + .hasCauseInstanceOf(org.h2.jdbc.JdbcSQLTimeoutException.class); + } + + private void doCancelOrmAtBegin(Consumer> test) throws SQLException { + Query query = DB.find(EBasic.class); + query.cancel(); + assertThatThrownBy(() -> test.accept(query)) + .isInstanceOf(PersistenceException.class) + .hasMessageContaining("Query was cancelled"); + } + + private void doCancelOrmDuringRun(Consumer> test) throws SQLException { + Query warmup = DB.find(EBasic.class); + test.accept(warmup); + Query warmup2 = DB.find(EBasic.class); + test.accept(warmup2); + Query query = DB.find(EBasic.class); + executeDelayed(query::cancel); + assertThatThrownBy(() -> test.accept(query)) + .isInstanceOf(PersistenceException.class) + .hasCauseInstanceOf(org.h2.jdbc.JdbcSQLTimeoutException.class); + } + + private void doCancelOrmFutureDuringRun(Function, Future> test) throws SQLException, InterruptedException, ExecutionException { + Query warmup = DB.find(EBasic.class); + test.apply(warmup).get(); + + Query query = DB.find(EBasic.class); + executeDelayed(query::cancel); + assertThatThrownBy(() -> { + try { + test.apply(query).get(); + } catch (ExecutionException ee) { + throw ee.getCause(); + } + }) + .isInstanceOf(PersistenceException.class) + .hasCauseInstanceOf(org.h2.jdbc.JdbcSQLTimeoutException.class); + } + + private void doCancelOrmDtoAtBegin(Consumer> test) throws SQLException { + DtoQuery query = DB.find(EBasic.class).select("id,status").asDto(EBasicDto.class); + query.cancel(); + assertThatThrownBy(() -> test.accept(query)) + .isInstanceOf(PersistenceException.class) + .hasMessageContaining("Query was cancelled"); + } + + private void doCancelOrmDtoDuringRun(Consumer> test) throws SQLException { + DtoQuery warmup = DB.find(EBasic.class).select("id,status").asDto(EBasicDto.class); + test.accept(warmup); + + DtoQuery query = DB.find(EBasic.class).select("id,status").asDto(EBasicDto.class); + executeDelayed(query::cancel); + assertThatThrownBy(() -> test.accept(query)) + .isInstanceOf(PersistenceException.class) + .hasCauseInstanceOf(org.h2.jdbc.JdbcSQLTimeoutException.class); + } + + private void doCancelSqlDtoAtBegin(Consumer> test) throws SQLException { + DtoQuery query = DB.findDto(EBasicDto.class, "select id, status from e_basic"); + query.cancel(); + assertThatThrownBy(() -> test.accept(query)) + .isInstanceOf(PersistenceException.class) + .hasMessageContaining("Query was cancelled"); + } + + private void doCancelSqlDtoDuringRun(Consumer> test) throws SQLException { + DtoQuery warmup = DB.findDto(EBasicDto.class, "select id, status from e_basic"); + test.accept(warmup); + + DtoQuery query = DB.findDto(EBasicDto.class, "select id, status from e_basic"); + executeDelayed(query::cancel); + assertThatThrownBy(() -> test.accept(query)) + .isInstanceOf(PersistenceException.class) + .hasCauseInstanceOf(org.h2.jdbc.JdbcSQLTimeoutException.class); + } + + private void executeDelayed(Runnable r) throws SQLException { + // We modify the DB here. Otherwise we may hit an internal H2 cache, if the + // same query is performed. Queries from the cache cannot be canceled. + EBasic makeDbDirty = new EBasic("Basic " + UUID.randomUUID()); + DB.save(makeDbDirty); + SlowDownEBasic.setSelectWaitMillis(timing * 3); + new Thread(() -> { + try { + Thread.sleep(timing); + r.run(); + SlowDownEBasic.setSelectWaitMillis(0); + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + } + +} From d3137be9845ee6ff7f76d1e4d29cd6483834b7ab Mon Sep 17 00:00:00 2001 From: Cas de Graaf Date: Wed, 30 Jun 2021 16:23:20 +0200 Subject: [PATCH 62/72] in addition to #2236 - autotune still throws RunTimeException due to existing ORMQueryDetail.toString() calls, replaced with .asString() --- .../server/autotune/service/AutoTuneDiffCollection.java | 4 ++-- .../ebeaninternal/server/autotune/service/TunedQueryInfo.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/AutoTuneDiffCollection.java b/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/AutoTuneDiffCollection.java index 9eb5439f2..e9ebe5af1 100644 --- a/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/AutoTuneDiffCollection.java +++ b/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/AutoTuneDiffCollection.java @@ -113,7 +113,7 @@ public class AutoTuneDiffCollection { diffCount++; - Origin origin = createOrigin(entry, point, tuneDetail.toString()); + Origin origin = createOrigin(entry, point, tuneDetail.asString()); ProfileDiff diff = document.getProfileDiff(); if (diff == null) { diff = new ProfileDiff(); @@ -147,7 +147,7 @@ public class AutoTuneDiffCollection { Origin origin = new Origin(); origin.setKey(point.getKey()); origin.setBeanType(point.getBeanType()); - origin.setDetail(entry.getDetail().toString()); + origin.setDetail(entry.getDetail().asString()); origin.setCallStack(point.getCallOrigin().getFullDescription()); origin.setOriginal(query); diff --git a/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/TunedQueryInfo.java b/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/TunedQueryInfo.java index 374a8d722..43e66c5af 100644 --- a/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/TunedQueryInfo.java +++ b/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/TunedQueryInfo.java @@ -65,7 +65,7 @@ public class TunedQueryInfo implements Serializable { @Override public String toString() { - return tunedDetail.toString(); + return tunedDetail.asString(); } } From e82b8c80f066cbcc2d6bd2f05c5e066ff6cb55ef Mon Sep 17 00:00:00 2001 From: rbygrave Date: Tue, 13 Jul 2021 11:30:31 +1200 Subject: [PATCH 63/72] #2265 - ArrayIndexOutOfBoundsException: Index -1 out of bounds with @IdClass and batch insert accessing id property --- .../server/core/PersistRequestBean.java | 8 ++++--- .../server/deploy/BeanDescriptor.java | 10 +++++++- .../tests/model/bridge/TestIdClassScalar.java | 24 +++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java index ce6a39878..8b3285c7a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java @@ -379,9 +379,11 @@ public final class PersistRequestBean extends PersistRequest implements BeanP public void setBatched() { batched = true; if (type == Type.INSERT || type == Type.UPDATE) { - // used to trigger automatic jdbc batch flush - intercept.registerGetterCallback(this); - getterCallback = true; + if (beanDescriptor.hasSingleIdProperty()) { + // used to trigger automatic jdbc batch flush + intercept.registerGetterCallback(this); + getterCallback = true; + } } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index 326cbae00..260985417 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -1747,6 +1747,13 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { return idProperty != null; } + /** + * Return false for IdClass case with multiple @Id properties. + */ + public boolean hasSingleIdProperty() { + return idPropertyIndex != -1; + } + /** * Return true if this type has a simple Id and the platform supports mutli-value binding. */ @@ -3145,7 +3152,8 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { } public boolean isIdLoaded(EntityBeanIntercept ebi) { - return ebi.isLoadedProperty(idPropertyIndex); + // assume id loaded for IdClass case with idPropertyIndex == -1 + return idPropertyIndex == -1 ? true : ebi.isLoadedProperty(idPropertyIndex); } boolean hasIdValue(EntityBean bean) { diff --git a/ebean-core/src/test/java/org/tests/model/bridge/TestIdClassScalar.java b/ebean-core/src/test/java/org/tests/model/bridge/TestIdClassScalar.java index 06b2ee143..a06c7e173 100644 --- a/ebean-core/src/test/java/org/tests/model/bridge/TestIdClassScalar.java +++ b/ebean-core/src/test/java/org/tests/model/bridge/TestIdClassScalar.java @@ -1,7 +1,9 @@ package org.tests.model.bridge; import io.ebean.BaseTestCase; +import io.ebean.DB; import io.ebean.Ebean; +import io.ebean.Transaction; import org.ebeantest.LoggedSqlCollector; import org.junit.Test; @@ -39,6 +41,28 @@ public class TestIdClassScalar extends BaseTestCase { } + @Test + public void insertBatch() { + UUID siteId = UUID.randomUUID(); + UUID userId = UUID.randomUUID(); + + try (final Transaction transaction = DB.beginTransaction()) { + transaction.setBatchMode(true); + + + BSiteUserD access = new BSiteUserD(BAccessLevel.ONE, siteId, userId); + DB.save(access); + + final UUID siteId1 = access.getSiteId(); // ArrayIndexOutOfBoundsException here + assertThat(siteId1).isNotNull(); + assertThat(access.getUserId()).isEqualTo(userId); + + transaction.commit(); + } + + DB.delete(BSiteUserD.class, new BEmbId(siteId, userId)); + } + @Test public void test() { From baa05945017ee912c8e0db6d50938028c1f0bae6 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Tue, 13 Jul 2021 12:10:23 +1200 Subject: [PATCH 64/72] Change ebean-test tests to use H2 rather than mariadb --- ebean-test/src/test/resources/application-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebean-test/src/test/resources/application-test.yaml b/ebean-test/src/test/resources/application-test.yaml index 7ad9e2dd5..d0d3fc81a 100644 --- a/ebean-test/src/test/resources/application-test.yaml +++ b/ebean-test/src/test/resources/application-test.yaml @@ -24,7 +24,7 @@ ebean: test: redis: latest - platform: mariadb #, postgres, mysql, mariadb, oracle, sqlserver, hana + platform: h2 #, postgres, mysql, mariadb, oracle, sqlserver, hana # useDocker: false ddlMode: dropCreate # none | dropCreate | create | migrations dbName: junk From 8a698bd71e0ff735c95a82f0e63347174715517e Mon Sep 17 00:00:00 2001 From: rbygrave Date: Tue, 13 Jul 2021 12:16:01 +1200 Subject: [PATCH 65/72] [maven-release-plugin] prepare release ebean-parent-12.9.3 --- ebean-api/pom.xml | 2 +- ebean-autotune/pom.xml | 6 +++--- ebean-bom/pom.xml | 30 +++++++++++++++--------------- ebean-core-type/pom.xml | 4 ++-- ebean-core/pom.xml | 10 +++++----- ebean-ddl-generator/pom.xml | 6 +++--- ebean-externalmapping-api/pom.xml | 2 +- ebean-externalmapping-xml/pom.xml | 10 +++++----- ebean-postgis/pom.xml | 6 +++--- ebean-querybean/pom.xml | 10 +++++----- ebean-redis/pom.xml | 12 ++++++------ ebean-test/pom.xml | 6 +++--- ebean/pom.xml | 8 ++++---- kotlin-querybean-generator/pom.xml | 8 ++++---- pom.xml | 4 ++-- querybean-generator/pom.xml | 2 +- 16 files changed, 63 insertions(+), 63 deletions(-) diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index a96321a13..b610d3d08 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3-SNAPSHOT + 12.9.3 ebean api diff --git a/ebean-autotune/pom.xml b/ebean-autotune/pom.xml index b5fe45223..3e8c66b68 100644 --- a/ebean-autotune/pom.xml +++ b/ebean-autotune/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3-SNAPSHOT + 12.9.3 @@ -14,7 +14,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.8.0 + ebean-parent-12.9.3 ebean autotune @@ -26,7 +26,7 @@ io.ebean ebean-core - 12.9.3-SNAPSHOT + 12.9.3 provided diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index 694875208..cc75c1959 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3-SNAPSHOT + 12.9.3 ebean bom @@ -81,88 +81,88 @@ io.ebean ebean - 12.9.3-SNAPSHOT + 12.9.3 io.ebean ebean-api - 12.9.3-SNAPSHOT + 12.9.3 io.ebean ebean-core - 12.9.3-SNAPSHOT + 12.9.3 io.ebean ebean-core-type - 12.9.3-SNAPSHOT + 12.9.3 io.ebean ebean-ddl-generator - 12.9.3-SNAPSHOT + 12.9.3 io.ebean ebean-externalmapping-api - 12.9.3-SNAPSHOT + 12.9.3 io.ebean ebean-externalmapping-xml - 12.9.3-SNAPSHOT + 12.9.3 io.ebean ebean-autotune - 12.9.3-SNAPSHOT + 12.9.3 io.ebean ebean-querybean - 12.9.3-SNAPSHOT + 12.9.3 io.ebean querybean-generator - 12.9.3-SNAPSHOT + 12.9.3 provided io.ebean kotlin-querybean-generator - 12.9.3-SNAPSHOT + 12.9.3 provided io.ebean ebean-test - 12.9.3-SNAPSHOT + 12.9.3 test io.ebean ebean-postgis - 12.9.3-SNAPSHOT + 12.9.3 io.ebean ebean-redis - 12.9.3-SNAPSHOT + 12.9.3 diff --git a/ebean-core-type/pom.xml b/ebean-core-type/pom.xml index 98aaf160a..0eb7e29ed 100644 --- a/ebean-core-type/pom.xml +++ b/ebean-core-type/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3-SNAPSHOT + 12.9.3 ebean-core-type @@ -16,7 +16,7 @@ io.ebean ebean-api - 12.9.3-SNAPSHOT + 12.9.3 diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 5575c187b..2c1a8c212 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -3,7 +3,7 @@ ebean-parent io.ebean - 12.9.3-SNAPSHOT + 12.9.3 ebean-core @@ -15,7 +15,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.8.0 + ebean-parent-12.9.3 @@ -87,19 +87,19 @@ io.ebean ebean-api - 12.9.3-SNAPSHOT + 12.9.3 io.ebean ebean-core-type - 12.9.3-SNAPSHOT + 12.9.3 io.ebean ebean-externalmapping-api - 12.9.3-SNAPSHOT + 12.9.3 diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml index d3c571848..83c4b1589 100644 --- a/ebean-ddl-generator/pom.xml +++ b/ebean-ddl-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3-SNAPSHOT + 12.9.3 ebean ddl generation @@ -28,14 +28,14 @@ io.ebean ebean-core-type - 12.9.3-SNAPSHOT + 12.9.3 provided io.ebean ebean-core - 12.9.3-SNAPSHOT + 12.9.3 provided diff --git a/ebean-externalmapping-api/pom.xml b/ebean-externalmapping-api/pom.xml index 2faf0af3d..f5651754c 100644 --- a/ebean-externalmapping-api/pom.xml +++ b/ebean-externalmapping-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3-SNAPSHOT + 12.9.3 ebean external mapping api diff --git a/ebean-externalmapping-xml/pom.xml b/ebean-externalmapping-xml/pom.xml index ad1d5b4aa..d65f141c1 100644 --- a/ebean-externalmapping-xml/pom.xml +++ b/ebean-externalmapping-xml/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3-SNAPSHOT + 12.9.3 @@ -14,7 +14,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.8.0 + ebean-parent-12.9.3 ebean external mapping xml @@ -33,7 +33,7 @@ io.ebean ebean-externalmapping-api - 12.9.3-SNAPSHOT + 12.9.3 @@ -59,14 +59,14 @@ io.ebean ebean-core - 12.9.3-SNAPSHOT + 12.9.3 test io.ebean ebean-ddl-generator - 12.9.3-SNAPSHOT + 12.9.3 test diff --git a/ebean-postgis/pom.xml b/ebean-postgis/pom.xml index 67dd8867b..52381810a 100644 --- a/ebean-postgis/pom.xml +++ b/ebean-postgis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3-SNAPSHOT + 12.9.3 ebean postgis @@ -23,7 +23,7 @@ io.ebean ebean-core - 12.9.3-SNAPSHOT + 12.9.3 provided @@ -74,7 +74,7 @@ io.ebean ebean-test - 12.9.3-SNAPSHOT + 12.9.3 test diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml index 7fbd226c0..764c85bb6 100644 --- a/ebean-querybean/pom.xml +++ b/ebean-querybean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3-SNAPSHOT + 12.9.3 ebean querybean @@ -17,7 +17,7 @@ io.ebean ebean-core - 12.9.3-SNAPSHOT + 12.9.3 provided @@ -57,21 +57,21 @@ io.ebean ebean-ddl-generator - 12.9.3-SNAPSHOT + 12.9.3 test io.ebean querybean-generator - 12.9.3-SNAPSHOT + 12.9.3 test io.ebean ebean-test - 12.9.3-SNAPSHOT + 12.9.3 test diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml index 6490c77d2..59d3315b1 100644 --- a/ebean-redis/pom.xml +++ b/ebean-redis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3-SNAPSHOT + 12.9.3 ebean-redis @@ -22,35 +22,35 @@ io.ebean ebean-api - 12.9.3-SNAPSHOT + 12.9.3 provided io.ebean ebean-core - 12.9.3-SNAPSHOT + 12.9.3 provided io.ebean ebean-querybean - 12.9.3-SNAPSHOT + 12.9.3 test io.ebean querybean-generator - 12.9.3-SNAPSHOT + 12.9.3 test io.ebean ebean-test - 12.9.3-SNAPSHOT + 12.9.3 test diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml index a99ff03cb..2a0fb7838 100644 --- a/ebean-test/pom.xml +++ b/ebean-test/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3-SNAPSHOT + 12.9.3 ebean test @@ -29,14 +29,14 @@ io.ebean ebean-core - 12.9.3-SNAPSHOT + 12.9.3 provided io.ebean ebean-ddl-generator - 12.9.3-SNAPSHOT + 12.9.3 diff --git a/ebean/pom.xml b/ebean/pom.xml index 4606f1db7..1cdb7f12f 100644 --- a/ebean/pom.xml +++ b/ebean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3-SNAPSHOT + 12.9.3 ebean composite @@ -22,20 +22,20 @@ io.ebean ebean-api - 12.9.3-SNAPSHOT + 12.9.3 io.ebean ebean-core - 12.9.3-SNAPSHOT + 12.9.3 io.ebean ebean-querybean - 12.9.3-SNAPSHOT + 12.9.3 diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml index 94d002e74..4736e3fed 100644 --- a/kotlin-querybean-generator/pom.xml +++ b/kotlin-querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3-SNAPSHOT + 12.9.3 kotlin querybean generator @@ -29,7 +29,7 @@ io.ebean ebean-querybean - 12.9.3-SNAPSHOT + 12.9.3 test @@ -43,7 +43,7 @@ io.ebean ebean-core - 12.9.3-SNAPSHOT + 12.9.3 test @@ -64,7 +64,7 @@ io.ebean ebean-ddl-generator - 12.9.3-SNAPSHOT + 12.9.3 test diff --git a/pom.xml b/pom.xml index ad1a85ddb..6c2ea07a3 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.ebean ebean-parent - 12.9.3-SNAPSHOT + 12.9.3 pom ebean parent @@ -18,7 +18,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.8.0 + ebean-parent-12.9.3 diff --git a/querybean-generator/pom.xml b/querybean-generator/pom.xml index e8c0c9d65..b34e06e54 100644 --- a/querybean-generator/pom.xml +++ b/querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3-SNAPSHOT + 12.9.3 querybean generator From be810aa56808ac5ac8feff62b0202af77a8425b7 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Tue, 13 Jul 2021 12:16:12 +1200 Subject: [PATCH 66/72] [maven-release-plugin] prepare for next development iteration --- ebean-api/pom.xml | 2 +- ebean-autotune/pom.xml | 6 +++--- ebean-bom/pom.xml | 30 +++++++++++++++--------------- ebean-core-type/pom.xml | 4 ++-- ebean-core/pom.xml | 10 +++++----- ebean-ddl-generator/pom.xml | 6 +++--- ebean-externalmapping-api/pom.xml | 2 +- ebean-externalmapping-xml/pom.xml | 10 +++++----- ebean-postgis/pom.xml | 6 +++--- ebean-querybean/pom.xml | 10 +++++----- ebean-redis/pom.xml | 12 ++++++------ ebean-test/pom.xml | 6 +++--- ebean/pom.xml | 8 ++++---- kotlin-querybean-generator/pom.xml | 8 ++++---- pom.xml | 4 ++-- querybean-generator/pom.xml | 2 +- 16 files changed, 63 insertions(+), 63 deletions(-) diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index b610d3d08..51c641732 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3 + 12.9.4-SNAPSHOT ebean api diff --git a/ebean-autotune/pom.xml b/ebean-autotune/pom.xml index 3e8c66b68..18aa6f7f2 100644 --- a/ebean-autotune/pom.xml +++ b/ebean-autotune/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3 + 12.9.4-SNAPSHOT @@ -14,7 +14,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.9.3 + ebean-parent-12.8.0 ebean autotune @@ -26,7 +26,7 @@ io.ebean ebean-core - 12.9.3 + 12.9.4-SNAPSHOT provided diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index cc75c1959..e84d91d6e 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3 + 12.9.4-SNAPSHOT ebean bom @@ -81,88 +81,88 @@ io.ebean ebean - 12.9.3 + 12.9.4-SNAPSHOT io.ebean ebean-api - 12.9.3 + 12.9.4-SNAPSHOT io.ebean ebean-core - 12.9.3 + 12.9.4-SNAPSHOT io.ebean ebean-core-type - 12.9.3 + 12.9.4-SNAPSHOT io.ebean ebean-ddl-generator - 12.9.3 + 12.9.4-SNAPSHOT io.ebean ebean-externalmapping-api - 12.9.3 + 12.9.4-SNAPSHOT io.ebean ebean-externalmapping-xml - 12.9.3 + 12.9.4-SNAPSHOT io.ebean ebean-autotune - 12.9.3 + 12.9.4-SNAPSHOT io.ebean ebean-querybean - 12.9.3 + 12.9.4-SNAPSHOT io.ebean querybean-generator - 12.9.3 + 12.9.4-SNAPSHOT provided io.ebean kotlin-querybean-generator - 12.9.3 + 12.9.4-SNAPSHOT provided io.ebean ebean-test - 12.9.3 + 12.9.4-SNAPSHOT test io.ebean ebean-postgis - 12.9.3 + 12.9.4-SNAPSHOT io.ebean ebean-redis - 12.9.3 + 12.9.4-SNAPSHOT diff --git a/ebean-core-type/pom.xml b/ebean-core-type/pom.xml index 0eb7e29ed..7d38dad0d 100644 --- a/ebean-core-type/pom.xml +++ b/ebean-core-type/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3 + 12.9.4-SNAPSHOT ebean-core-type @@ -16,7 +16,7 @@ io.ebean ebean-api - 12.9.3 + 12.9.4-SNAPSHOT diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 2c1a8c212..689ee5f2e 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -3,7 +3,7 @@ ebean-parent io.ebean - 12.9.3 + 12.9.4-SNAPSHOT ebean-core @@ -15,7 +15,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.9.3 + ebean-parent-12.8.0 @@ -87,19 +87,19 @@ io.ebean ebean-api - 12.9.3 + 12.9.4-SNAPSHOT io.ebean ebean-core-type - 12.9.3 + 12.9.4-SNAPSHOT io.ebean ebean-externalmapping-api - 12.9.3 + 12.9.4-SNAPSHOT diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml index 83c4b1589..04894f284 100644 --- a/ebean-ddl-generator/pom.xml +++ b/ebean-ddl-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3 + 12.9.4-SNAPSHOT ebean ddl generation @@ -28,14 +28,14 @@ io.ebean ebean-core-type - 12.9.3 + 12.9.4-SNAPSHOT provided io.ebean ebean-core - 12.9.3 + 12.9.4-SNAPSHOT provided diff --git a/ebean-externalmapping-api/pom.xml b/ebean-externalmapping-api/pom.xml index f5651754c..25767c934 100644 --- a/ebean-externalmapping-api/pom.xml +++ b/ebean-externalmapping-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3 + 12.9.4-SNAPSHOT ebean external mapping api diff --git a/ebean-externalmapping-xml/pom.xml b/ebean-externalmapping-xml/pom.xml index d65f141c1..f6c46a020 100644 --- a/ebean-externalmapping-xml/pom.xml +++ b/ebean-externalmapping-xml/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3 + 12.9.4-SNAPSHOT @@ -14,7 +14,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.9.3 + ebean-parent-12.8.0 ebean external mapping xml @@ -33,7 +33,7 @@ io.ebean ebean-externalmapping-api - 12.9.3 + 12.9.4-SNAPSHOT @@ -59,14 +59,14 @@ io.ebean ebean-core - 12.9.3 + 12.9.4-SNAPSHOT test io.ebean ebean-ddl-generator - 12.9.3 + 12.9.4-SNAPSHOT test diff --git a/ebean-postgis/pom.xml b/ebean-postgis/pom.xml index 52381810a..1278c7bb9 100644 --- a/ebean-postgis/pom.xml +++ b/ebean-postgis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3 + 12.9.4-SNAPSHOT ebean postgis @@ -23,7 +23,7 @@ io.ebean ebean-core - 12.9.3 + 12.9.4-SNAPSHOT provided @@ -74,7 +74,7 @@ io.ebean ebean-test - 12.9.3 + 12.9.4-SNAPSHOT test diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml index 764c85bb6..2ab505a6e 100644 --- a/ebean-querybean/pom.xml +++ b/ebean-querybean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3 + 12.9.4-SNAPSHOT ebean querybean @@ -17,7 +17,7 @@ io.ebean ebean-core - 12.9.3 + 12.9.4-SNAPSHOT provided @@ -57,21 +57,21 @@ io.ebean ebean-ddl-generator - 12.9.3 + 12.9.4-SNAPSHOT test io.ebean querybean-generator - 12.9.3 + 12.9.4-SNAPSHOT test io.ebean ebean-test - 12.9.3 + 12.9.4-SNAPSHOT test diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml index 59d3315b1..1f9ab2ba1 100644 --- a/ebean-redis/pom.xml +++ b/ebean-redis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3 + 12.9.4-SNAPSHOT ebean-redis @@ -22,35 +22,35 @@ io.ebean ebean-api - 12.9.3 + 12.9.4-SNAPSHOT provided io.ebean ebean-core - 12.9.3 + 12.9.4-SNAPSHOT provided io.ebean ebean-querybean - 12.9.3 + 12.9.4-SNAPSHOT test io.ebean querybean-generator - 12.9.3 + 12.9.4-SNAPSHOT test io.ebean ebean-test - 12.9.3 + 12.9.4-SNAPSHOT test diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml index 2a0fb7838..58283de10 100644 --- a/ebean-test/pom.xml +++ b/ebean-test/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3 + 12.9.4-SNAPSHOT ebean test @@ -29,14 +29,14 @@ io.ebean ebean-core - 12.9.3 + 12.9.4-SNAPSHOT provided io.ebean ebean-ddl-generator - 12.9.3 + 12.9.4-SNAPSHOT diff --git a/ebean/pom.xml b/ebean/pom.xml index 1cdb7f12f..e6f0c157f 100644 --- a/ebean/pom.xml +++ b/ebean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3 + 12.9.4-SNAPSHOT ebean composite @@ -22,20 +22,20 @@ io.ebean ebean-api - 12.9.3 + 12.9.4-SNAPSHOT io.ebean ebean-core - 12.9.3 + 12.9.4-SNAPSHOT io.ebean ebean-querybean - 12.9.3 + 12.9.4-SNAPSHOT diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml index 4736e3fed..d64f656be 100644 --- a/kotlin-querybean-generator/pom.xml +++ b/kotlin-querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3 + 12.9.4-SNAPSHOT kotlin querybean generator @@ -29,7 +29,7 @@ io.ebean ebean-querybean - 12.9.3 + 12.9.4-SNAPSHOT test @@ -43,7 +43,7 @@ io.ebean ebean-core - 12.9.3 + 12.9.4-SNAPSHOT test @@ -64,7 +64,7 @@ io.ebean ebean-ddl-generator - 12.9.3 + 12.9.4-SNAPSHOT test diff --git a/pom.xml b/pom.xml index 6c2ea07a3..4c4218409 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.ebean ebean-parent - 12.9.3 + 12.9.4-SNAPSHOT pom ebean parent @@ -18,7 +18,7 @@ scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.9.3 + ebean-parent-12.8.0 diff --git a/querybean-generator/pom.xml b/querybean-generator/pom.xml index b34e06e54..af8d8c273 100644 --- a/querybean-generator/pom.xml +++ b/querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.3 + 12.9.4-SNAPSHOT querybean generator From 6fb28d7ded18c1cc0614907de2c4c5c3f7c8ee20 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Tue, 13 Jul 2021 12:54:21 +1200 Subject: [PATCH 67/72] #2255 Specific test for setParameter cannot assigning the value when parameter in selecting section of query --- .../org/tests/query/sqlquery/SqlQueryTests.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ebean-core/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java b/ebean-core/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java index 572ebf39e..996f08bbe 100644 --- a/ebean-core/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java +++ b/ebean-core/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java @@ -5,6 +5,8 @@ import io.ebean.DB; import io.ebean.RowMapper; import io.ebean.SqlQuery; import io.ebean.SqlRow; +import io.ebean.annotation.ForPlatform; +import io.ebean.annotation.Platform; import io.ebean.meta.MetaTimedMetric; import org.ebeantest.LoggedSqlCollector; import org.junit.Test; @@ -24,6 +26,19 @@ import static org.junit.Assert.assertEquals; public class SqlQueryTests extends BaseTestCase { + @ForPlatform(Platform.H2) + @Test + public void selectFunction() { + + String sql = "select length(?)"; + final Long val = DB.sqlQuery(sql).setParameter("NotVeryLong").mapToScalar(Long.class).findOne(); + assertThat(val).isEqualTo(11); + + String sql2 = "select length(:val)"; + final Long val2 = DB.sqlQuery(sql2).setParameter("val", "NotVeryLong").mapToScalar(Long.class).findOne(); + assertThat(val2).isEqualTo(11); + } + @Test public void findSingleAttributeList_decimal() { From 8840628d34082ddf0e5f281d73bac8dd29107c6d Mon Sep 17 00:00:00 2001 From: rbygrave Date: Tue, 13 Jul 2021 14:45:48 +1200 Subject: [PATCH 68/72] #2262 - Transaction postCommit Callback not invoked when the active transaction has no database writes --- .../server/transaction/JdbcTransaction.java | 2 ++ .../transaction/TestTransactionCallback.java | 30 +++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java index b4bd7b714..f61f03699 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java @@ -941,11 +941,13 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes { */ private void connectionEndForQueryOnly() { try { + withEachCallback(TransactionCallback::preCommit); if (onQueryOnly == OnQueryOnly.COMMIT) { performCommit(); } else { performRollback(); } + withEachCallback(TransactionCallback::postCommit); } catch (SQLException e) { logger.error("Error when ending a query only transaction via " + onQueryOnly, e); } diff --git a/ebean-core/src/test/java/org/tests/transaction/TestTransactionCallback.java b/ebean-core/src/test/java/org/tests/transaction/TestTransactionCallback.java index 44e91f601..75ca4d567 100644 --- a/ebean-core/src/test/java/org/tests/transaction/TestTransactionCallback.java +++ b/ebean-core/src/test/java/org/tests/transaction/TestTransactionCallback.java @@ -1,10 +1,6 @@ package org.tests.transaction; -import io.ebean.BaseTestCase; -import io.ebean.Ebean; -import io.ebean.EbeanServer; -import io.ebean.Transaction; -import io.ebean.TransactionCallbackAdapter; +import io.ebean.*; import org.junit.Test; import javax.persistence.PersistenceException; @@ -50,8 +46,32 @@ public class TestTransactionCallback extends BaseTestCase { assertEquals(1, countPostCommit); assertEquals(1, countPreRollback); assertEquals(1, countPostRollback); + } + @Test + public void test_commit_whenNoDbWrite() { + try (Transaction txn = DB.beginTransaction()) { + DB.register(new MyCallback()); + txn.commit(); + } + assertEquals(1, countPreCommit); + assertEquals(1, countPostCommit); + assertEquals(0, countPreRollback); + assertEquals(0, countPostRollback); + } + + @Test + public void test_rollback_whenNoDbWrite() { + try (Transaction txn = DB.beginTransaction()) { + DB.register(new MyCallback()); + txn.rollback(); + } + + assertEquals(0, countPreCommit); + assertEquals(0, countPostCommit); + assertEquals(1, countPreRollback); + assertEquals(1, countPostRollback); } @Test(expected = PersistenceException.class) From 2763f333d48f15ceec0b31508f0230d1a90fc8db Mon Sep 17 00:00:00 2001 From: rbygrave Date: Tue, 13 Jul 2021 14:48:40 +1200 Subject: [PATCH 69/72] #2262 - Tidy tests for Transaction postCommit Callback --- .../transaction/TestTransactionCallback.java | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/ebean-core/src/test/java/org/tests/transaction/TestTransactionCallback.java b/ebean-core/src/test/java/org/tests/transaction/TestTransactionCallback.java index 75ca4d567..d926b8067 100644 --- a/ebean-core/src/test/java/org/tests/transaction/TestTransactionCallback.java +++ b/ebean-core/src/test/java/org/tests/transaction/TestTransactionCallback.java @@ -1,6 +1,9 @@ package org.tests.transaction; -import io.ebean.*; +import io.ebean.BaseTestCase; +import io.ebean.DB; +import io.ebean.Transaction; +import io.ebean.TransactionCallbackAdapter; import org.junit.Test; import javax.persistence.PersistenceException; @@ -14,20 +17,12 @@ public class TestTransactionCallback extends BaseTestCase { int countPreRollback; int countPostRollback; - @Test(expected = PersistenceException.class) - public void test_noActiveTransaction() { - - Ebean.register(new MyCallback()); - } - @Test public void test_commitAndRollback() { - - - try (Transaction txn = Ebean.beginTransaction()) { - Ebean.register(new MyCallback()); - txn.getConnection(); - Ebean.commitTransaction(); + try (Transaction txn = DB.beginTransaction()) { + DB.register(new MyCallback()); + txn.getConnection(); // Ebean assumes writes have occurred + txn.commit(); } assertEquals(1, countPreCommit); @@ -35,11 +30,11 @@ public class TestTransactionCallback extends BaseTestCase { assertEquals(0, countPreRollback); assertEquals(0, countPostRollback); - Ebean.beginTransaction(); + DB.beginTransaction(); try { - Ebean.register(new MyCallback()); + DB.register(new MyCallback()); } finally { - Ebean.rollbackTransaction(); + DB.rollbackTransaction(); } assertEquals(1, countPreCommit); @@ -75,12 +70,14 @@ public class TestTransactionCallback extends BaseTestCase { } @Test(expected = PersistenceException.class) - public void test_withEbeanserver() { - - EbeanServer server = Ebean.getServer(null); - server.register(new MyCallback()); + public void test_noActiveTransaction() { + DB.register(new MyCallback()); } + @Test(expected = PersistenceException.class) + public void test_noActiveTransaction_withDatabase() { + DB.getDefault().register(new MyCallback()); + } class MyCallback extends TransactionCallbackAdapter { From 8c77740aa7b1bf7789df2687180fe270d0ebaa3f Mon Sep 17 00:00:00 2001 From: rbygrave Date: Tue, 13 Jul 2021 15:48:10 +1200 Subject: [PATCH 70/72] Bump version tp 12.10.0-SNAPSHOT (with 12.9.4-RC1 ddl generator) --- ebean-api/pom.xml | 2 +- ebean-autotune/pom.xml | 4 ++-- ebean-bom/pom.xml | 30 +++++++++++++++--------------- ebean-core-type/pom.xml | 4 ++-- ebean-core/pom.xml | 10 +++++----- ebean-ddl-generator/pom.xml | 6 +++--- ebean-externalmapping-api/pom.xml | 2 +- ebean-externalmapping-xml/pom.xml | 8 ++++---- ebean-postgis/pom.xml | 6 +++--- ebean-querybean/pom.xml | 10 +++++----- ebean-redis/pom.xml | 12 ++++++------ ebean-test/pom.xml | 6 +++--- ebean/pom.xml | 8 ++++---- kotlin-querybean-generator/pom.xml | 8 ++++---- pom.xml | 2 +- querybean-generator/pom.xml | 2 +- 16 files changed, 60 insertions(+), 60 deletions(-) diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index 51c641732..0a2a3b777 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT ebean api diff --git a/ebean-autotune/pom.xml b/ebean-autotune/pom.xml index 18aa6f7f2..6b6e9511a 100644 --- a/ebean-autotune/pom.xml +++ b/ebean-autotune/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT @@ -26,7 +26,7 @@ io.ebean ebean-core - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT provided diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index e84d91d6e..446c823f2 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT ebean bom @@ -81,88 +81,88 @@ io.ebean ebean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT io.ebean ebean-api - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT io.ebean ebean-core - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT io.ebean ebean-core-type - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT io.ebean ebean-ddl-generator - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT io.ebean ebean-externalmapping-api - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT io.ebean ebean-externalmapping-xml - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT io.ebean ebean-autotune - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT io.ebean ebean-querybean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT io.ebean querybean-generator - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT provided io.ebean kotlin-querybean-generator - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT provided io.ebean ebean-test - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT test io.ebean ebean-postgis - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT io.ebean ebean-redis - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT diff --git a/ebean-core-type/pom.xml b/ebean-core-type/pom.xml index 7d38dad0d..9d9d22dfc 100644 --- a/ebean-core-type/pom.xml +++ b/ebean-core-type/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT ebean-core-type @@ -16,7 +16,7 @@ io.ebean ebean-api - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 196ca602f..5febb1ea6 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -3,7 +3,7 @@ ebean-parent io.ebean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT ebean-core @@ -72,7 +72,7 @@ io.ebean ebean-ddl-generator - 12.9.3a + 12.9.4-RC1 test @@ -87,19 +87,19 @@ io.ebean ebean-api - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT io.ebean ebean-core-type - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT io.ebean ebean-externalmapping-api - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml index 04894f284..2c7df7a5d 100644 --- a/ebean-ddl-generator/pom.xml +++ b/ebean-ddl-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT ebean ddl generation @@ -28,14 +28,14 @@ io.ebean ebean-core-type - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT provided io.ebean ebean-core - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT provided diff --git a/ebean-externalmapping-api/pom.xml b/ebean-externalmapping-api/pom.xml index 25767c934..5eed791a6 100644 --- a/ebean-externalmapping-api/pom.xml +++ b/ebean-externalmapping-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT ebean external mapping api diff --git a/ebean-externalmapping-xml/pom.xml b/ebean-externalmapping-xml/pom.xml index f6c46a020..76e183c67 100644 --- a/ebean-externalmapping-xml/pom.xml +++ b/ebean-externalmapping-xml/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT @@ -33,7 +33,7 @@ io.ebean ebean-externalmapping-api - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT @@ -59,14 +59,14 @@ io.ebean ebean-core - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT test io.ebean ebean-ddl-generator - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT test diff --git a/ebean-postgis/pom.xml b/ebean-postgis/pom.xml index 1278c7bb9..9a7cb3948 100644 --- a/ebean-postgis/pom.xml +++ b/ebean-postgis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT ebean postgis @@ -23,7 +23,7 @@ io.ebean ebean-core - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT provided @@ -74,7 +74,7 @@ io.ebean ebean-test - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT test diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml index 2ab505a6e..3d0600c83 100644 --- a/ebean-querybean/pom.xml +++ b/ebean-querybean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT ebean querybean @@ -17,7 +17,7 @@ io.ebean ebean-core - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT provided @@ -57,21 +57,21 @@ io.ebean ebean-ddl-generator - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT test io.ebean querybean-generator - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT test io.ebean ebean-test - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT test diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml index 1f9ab2ba1..13e5695b8 100644 --- a/ebean-redis/pom.xml +++ b/ebean-redis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT ebean-redis @@ -22,35 +22,35 @@ io.ebean ebean-api - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT provided io.ebean ebean-core - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT provided io.ebean ebean-querybean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT test io.ebean querybean-generator - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT test io.ebean ebean-test - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT test diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml index 58283de10..fa0b249da 100644 --- a/ebean-test/pom.xml +++ b/ebean-test/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT ebean test @@ -29,14 +29,14 @@ io.ebean ebean-core - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT provided io.ebean ebean-ddl-generator - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT diff --git a/ebean/pom.xml b/ebean/pom.xml index e6f0c157f..c9c24cb7b 100644 --- a/ebean/pom.xml +++ b/ebean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT ebean composite @@ -22,20 +22,20 @@ io.ebean ebean-api - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT io.ebean ebean-core - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT io.ebean ebean-querybean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml index d64f656be..a4b14ff53 100644 --- a/kotlin-querybean-generator/pom.xml +++ b/kotlin-querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT kotlin querybean generator @@ -29,7 +29,7 @@ io.ebean ebean-querybean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT test @@ -43,7 +43,7 @@ io.ebean ebean-core - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT test @@ -64,7 +64,7 @@ io.ebean ebean-ddl-generator - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT test diff --git a/pom.xml b/pom.xml index 4c4218409..da28cb2ab 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.ebean ebean-parent - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT pom ebean parent diff --git a/querybean-generator/pom.xml b/querybean-generator/pom.xml index af8d8c273..b75980c9e 100644 --- a/querybean-generator/pom.xml +++ b/querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.9.4-SNAPSHOT + 12.10.0-SNAPSHOT querybean generator From 4d15535381a4be29814c486a25bc1c3cd6eee931 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Tue, 13 Jul 2021 16:48:45 +1200 Subject: [PATCH 71/72] No functional change - tidy DefaultDbSqlContext --- .../server/query/DefaultDbSqlContext.java | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultDbSqlContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultDbSqlContext.java index 9ee2c253c..378647818 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultDbSqlContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultDbSqlContext.java @@ -11,11 +11,10 @@ import java.util.HashSet; class DefaultDbSqlContext implements DbSqlContext { private static final String COMMA = ", "; - private static final String PERIOD = "."; private static final int STRING_BUILDER_INITIAL_CAPACITY = 140; - private static final String tableAliasPlaceHolder = "${ta}"; + private static final String tableAliasManyPlaceHolder = "${mta}"; private final String columnAliasPrefix; @@ -112,26 +111,21 @@ class DefaultDbSqlContext implements DbSqlContext { @Override public void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2, String extraWhere) { - if (tableJoins == null) { tableJoins = new HashSet<>(); } - String joinKey = table + "-" + a1 + "-" + a2; if (tableJoins.contains(joinKey)) { return; } tableJoins.add(joinKey); - sb.append(" ").append(type); boolean addAsOfOnClause = false; if (draftSupport != null) { appendTable(table, draftSupport.getDraftTable(table)); - } else if (!historyQuery) { sb.append(" ").append(table).append(" "); - } else { // check if there is an associated history table and if so // use the unionAll view - we expect an additional predicate to match @@ -164,21 +158,17 @@ class DefaultDbSqlContext implements DbSqlContext { if (addAsOfOnClause) { sb.append(" and ").append(historySupport.getAsOfPredicate(a2)); } - if (extraWhere != null && !extraWhere.isEmpty()) { sb.append(" and "); // we will also need a many-table alias here - sb.append(extraWhere.replace(tableAliasPlaceHolder, a2).replace("${mta}", a1)); + sb.append(extraWhere.replace(tableAliasPlaceHolder, a2).replace(tableAliasManyPlaceHolder, a1)); } - - } private void appendTable(String table, String draftTable) { if (draftTable != null) { // there is an associated history table and view so use that sb.append(" ").append(draftTable).append(" "); - } else { sb.append(" ").append(table).append(" "); } @@ -201,7 +191,6 @@ class DefaultDbSqlContext implements DbSqlContext { @Override public String getRelativePrefix(String propName) { - return currentPrefix == null ? propName : currentPrefix + "." + propName; } @@ -236,7 +225,6 @@ class DefaultDbSqlContext implements DbSqlContext { // the same join has already been added. return; } - // we only want to add this join once formulaJoins.add(converted); sb.append(" "); @@ -271,13 +259,10 @@ class DefaultDbSqlContext implements DbSqlContext { @Override public void appendHistorySysPeriod() { - String tableAlias = tableAliasStack.peek(); - sb.append(COMMA); sb.append(historySupport.getSysPeriodLower(tableAlias)); appendColumnAlias(); - sb.append(COMMA); sb.append(historySupport.getSysPeriodUpper(tableAlias)); appendColumnAlias(); From 7408483a43ce4e6b3ecef04534fb3c2103c25e5f Mon Sep 17 00:00:00 2001 From: rbygrave Date: Thu, 15 Jul 2021 17:14:22 +1200 Subject: [PATCH 72/72] #2264 - Use of default timezone for OffsetDateTime offsets problematic for unit tests and presentation layer --- .../server/type/DefaultTypeManager.java | 12 ++++- .../server/type/ScalarTypeOffsetDateTime.java | 7 ++- .../server/type/ScalarTypeZonedDateTime.java | 7 ++- .../type/ScalarTypeOffsetDateTimeTest.java | 50 +++++++++++++++--- .../type/ScalarTypeZonedDateTimeTest.java | 51 ++++++++++++++++--- 5 files changed, 109 insertions(+), 18 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index 82c3ff46a..befe46b20 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -750,12 +750,15 @@ public final class DefaultTypeManager implements TypeManager { } private void initialiseJavaTimeTypes(DatabaseConfig config) { + + ZoneId zoneId = getZoneId(config); + typeMap.put(java.nio.file.Path.class, new ScalarTypePath()); addType(java.time.Period.class, new ScalarTypePeriod()); addType(java.time.LocalDate.class, new ScalarTypeLocalDate(jsonDate)); addType(java.time.LocalDateTime.class, new ScalarTypeLocalDateTime(jsonDateTime)); - addType(OffsetDateTime.class, new ScalarTypeOffsetDateTime(jsonDateTime)); - addType(ZonedDateTime.class, new ScalarTypeZonedDateTime(jsonDateTime)); + addType(OffsetDateTime.class, new ScalarTypeOffsetDateTime(jsonDateTime, zoneId)); + addType(ZonedDateTime.class, new ScalarTypeZonedDateTime(jsonDateTime, zoneId)); addType(Instant.class, new ScalarTypeInstant(jsonDateTime)); addType(DayOfWeek.class, new ScalarTypeDayOfWeek()); addType(Month.class, new ScalarTypeMonth()); @@ -771,6 +774,11 @@ public final class DefaultTypeManager implements TypeManager { addType(Duration.class, (durationNanos) ? new ScalarTypeDurationWithNanos() : new ScalarTypeDuration()); } + private ZoneId getZoneId(DatabaseConfig config) { + final String dataTimeZone = config.getDataTimeZone(); + return (dataTimeZone == null) ? ZoneOffset.systemDefault() : TimeZone.getTimeZone(dataTimeZone).toZoneId(); + } + private void addType(Class clazz, ScalarType scalarType) { typeMap.put(clazz, scalarType); logicalMap.putIfAbsent(clazz.getSimpleName(), scalarType); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeOffsetDateTime.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeOffsetDateTime.java index b62c693d3..b68f3eba9 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeOffsetDateTime.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeOffsetDateTime.java @@ -15,8 +15,11 @@ import static io.ebeaninternal.server.type.IsoJsonDateTimeParser.formatIso; */ public class ScalarTypeOffsetDateTime extends ScalarTypeBaseDateTime { - public ScalarTypeOffsetDateTime(JsonConfig.DateTime mode) { + private final ZoneId zoneId; + + public ScalarTypeOffsetDateTime(JsonConfig.DateTime mode, ZoneId zoneId) { super(mode, OffsetDateTime.class, false, Types.TIMESTAMP); + this.zoneId = zoneId; } @Override @@ -46,7 +49,7 @@ public class ScalarTypeOffsetDateTime extends ScalarTypeBaseDateTime { - public ScalarTypeZonedDateTime(JsonConfig.DateTime mode) { + private final ZoneId zoneId; + + public ScalarTypeZonedDateTime(JsonConfig.DateTime mode, ZoneId zoneId) { super(mode, ZonedDateTime.class, false, Types.TIMESTAMP); + this.zoneId = zoneId; } @Override @@ -44,7 +47,7 @@ public class ScalarTypeZonedDateTime extends ScalarTypeBaseDateTime jsonTester = new JsonTester<>(type); jsonTester.test(now); - ScalarTypeOffsetDateTime typeNanos = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.NANOS); + ScalarTypeOffsetDateTime typeNanos = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.NANOS, ZoneOffset.systemDefault()); jsonTester = new JsonTester<>(typeNanos); jsonTester.test(now); - ScalarTypeOffsetDateTime typeIso = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.ISO8601); + ScalarTypeOffsetDateTime typeIso = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.ISO8601, ZoneOffset.systemDefault()); jsonTester = new JsonTester<>(typeIso); jsonTester.test(now); } @@ -81,7 +119,7 @@ public class ScalarTypeOffsetDateTimeTest { @Test public void isoJsonFormatParse() { - ScalarTypeOffsetDateTime typeIso = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.ISO8601); + ScalarTypeOffsetDateTime typeIso = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.ISO8601, ZoneOffset.systemDefault()); OffsetDateTime now = OffsetDateTime.now(); String asJson = typeIso.toJsonISO8601(now); diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeZonedDateTimeTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeZonedDateTimeTest.java index ea501b273..168041723 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeZonedDateTimeTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeZonedDateTimeTest.java @@ -4,7 +4,11 @@ import io.ebean.config.JsonConfig; import org.junit.Test; import java.sql.Timestamp; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.util.TimeZone; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.*; @@ -12,12 +16,12 @@ import static org.junit.Assert.*; public class ScalarTypeZonedDateTimeTest { - ScalarTypeZonedDateTime type = new ScalarTypeZonedDateTime(JsonConfig.DateTime.MILLIS); + ScalarTypeZonedDateTime type = new ScalarTypeZonedDateTime(JsonConfig.DateTime.MILLIS, ZoneId.systemDefault()); ZonedDateTime warmUp = ZonedDateTime.now(); @Test - public void testConvertToMillis() throws Exception { + public void testConvertToMillis() { warmUp.hashCode(); @@ -29,7 +33,7 @@ public class ScalarTypeZonedDateTimeTest { } @Test - public void testConvertFromTimestamp() throws Exception { + public void testConvertFromTimestamp() { Timestamp now = new Timestamp(System.currentTimeMillis()); @@ -39,6 +43,41 @@ public class ScalarTypeZonedDateTimeTest { assertEquals(now, timestamp); } + @Test + public void convertFromInstant_with_UTC_expect_matchingZoneOffset() { + final TimeZone timeZoneToUse = TimeZone.getTimeZone("UTC"); + final ZoneOffset expectedZoneOffset = ZoneOffset.UTC; + + convertFromInstantWithConfiguredTimeZone(timeZoneToUse, expectedZoneOffset); + } + + @Test + public void convertFromInstant_with_EST_expect_matchingZoneOffset() { + final TimeZone timeZoneToUse = TimeZone.getTimeZone("EST"); + final ZoneOffset expectedOffset = OffsetDateTime.now(timeZoneToUse.toZoneId()).getOffset(); + + convertFromInstantWithConfiguredTimeZone(timeZoneToUse, expectedOffset); + } + + private void convertFromInstantWithConfiguredTimeZone(TimeZone timeZoneToUse, ZoneOffset expectedZoneOffset) { + TimeZone previous = TimeZone.getDefault(); + try { + OffsetDateTime dateTime = OffsetDateTime.parse("2021-01-01T00:00:00+11:00"); + + // test ScalarTypeOffsetDateTime with the configured timeZone to use + ScalarTypeZonedDateTime type = new ScalarTypeZonedDateTime(JsonConfig.DateTime.MILLIS, timeZoneToUse.toZoneId()); + + // effectively we desire to ignore the system timezone and use the configured one + TimeZone.setDefault(timeZoneToUse); + + final ZonedDateTime zonedDateTime = type.convertFromInstant(dateTime.toInstant()); + + assertEquals(expectedZoneOffset, zonedDateTime.getOffset()); + + } finally { + TimeZone.setDefault(previous); + } + } @Test public void testToJdbcType() throws Exception { @@ -68,11 +107,11 @@ public class ScalarTypeZonedDateTimeTest { JsonTester jsonTester = new JsonTester<>(type); jsonTester.test(now); - ScalarTypeZonedDateTime typeNanos = new ScalarTypeZonedDateTime(JsonConfig.DateTime.NANOS); + ScalarTypeZonedDateTime typeNanos = new ScalarTypeZonedDateTime(JsonConfig.DateTime.NANOS, ZoneId.systemDefault()); jsonTester = new JsonTester<>(typeNanos); jsonTester.test(now); - ScalarTypeZonedDateTime typeIso = new ScalarTypeZonedDateTime(JsonConfig.DateTime.ISO8601); + ScalarTypeZonedDateTime typeIso = new ScalarTypeZonedDateTime(JsonConfig.DateTime.ISO8601, ZoneId.systemDefault()); jsonTester = new JsonTester<>(typeIso); jsonTester.test(now); } @@ -80,7 +119,7 @@ public class ScalarTypeZonedDateTimeTest { @Test public void toJsonISO8601() { - ScalarTypeZonedDateTime typeIso = new ScalarTypeZonedDateTime(JsonConfig.DateTime.ISO8601); + ScalarTypeZonedDateTime typeIso = new ScalarTypeZonedDateTime(JsonConfig.DateTime.ISO8601, ZoneId.systemDefault()); ZonedDateTime now = ZonedDateTime.now(); String asJson = typeIso.toJsonISO8601(now);