From 7e265f7bde97120cf66e59e43abf044e0b047764 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Tue, 13 Jul 2021 09:09:30 +0200 Subject: [PATCH] FIX: SubQuery with fetch-path does not work correctly --- .../server/query/SqlTreeBuilder.java | 2 +- .../server/querydefn/DefaultOrmQuery.java | 4 +- .../java/org/tests/query/TestSubQuery.java | 71 +++++++++++++++++++ 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java index 239e8a58e..e4c936e45 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java @@ -311,7 +311,7 @@ public final class SqlTreeBuilder { } else { // do not read Id on child beans (e.g. when used with fetch()) - boolean withId = isNotSingleAttribute(); + boolean withId = isNotSingleAttribute() && !subQuery; return new SqlTreeNodeBean(prefix, prop, props, myList, withId, temporalMode, disableLazyLoad); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 96a923637..3a81183e9 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -616,7 +616,7 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery { * Limit the number of fetch joins to Many properties, mark as query joins as needed. */ private void markQueryJoins() { - detail.markQueryJoins(beanDescriptor, lazyLoadManyPath, isAllowOneManyFetch(), type != Type.ATTRIBUTE); + detail.markQueryJoins(beanDescriptor, lazyLoadManyPath, isAllowOneManyFetch(), type != Type.ATTRIBUTE && type != Type.SUBQUERY); } private boolean isAllowOneManyFetch() { @@ -629,7 +629,7 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery { @Override public void setDefaultSelectClause() { - if (type != Type.ATTRIBUTE) { + if (type != Type.ATTRIBUTE && type != Type.SUBQUERY) { detail.setDefaultSelectClause(beanDescriptor); } else if (!detail.hasSelectClause()) { // explicit empty select when single attribute query on non-root fetch path diff --git a/ebean-core/src/test/java/org/tests/query/TestSubQuery.java b/ebean-core/src/test/java/org/tests/query/TestSubQuery.java index db9eb7cbd..61ffacf56 100644 --- a/ebean-core/src/test/java/org/tests/query/TestSubQuery.java +++ b/ebean-core/src/test/java/org/tests/query/TestSubQuery.java @@ -6,6 +6,8 @@ import io.ebean.Query; import org.junit.Test; import org.tests.model.basic.CKeyParent; import org.tests.model.basic.Order; +import org.tests.model.basic.OrderDetail; +import org.tests.model.basic.OrderShipment; import org.tests.model.basic.ResetBasicData; import org.tests.model.basic.Vehicle; import org.tests.model.basic.VehicleDriver; @@ -47,6 +49,75 @@ public class TestSubQuery extends BaseTestCase { DB.find(Order.class).where().isIn("id", sq).findList(); } + /** + * Testcase, that discovered, that DefaultOrmQuery.setDefaultSelectClause is set on subQueries with fetch path. + * Also checks, that SqlTreeBuilder does not read id on Many2One props. + */ + @Test + public void test_IsInWithFetchSubQuery1() { + + List productIds = new ArrayList<>(); + productIds.add(3); + + Query sq = DB.createQuery(OrderDetail.class).fetch("order", "id").where() + .isIn("product.id", productIds).query(); + + // execute the subQuery as copy (generatedSQL must be part of original query) + Query debugSq = sq.copy(); + debugSq.findSingleAttribute(); + assertThat(debugSq.getGeneratedSql()).isEqualTo( + "select t1.id from o_order_detail t0 join o_order t1 on t1.id = t0.order_id where t0.product_id in (?)"); + + Query query = DB.find(Order.class).select("shipDate").where().isIn("id", sq).query(); + query.findSingleAttribute(); + + assertThat(query.getGeneratedSql()) + .isEqualTo("select t0.ship_date from o_order t0 where (t0.id) in (" + debugSq.getGeneratedSql() + ")"); + } + + /** + * Test checks, that DefaultOrmQuery.markQueryJoins handles subQuery correct. + */ + @Test + public void test_IsInWithFetchSubQuery2() { + + Query sq = DB.createQuery(OrderDetail.class).fetch("order.customer", "anniversary").where() + .eq("order.customer.name", "Roland") + .query().setDistinct(true); + + // execute the subQuery as copy (generatedSQL must be part of original query) + Query debugSq = sq.copy(); + debugSq.findSingleAttribute(); + + Query query = DB.find(Order.class).select("status").where().isIn("shipDate", sq).query(); + query.findSingleAttribute(); + assertThat(query.getGeneratedSql()) + .isEqualTo("select t0.status from o_order t0 where (t0.ship_date) in (" + debugSq.getGeneratedSql() + ")"); + } + + /** + * Checks, that SqlTreeBuilder does not read id on One2Many props. + */ + @Test + public void test_IsInWithFetchSubQuery3() { + + List productIds = new ArrayList<>(); + productIds.add(3); + + Query sq = DB.createQuery(OrderDetail.class).fetch("order.shipments", "id").where() + .isIn("product.id", productIds).query(); + + // execute the subQuery as copy (generatedSQL must be part of original query) + Query debugSq = sq.copy(); + debugSq.findSingleAttribute(); + + Query query = DB.find(OrderShipment.class).select("shipTime").where().isIn("id", sq).query(); + query.findSingleAttribute(); + + assertThat(query.getGeneratedSql()) + .isEqualTo("select t0.ship_time from or_order_ship t0 where (t0.id) in (" + debugSq.getGeneratedSql() + ")"); + } + public void testCompositeKey() { ResetBasicData.reset();