diff --git a/ebean-api/src/main/java/io/ebean/QueryBuilder.java b/ebean-api/src/main/java/io/ebean/QueryBuilder.java index 1cc1d2d73e..4a95544de4 100644 --- a/ebean-api/src/main/java/io/ebean/QueryBuilder.java +++ b/ebean-api/src/main/java/io/ebean/QueryBuilder.java @@ -20,6 +20,7 @@ * @param The type of the builder * @param The entity bean type */ +//public interface QueryBuilder, T> extends QueryBuilderProjection { public interface QueryBuilder extends QueryBuilderProjection { /** @@ -27,6 +28,15 @@ public interface QueryBuilder extends QueryBuilderProjection { */ SELF alias(String alias); + /** + * Apply changes to the query using a function. + *

+ * This can be used to apply generic features to queries. + * + * @param apply The changes to apply to the query + */ + SELF also(Consumer> apply); + /** * Apply changes to the query conditional on the supplied predicate. *

diff --git a/ebean-api/src/main/java/io/ebean/QueryBuilderProjection.java b/ebean-api/src/main/java/io/ebean/QueryBuilderProjection.java index 54ff946571..fc84169a9e 100644 --- a/ebean-api/src/main/java/io/ebean/QueryBuilderProjection.java +++ b/ebean-api/src/main/java/io/ebean/QueryBuilderProjection.java @@ -6,6 +6,7 @@ * @param The builder type * @param The entity bean type */ +//public interface QueryBuilderProjection, T> { public interface QueryBuilderProjection { /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java index 9f3278d1d8..1132cc9c85 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java @@ -2,30 +2,7 @@ import io.avaje.lang.NonNullApi; import io.avaje.lang.Nullable; -import io.ebean.CacheMode; -import io.ebean.CountDistinctOrder; -import io.ebean.Database; -import io.ebean.DtoQuery; -import io.ebean.Expression; -import io.ebean.ExpressionFactory; -import io.ebean.ExpressionList; -import io.ebean.FetchConfig; -import io.ebean.FetchGroup; -import io.ebean.FetchPath; -import io.ebean.FutureIds; -import io.ebean.FutureList; -import io.ebean.FutureRowCount; -import io.ebean.OrderBy; -import io.ebean.PagedList; -import io.ebean.PersistenceContextScope; -import io.ebean.ProfileLocation; -import io.ebean.Query; -import io.ebean.QueryIterator; -import io.ebean.QueryType; -import io.ebean.RawSql; -import io.ebean.Transaction; -import io.ebean.UpdateQuery; -import io.ebean.Version; +import io.ebean.*; import io.ebean.service.SpiFetchGroupQuery; import io.ebeaninternal.api.SpiQueryFetch; import io.ebeaninternal.server.querydefn.OrmQueryDetail; @@ -230,6 +207,11 @@ public Query apply(FetchPath fetchPath) { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } + @Override + public Query also(Consumer> apply) { + throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); + } + @Override public Query alsoIf(BooleanSupplier predicate, Consumer> apply) { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); 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 097c2fc333..91ac3f0b30 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 @@ -293,6 +293,12 @@ public final Query apply(FetchPath fetchPath) { return this; } + @Override + public Query also(Consumer> apply) { + apply.accept(this); + return this; + } + @Override public Query alsoIf(BooleanSupplier predicate, Consumer> consumer) { if (predicate.getAsBoolean()) { diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/IQueryBean.java b/ebean-querybean/src/main/java/io/ebean/typequery/IQueryBean.java index 3fff7c44bf..169bafc073 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/IQueryBean.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/IQueryBean.java @@ -44,6 +44,7 @@ * @param the entity bean type (normal entity bean type e.g. Customer) * @param the specific query bean type (e.g. QCustomer) */ +//public interface IQueryBean> extends QueryBuilder { public interface IQueryBean extends QueryBuilder { /** diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/QueryBean.java b/ebean-querybean/src/main/java/io/ebean/typequery/QueryBean.java index 66c0215b7f..3b98b0885b 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/QueryBean.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/QueryBean.java @@ -57,6 +57,7 @@ * @param the specific root query bean type (e.g. QCustomer) */ @NonNullApi +//public abstract class QueryBean> implements IQueryBean { public abstract class QueryBean implements IQueryBean { /** @@ -275,6 +276,13 @@ public final R apply(FetchPath pathProperties) { return root; } + + @Override + public R also(Consumer> apply) { + apply.accept(this); + return root; + } + @Override public final R alsoIf(BooleanSupplier predicate, Consumer apply) { if (predicate.getAsBoolean()) { diff --git a/ebean-querybean/src/test/java/org/querytest/QueryAlsoIfTest.java b/ebean-querybean/src/test/java/org/querytest/QueryAlsoIfTest.java index a9a4dbf50f..4c22257f91 100644 --- a/ebean-querybean/src/test/java/org/querytest/QueryAlsoIfTest.java +++ b/ebean-querybean/src/test/java/org/querytest/QueryAlsoIfTest.java @@ -1,9 +1,12 @@ package org.querytest; +import io.ebean.QueryBuilder; import org.example.domain.Customer; import org.example.domain.query.QCustomer; import org.junit.jupiter.api.Test; +import java.util.function.Consumer; + import static org.assertj.core.api.Assertions.assertThat; import static org.example.domain.query.QCustomer.Alias.name; @@ -11,6 +14,32 @@ class QueryAlsoIfTest { int dummy = 1; + @Test + void also() { + var myPager = new MyPager(10); + var q = new QCustomer() + .select(name) + .also(myPager) + .query(); + + q.findList(); + assertThat(q.getGeneratedSql()).contains("select /* QueryAlsoIfTest.also */ t0.id, t0.name from be_customer t0 limit 10"); + } + + static class MyPager implements Consumer> { + + final int maxRows; + + MyPager(int maxRows) { + this.maxRows = maxRows; + } + + @Override + public void accept(QueryBuilder queryBuilder) { + queryBuilder.setMaxRows(maxRows); + } + } + @Test void apply() { var q = new QCustomer() diff --git a/ebean-test/src/test/java/org/tests/basic/TestFetchId.java b/ebean-test/src/test/java/org/tests/basic/TestFetchId.java index 00fb022527..80eecd2a76 100644 --- a/ebean-test/src/test/java/org/tests/basic/TestFetchId.java +++ b/ebean-test/src/test/java/org/tests/basic/TestFetchId.java @@ -1,5 +1,6 @@ package org.tests.basic; +import io.ebean.test.LoggedSql; import io.ebean.xtest.BaseTestCase; import io.ebean.DB; import io.ebean.FutureIds; @@ -28,8 +29,13 @@ public void testFetchId() throws InterruptedException, ExecutionException { .gt("details.id", 0) .query(); + LoggedSql.start(); List ids = query.findIds(); assertThat(ids).isNotEmpty(); + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).doesNotContain("order by"); + assertThat(sql.get(0)).doesNotContain("join o_order_detail t1"); FutureIds futureIds = query.findFutureIds(); diff --git a/ebean-test/src/test/java/org/tests/batchload/TestQueryJoinToAssocOne.java b/ebean-test/src/test/java/org/tests/batchload/TestQueryJoinToAssocOne.java index 3a4eeb6752..56e547d6d1 100644 --- a/ebean-test/src/test/java/org/tests/batchload/TestQueryJoinToAssocOne.java +++ b/ebean-test/src/test/java/org/tests/batchload/TestQueryJoinToAssocOne.java @@ -230,5 +230,6 @@ public void testJoinOnPartiallyPopulatedParent_withLazyLoadingDisabled() { String originQuery = trimSql(loggedSql.get(0), 5); assertThat(originQuery).contains("select t0.id, t0.status, t0.ship_date, t1.id, t1.order_qty, t1.unit_price"); assertThat(originQuery).contains(" from o_order t0 left join o_order_detail t1 "); + assertThat(originQuery).contains(" order by t0.id, t1.id asc, t1.order_qty asc, t1.cretime desc;"); } } diff --git a/ebean-test/src/test/java/org/tests/cascade/TestOrderedList.java b/ebean-test/src/test/java/org/tests/cascade/TestOrderedList.java index 15ceacfc8b..695d72548f 100644 --- a/ebean-test/src/test/java/org/tests/cascade/TestOrderedList.java +++ b/ebean-test/src/test/java/org/tests/cascade/TestOrderedList.java @@ -185,7 +185,7 @@ public void testModifyListWithCache() { DB.save(masterDb); masterDb = DB.find(OmCacheOrderedMaster.class, master.getId()); - assertThat(masterDb.getDetails()).containsExactly(detail3, detail1); + assertThat(masterDb.getDetails()).containsExactlyInAnyOrder(detail3, detail1); } } diff --git a/ebean-test/src/test/java/org/tests/query/TestManyWhereJoin.java b/ebean-test/src/test/java/org/tests/query/TestManyWhereJoin.java index 3cacc5208d..2f4701ddd4 100644 --- a/ebean-test/src/test/java/org/tests/query/TestManyWhereJoin.java +++ b/ebean-test/src/test/java/org/tests/query/TestManyWhereJoin.java @@ -181,5 +181,6 @@ public void testUsingForeignKeyAndFetch() { // additional join for fetching the many details assertThat(sql).contains(" left join o_order_detail t1 on t1.order_id = t0.id"); + assertThat(sql).contains("order by t0.cretime, t0.id, t1.id asc, t1.order_qty asc, t1.cretime desc"); } } diff --git a/ebean-test/src/test/java/org/tests/query/TestQueryAlsoIf.java b/ebean-test/src/test/java/org/tests/query/TestQueryAlsoIf.java index 3ed66c8003..08478d9399 100644 --- a/ebean-test/src/test/java/org/tests/query/TestQueryAlsoIf.java +++ b/ebean-test/src/test/java/org/tests/query/TestQueryAlsoIf.java @@ -2,16 +2,49 @@ import io.ebean.DB; import io.ebean.Query; +import io.ebean.QueryBuilder; +import io.ebean.xtest.BaseTestCase; import org.junit.jupiter.api.Test; import org.tests.model.basic.Customer; import org.tests.model.basic.ResetBasicData; +import java.util.function.Consumer; + import static org.assertj.core.api.Assertions.assertThat; -class TestQueryAlsoIf { +class TestQueryAlsoIf extends BaseTestCase { int dummy = 1; + @Test + void also() { + ResetBasicData.reset(); + + MyPager myPager = new MyPager(10); + Query query = DB.find(Customer.class) + .select("name") + .also(myPager); + + query.findList(); + if (isLimitOffset()) { + assertThat(query.getGeneratedSql()).isEqualTo("select t0.id, t0.name from o_customer t0 limit 10"); + } + } + + static class MyPager implements Consumer> { + + final int maxRows; + + MyPager(int maxRows) { + this.maxRows = maxRows; + } + + @Override + public void accept(QueryBuilder queryBuilder) { + queryBuilder.setMaxRows(maxRows); + } + } + @Test void apply() { ResetBasicData.reset(); diff --git a/ebean-test/src/test/java/org/tests/query/TestQueryFilterMany.java b/ebean-test/src/test/java/org/tests/query/TestQueryFilterMany.java index 6c4a3915b2..70a99047eb 100644 --- a/ebean-test/src/test/java/org/tests/query/TestQueryFilterMany.java +++ b/ebean-test/src/test/java/org/tests/query/TestQueryFilterMany.java @@ -213,6 +213,7 @@ public void test_with_findOne_rawSameQuery() { assertThat(sql).hasSize(1); assertThat(sql.get(0)).contains("from o_customer t0 left join o_order t1"); assertThat(sql.get(0)).contains("where t1.order_date is not null"); + assertThat(sql.get(0)).contains("order by t0.id"); } @Test @@ -269,6 +270,7 @@ public void test_filterMany_in_findCount() { List sqlList = LoggedSql.stop(); assertEquals(1, sqlList.size()); assertThat(sqlList.get(0)).contains("select count(*) from o_customer"); + assertThat(sqlList.get(0)).doesNotContain("order by"); } @Test diff --git a/ebean-test/src/test/java/org/tests/query/TestQueryFilterManyOnM2M.java b/ebean-test/src/test/java/org/tests/query/TestQueryFilterManyOnM2M.java index 55690536e6..f04d61c448 100644 --- a/ebean-test/src/test/java/org/tests/query/TestQueryFilterManyOnM2M.java +++ b/ebean-test/src/test/java/org/tests/query/TestQueryFilterManyOnM2M.java @@ -1,17 +1,27 @@ package org.tests.query; +import io.ebean.test.LoggedSql; import io.ebean.xtest.BaseTestCase; import io.ebean.DB; import org.junit.jupiter.api.Test; import org.tests.model.basic.MUser; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + public class TestQueryFilterManyOnM2M extends BaseTestCase { @Test public void test() { + LoggedSql.start(); DB.find(MUser.class).fetch("roles").filterMany("roles").ilike("roleName", "Jim%").findList(); + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("select t0.userid, t0.user_name, t0.user_type_id, t1.roleid, t1.role_name from muser t0 left join mrole_muser t1z_ on t1z_.muser_userid = t0.userid left join mrole t1 on t1.roleid = t1z_.mrole_roleid where lower(t1.role_name) like"); + assertThat(sql.get(0)).contains("order by t0.userid;"); } } diff --git a/ebean-test/src/test/java/org/tests/query/TestQueryFilterManySimple.java b/ebean-test/src/test/java/org/tests/query/TestQueryFilterManySimple.java index c9af3e2cea..482a70e45b 100644 --- a/ebean-test/src/test/java/org/tests/query/TestQueryFilterManySimple.java +++ b/ebean-test/src/test/java/org/tests/query/TestQueryFilterManySimple.java @@ -1,5 +1,6 @@ package org.tests.query; +import io.ebean.test.LoggedSql; import io.ebean.xtest.BaseTestCase; import io.ebean.DB; import org.junit.jupiter.api.Test; @@ -10,6 +11,8 @@ import java.sql.Date; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; + public class TestQueryFilterManySimple extends BaseTestCase { @Test @@ -20,6 +23,7 @@ public void test() { // not really last week :) Date lastWeek = Date.valueOf("2010-01-01"); + LoggedSql.start(); List list = DB.find(Customer.class) // .join("orders", new JoinConfig().lazy()) // .join("orders", new JoinConfig().query()) @@ -29,5 +33,14 @@ public void test() { // invoke lazy loading list.get(0).getOrders().size(); + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(2); + assertThat(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"); + assertThat(sql.get(0)).contains("order by t0.id"); + if (isPostgresCompatible()) { + assertThat(sql.get(1)).contains("from contact t0 where (t0.customer_id) = any(?) and t0.first_name is not null;"); + } else { + assertThat(sql.get(1)).contains("from contact t0 where (t0.customer_id) in (?) and t0.first_name is not null;"); + } } } diff --git a/ebean-test/src/test/java/org/tests/query/TestRowCount.java b/ebean-test/src/test/java/org/tests/query/TestRowCount.java index 627b9e883b..93d568ae45 100644 --- a/ebean-test/src/test/java/org/tests/query/TestRowCount.java +++ b/ebean-test/src/test/java/org/tests/query/TestRowCount.java @@ -1,5 +1,6 @@ package org.tests.query; +import io.ebean.test.LoggedSql; import io.ebean.xtest.BaseTestCase; import io.ebean.DB; import io.ebean.Query; @@ -19,6 +20,7 @@ public class TestRowCount extends BaseTestCase { public void test() { ResetBasicData.reset(); + LoggedSql.start(); Query query = DB.find(Order.class) .fetch("details") @@ -32,6 +34,13 @@ public void test() { List ids = query.findIds(); List list = query.findList(); + + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(3); + assertThat(sql.get(0)).doesNotContain("order by"); + assertThat(sql.get(1)).contains("order by"); + assertThat(sql.get(2)).contains("order by t0.id desc"); + System.out.println(list); for (Order order : list) { order.getStatus();