From 6865e1e7dc2cf712849df28c96d825042a887c82 Mon Sep 17 00:00:00 2001 From: Noemi Praml Date: Thu, 26 Jun 2025 08:43:05 +0200 Subject: [PATCH 1/6] Test fetch ID only --- .../java/org/tests/query/TestFetchIdOnly.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 ebean-test/src/test/java/org/tests/query/TestFetchIdOnly.java diff --git a/ebean-test/src/test/java/org/tests/query/TestFetchIdOnly.java b/ebean-test/src/test/java/org/tests/query/TestFetchIdOnly.java new file mode 100644 index 0000000000..e5955e3abc --- /dev/null +++ b/ebean-test/src/test/java/org/tests/query/TestFetchIdOnly.java @@ -0,0 +1,55 @@ +package org.tests.query; + +import io.ebean.DB; +import io.ebean.Query; +import io.ebean.test.LoggedSql; +import io.ebean.text.PathProperties; +import io.ebean.xtest.BaseTestCase; +import org.junit.jupiter.api.Test; +import org.tests.model.basic.Order; +import org.tests.model.basic.ResetBasicData; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestFetchIdOnly extends BaseTestCase { + + @Test + void test_withFetchPath() { + ResetBasicData.reset(); + + PathProperties root = PathProperties.parse("status,customer(id)"); + LoggedSql.start(); + Query query = DB.find(Order.class).apply(root); + query.findList(); + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.kcustomer_id from o_order t0"); + } + + @Test + void test_withSelect() { + ResetBasicData.reset(); + + LoggedSql.start(); + Query query = DB.find(Order.class).select("status, customer"); + query.findList(); + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.kcustomer_id from o_order t0"); + } + + @Test + void test_withFetch() { + ResetBasicData.reset(); + + LoggedSql.start(); + Query query = DB.find(Order.class).select("status").fetch("customer", "id"); + query.findList(); + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.kcustomer_id from o_order t0"); + } + +} From 3a8ab567de80d6744ed6c9f07a811bad7f61f491 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Thu, 3 Jul 2025 15:53:29 +0200 Subject: [PATCH 2/6] Possible fix --- .../server/querydefn/OrmQueryDetail.java | 27 ++++++++++++++++++- .../server/querydefn/OrmQueryProperties.java | 13 ++++++++- .../java/org/tests/query/TestFetchIdOnly.java | 22 ++++++++------- 3 files changed, 50 insertions(+), 12 deletions(-) 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 d56de43fff..3d4555e386 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 @@ -6,10 +6,11 @@ import io.ebeaninternal.api.SpiQueryManyJoin; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.deploy.BeanPropertyAssoc; +import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; import io.ebeaninternal.server.el.ElPropertyDeploy; import io.ebeaninternal.server.el.ElPropertyValue; - import jakarta.persistence.PersistenceException; + import java.io.Serializable; import java.util.*; @@ -343,6 +344,30 @@ SpiQueryManyJoin markQueryJoins(BeanDescriptor beanDescriptor, String lazyLoa boolean fetchJoinFirstMany = allowOne; sortFetchPaths(beanDescriptor, addIds); + + ArrayList> entries = new ArrayList<>(fetchPaths.entrySet()); + ListIterator> it = entries.listIterator(entries.size()); + while (it.hasPrevious()) { + Map.Entry entry = it.previous(); + String path = entry.getKey(); + ElPropertyValue el = beanDescriptor.elGetValue(path); + if (el != null && el.beanProperty() instanceof BeanPropertyAssocOne) { + OrmQueryProperties prop = entry.getValue(); + if (prop.includesExactly(el.beanProperty().descriptor().idName())) { + OrmQueryProperties parent = prop.getParentPath() == null ? baseProps : fetchPaths.get(prop.getParentPath()); + if (parent.hasProperties()) { + // move the fetch to the parent path + parent.addInclude(el.beanProperty().name()); + it.remove(); + } + } + } + } + if (fetchPaths.size() != entries.size()) { + fetchPaths = new LinkedHashMap<>(); + entries.forEach(entry -> fetchPaths.put(entry.getKey(), entry.getValue())); + } + List pairs = sortByFetchPreference(beanDescriptor); for (FetchEntry pair : pairs) { 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 94bb5ab619..6efa4c8365 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 @@ -191,7 +191,7 @@ private SpiExpressionList getFilterManyTrimPath(int trimPath) { * Adjust filterMany expressions for inclusion in main query. */ public void filterManyInline() { - if (filterMany != null){ + if (filterMany != null) { filterMany.prefixProperty(path); } } @@ -431,4 +431,15 @@ public void queryPlanHash(StringBuilder builder) { builder.append('}'); } + boolean includesExactly(String property) { + return included != null + && included.size() == 1 + && included.contains(property); + } + + void addInclude(String prop) { + if (!allProperties) { + included.add(prop); + } + } } diff --git a/ebean-test/src/test/java/org/tests/query/TestFetchIdOnly.java b/ebean-test/src/test/java/org/tests/query/TestFetchIdOnly.java index e5955e3abc..c195206164 100644 --- a/ebean-test/src/test/java/org/tests/query/TestFetchIdOnly.java +++ b/ebean-test/src/test/java/org/tests/query/TestFetchIdOnly.java @@ -7,7 +7,6 @@ import io.ebean.xtest.BaseTestCase; import org.junit.jupiter.api.Test; import org.tests.model.basic.Order; -import org.tests.model.basic.ResetBasicData; import java.util.List; @@ -17,39 +16,42 @@ public class TestFetchIdOnly extends BaseTestCase { @Test void test_withFetchPath() { - ResetBasicData.reset(); - PathProperties root = PathProperties.parse("status,customer(id)"); LoggedSql.start(); Query query = DB.find(Order.class).apply(root); query.findList(); List sql = LoggedSql.stop(); assertThat(sql).hasSize(1); - assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.kcustomer_id from o_order t0"); + assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.kcustomer_id from o_order t0;"); } @Test void test_withSelect() { - ResetBasicData.reset(); - LoggedSql.start(); Query query = DB.find(Order.class).select("status, customer"); query.findList(); List sql = LoggedSql.stop(); assertThat(sql).hasSize(1); - assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.kcustomer_id from o_order t0"); + assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.kcustomer_id from o_order t0;"); } @Test void test_withFetch() { - ResetBasicData.reset(); - LoggedSql.start(); Query query = DB.find(Order.class).select("status").fetch("customer", "id"); query.findList(); List sql = LoggedSql.stop(); assertThat(sql).hasSize(1); - assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.kcustomer_id from o_order t0"); + assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.kcustomer_id from o_order t0;"); } + @Test + void test_withChildFetch() { + LoggedSql.start(); + Query query = DB.find(Order.class).select("status").fetch("customer.shippingAddress", "id"); + query.findList(); + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("select t0.id, t0.status, t1.id, t1.shipping_address_id from o_order t0 join o_customer t1 on t1.id = t0.kcustomer_id;"); + } } From 6aa1f7796dda9fc1623f7abd68b7681e0e107ee4 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Thu, 3 Jul 2025 16:39:27 +0200 Subject: [PATCH 3/6] Tidy up code --- .../server/querydefn/OrmQueryDetail.java | 54 +++++++++++-------- .../server/querydefn/OrmQueryProperties.java | 6 +-- 2 files changed, 34 insertions(+), 26 deletions(-) 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 3d4555e386..1841e86651 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 @@ -5,6 +5,7 @@ import io.ebean.util.SplitName; import io.ebeaninternal.api.SpiQueryManyJoin; import io.ebeaninternal.server.deploy.BeanDescriptor; +import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.deploy.BeanPropertyAssoc; import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; import io.ebeaninternal.server.el.ElPropertyDeploy; @@ -327,6 +328,35 @@ private void sortFetchPaths(BeanDescriptor d, OrmQueryProperties p, LinkedHas } } + /** + * After sorting, we try to convert id only fetches of a bean into a select of the parent bean. + */ + private void convertIdFetches(BeanDescriptor desc) { + String[] paths = fetchPaths.keySet().toArray(new String[0]); + int i = paths.length; + // iterate backwards - otherwise we might detect id only fetches, which are later converted + while (i-- > 0) { + String path = paths[i]; + ElPropertyDeploy el = desc.elPropertyDeploy(path); + if (el == null) { + throw new PersistenceException("Invalid fetch path " + path + " from " + desc.fullName()); + } else if (el.beanProperty() instanceof BeanPropertyAssocOne) { + BeanPropertyAssocOne assoc = (BeanPropertyAssocOne) el.beanProperty(); + if (assoc.hasForeignKeyConstraint()) { + OrmQueryProperties prop = fetchPaths.get(path); + // check, if we have exactly the ID selected and convert these fetches to a select of the parent bean + if (prop.includesExactly(assoc.descriptor().idName())) { + OrmQueryProperties parentProp = prop.getParentPath() == null ? baseProps : fetchPaths.get(prop.getParentPath()); + if (parentProp.hasProperties()) { + parentProp.addInclude(assoc.name()); + fetchPaths.remove(path); + } + } + } + } + } + } + /** * Mark 'fetch joins' to 'many' properties over to 'query joins' where needed. * @@ -345,29 +375,7 @@ SpiQueryManyJoin markQueryJoins(BeanDescriptor beanDescriptor, String lazyLoa sortFetchPaths(beanDescriptor, addIds); - ArrayList> entries = new ArrayList<>(fetchPaths.entrySet()); - ListIterator> it = entries.listIterator(entries.size()); - while (it.hasPrevious()) { - Map.Entry entry = it.previous(); - String path = entry.getKey(); - ElPropertyValue el = beanDescriptor.elGetValue(path); - if (el != null && el.beanProperty() instanceof BeanPropertyAssocOne) { - OrmQueryProperties prop = entry.getValue(); - if (prop.includesExactly(el.beanProperty().descriptor().idName())) { - OrmQueryProperties parent = prop.getParentPath() == null ? baseProps : fetchPaths.get(prop.getParentPath()); - if (parent.hasProperties()) { - // move the fetch to the parent path - parent.addInclude(el.beanProperty().name()); - it.remove(); - } - } - } - } - if (fetchPaths.size() != entries.size()) { - fetchPaths = new LinkedHashMap<>(); - entries.forEach(entry -> fetchPaths.put(entry.getKey(), entry.getValue())); - } - + convertIdFetches(beanDescriptor); List pairs = sortByFetchPreference(beanDescriptor); for (FetchEntry pair : pairs) { 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 6efa4c8365..5648ec3c91 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 @@ -87,7 +87,7 @@ public OrmQueryProperties(String path, String rawProperties, FetchConfig fetchCo this.parentPath = SplitName.parent(path); OrmQueryPropertiesParser.Response response = OrmQueryPropertiesParser.parse(rawProperties); this.allProperties = response.allProperties; - this.included = response.included; + this.included = response.included; // modifiable if (fetchConfig != null) { this.fetchConfig = fetchConfig; this.cache = fetchConfig.isCache(); @@ -104,7 +104,7 @@ public OrmQueryProperties(String path, Set included) { OrmQueryProperties(String path, Set included, FetchConfig fetchConfig) { this.path = path; this.parentPath = SplitName.parent(path); - this.included = included; + this.included = (included == null) ? null : new LinkedHashSet<>(included); this.allProperties = false; this.fetchConfig = fetchConfig; this.cache = fetchConfig.isCache(); @@ -114,7 +114,7 @@ public OrmQueryProperties(String path, Set included) { this.path = path; this.parentPath = SplitName.parent(path); this.allProperties = other.allProperties; - this.included = other.included; + this.included = (other.included == null) ? null : new LinkedHashSet<>(other.included); this.fetchConfig = fetchConfig; this.cache = fetchConfig.isCache(); } From 582f97abeea978eecb666f00744643fde4d3124b Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Thu, 3 Jul 2025 16:39:56 +0200 Subject: [PATCH 4/6] fix tests --- .../xtest/internal/server/grammer/EqlParserTest.java | 2 +- .../org/tests/lazyforeignkeys/TestLazyForeignKeys.java | 2 +- .../src/test/java/org/tests/merge/TestMergeCustomer.java | 8 ++++---- .../src/test/java/org/tests/query/TestSubQuery.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ebean-test/src/test/java/io/ebean/xtest/internal/server/grammer/EqlParserTest.java b/ebean-test/src/test/java/io/ebean/xtest/internal/server/grammer/EqlParserTest.java index 16b64b7d9f..6f48d51464 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/internal/server/grammer/EqlParserTest.java +++ b/ebean-test/src/test/java/io/ebean/xtest/internal/server/grammer/EqlParserTest.java @@ -629,7 +629,7 @@ void select_agg_sum() { List details = query.findList(); assertThat(details).isNotEmpty(); - assertSql(query).contains("select sum(t0.order_qty), t1.id from o_order_detail t0 join o_order t1 on t1.id = t0.order_id group by t1.id"); + assertSql(query).contains("select sum(t0.order_qty), t0.order_id from o_order_detail t0 group by t0.order_id"); } @Test diff --git a/ebean-test/src/test/java/org/tests/lazyforeignkeys/TestLazyForeignKeys.java b/ebean-test/src/test/java/org/tests/lazyforeignkeys/TestLazyForeignKeys.java index f88f148297..97f861ca20 100644 --- a/ebean-test/src/test/java/org/tests/lazyforeignkeys/TestLazyForeignKeys.java +++ b/ebean-test/src/test/java/org/tests/lazyforeignkeys/TestLazyForeignKeys.java @@ -77,7 +77,7 @@ public void testFindListWithSelect() { List list = query.findList(); assertEquals(1, list.size()); - assertSql(query).contains("t0.id, t0.attr1, t0.id1, t0.id2, t1.id, t2.id"); + assertSql(query).isEqualTo("select t0.id, t0.attr1, t0.id1, t0.id2, t1.id, t2.id from main_entity_relation t0 left join main_entity t1 on t1.id = t0.id1 left join main_entity t2 on t2.id = t0.id2"); MainEntityRelation rel1 = list.get(0); assertEquals("ent1", rel1.getEntity1().getId()); diff --git a/ebean-test/src/test/java/org/tests/merge/TestMergeCustomer.java b/ebean-test/src/test/java/org/tests/merge/TestMergeCustomer.java index e18f2983ea..197c3d60ba 100644 --- a/ebean-test/src/test/java/org/tests/merge/TestMergeCustomer.java +++ b/ebean-test/src/test/java/org/tests/merge/TestMergeCustomer.java @@ -93,7 +93,7 @@ public void customerWithAddresses_setClientGeneratedIds_expect_selectAndUpdate() List sql = LoggedSql.stop(); assertThat(sql).hasSize(6); - assertSql(sql.get(0)).contains("select t0.id, t2.id, t1.id from mcustomer t0 left join maddress t2 on t2.id = t0.shipping_address_id left join maddress t1 on t1.id = t0.billing_address_id where t0.id = ?"); + assertSql(sql.get(0)).contains("select t0.id, t0.billing_address_id, t0.shipping_address_id from mcustomer t0 where t0.id = ?"); assertSql(sql.get(1)).contains("update maddress set street=?, city=?, version=? where id=? and version=?"); assertSqlBind(sql, 2, 3); assertThat(sql.get(5)).contains("update mcustomer set name=?, version=?, shipping_address_id=?, billing_address_id=? where id=? and version=?"); @@ -122,7 +122,7 @@ public void customerWithAddresses_newAddress_setClientGeneratedIds_expect_insert List sql = LoggedSql.stop(); assertThat(sql).hasSize(8); - assertSql(sql.get(0)).contains("select t0.id, t2.id, t1.id from mcustomer t0 left join maddress t2 on t2.id = t0.shipping_address_id left join maddress t1 on t1.id = t0.billing_address_id where t0.id = ?"); + assertSql(sql.get(0)).contains("select t0.id, t0.billing_address_id, t0.shipping_address_id from mcustomer t0 where t0.id = ?"); assertSql(sql.get(1)).contains("insert into maddress (id, street, city, version) values (?,?,?,?)"); assertSqlBind(sql.get(2)); assertThat(sql.get(4)).contains("update maddress set street=?, city=?, version=? where id=? and version=?"); @@ -155,7 +155,7 @@ public void customerWithAddresses_newAddressWithId_setClientGeneratedIds_expect_ List sql = LoggedSql.stop(); assertThat(sql).hasSize(9); - assertSql(sql.get(0)).contains("select t0.id, t2.id, t1.id from mcustomer t0 left join maddress t2 on t2.id = t0.shipping_address_id left join maddress t1 on t1.id = t0.billing_address_id where t0.id = ?"); + assertSql(sql.get(0)).contains("select t0.id, t0.billing_address_id, t0.shipping_address_id from mcustomer t0 where t0.id = ?"); // Additional check to see if the address with the unknown UUID is 'insert' or 'update' assertSql(sql.get(1)).contains("select t0.id from maddress t0 where t0.id = ?"); @@ -317,7 +317,7 @@ public void fullMonty() { List sql = LoggedSql.stop(); if (isPersistBatchOnCascade()) { - assertSql(sql.get(0)).contains("select t0.id, t3.id, t1.id, t2.id from mcustomer t0 left join maddress t3 on t3.id = t0.shipping_address_id left join maddress t1 on t1.id = t0.billing_address_id left join mcontact t2 on t2.customer_id = t0.id where t0.id = ?"); + assertSql(sql.get(0)).contains("select t0.id, t0.shipping_address_id, t0.billing_address_id, t1.id from mcustomer t0 left join mcontact t1 on t1.customer_id = t0.id where t0.id = ? order by t0.id"); if (isH2() || isHana()) { // with nested OneToMany .. we need a second query to read the contact message ids assertSql(sql.get(1)).contains("select t0.contact_id, t0.id from mcontact_message t0 where (t0.contact_id) in (?,?,?,?,?,?,?,?,?,?)"); diff --git a/ebean-test/src/test/java/org/tests/query/TestSubQuery.java b/ebean-test/src/test/java/org/tests/query/TestSubQuery.java index a5af4828c5..ba06bdae80 100644 --- a/ebean-test/src/test/java/org/tests/query/TestSubQuery.java +++ b/ebean-test/src/test/java/org/tests/query/TestSubQuery.java @@ -75,9 +75,9 @@ public void test_IsInWithFetchSubQuery1() { Query debugSq = sq.copy(); debugSq.findSingleAttribute(); if (isPostgresCompatible()) { - 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 = any(?)"); + assertThat(debugSq.getGeneratedSql()).isEqualTo("select t0.order_id from o_order_detail t0 where t0.product_id = any(?)"); } else { - assertSql(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 (?)"); + assertSql(debugSq.getGeneratedSql()).isEqualTo("select t0.order_id from o_order_detail t0 where t0.product_id in (?)"); } Query query = DB.find(Order.class).select("shipDate").where().isIn("id", sq).query(); From d4bcb98b33ea512d1a3f2f3a40707f2e190aa6af Mon Sep 17 00:00:00 2001 From: Noemi Praml Date: Wed, 9 Jul 2025 10:24:28 +0200 Subject: [PATCH 5/6] add failing test --- .../tests/query/joins/TestQueryJoinOnFormula.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ebean-test/src/test/java/org/tests/query/joins/TestQueryJoinOnFormula.java b/ebean-test/src/test/java/org/tests/query/joins/TestQueryJoinOnFormula.java index e757bbce55..ca6e76c6e6 100644 --- a/ebean-test/src/test/java/org/tests/query/joins/TestQueryJoinOnFormula.java +++ b/ebean-test/src/test/java/org/tests/query/joins/TestQueryJoinOnFormula.java @@ -333,6 +333,20 @@ public void test_ChildPersonParentFindCount() { assertThat(loggedSql.get(0)).contains("where coalesce(f2.child_age, 0) = ?"); } + @Test + public void test_fetch_only() { + + LoggedSql.start(); + + DB.find(ChildPerson.class).select("name").fetch("parent.effectiveBean").findList(); + + List loggedSql = LoggedSql.stop(); + assertThat(loggedSql.get(0)).contains("from child_person t0 " + + "left join parent_person t1 on t1.identifier = t0.parent_identifier " + + "left join grand_parent_person j1 on j1.identifier = t1.parent_identifier " + + "left join e_basic t2 on t2.id = coalesce(t1.some_bean_id, j1.some_bean_id)"); + } + @Test public void test_softRef() { From e045e5aaa287e23b8ddea25847cdba0c29c4ed2d Mon Sep 17 00:00:00 2001 From: Noemi Praml Date: Tue, 8 Jul 2025 09:01:25 +0200 Subject: [PATCH 6/6] fix parentPath --- .../server/querydefn/OrmQueryDetail.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) 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 1841e86651..0982035a4c 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 @@ -331,29 +331,35 @@ private void sortFetchPaths(BeanDescriptor d, OrmQueryProperties p, LinkedHas /** * After sorting, we try to convert id only fetches of a bean into a select of the parent bean. */ - private void convertIdFetches(BeanDescriptor desc) { + private void convertIdFetches(BeanDescriptor desc, Set nonRemovable) { String[] paths = fetchPaths.keySet().toArray(new String[0]); int i = paths.length; // iterate backwards - otherwise we might detect id only fetches, which are later converted while (i-- > 0) { String path = paths[i]; ElPropertyDeploy el = desc.elPropertyDeploy(path); - if (el == null) { + OrmQueryProperties prop = fetchPaths.get(path); + if (nonRemovable.contains(path)) { + // do not remove + } else if (el == null) { throw new PersistenceException("Invalid fetch path " + path + " from " + desc.fullName()); } else if (el.beanProperty() instanceof BeanPropertyAssocOne) { BeanPropertyAssocOne assoc = (BeanPropertyAssocOne) el.beanProperty(); if (assoc.hasForeignKeyConstraint()) { - OrmQueryProperties prop = fetchPaths.get(path); // check, if we have exactly the ID selected and convert these fetches to a select of the parent bean if (prop.includesExactly(assoc.descriptor().idName())) { OrmQueryProperties parentProp = prop.getParentPath() == null ? baseProps : fetchPaths.get(prop.getParentPath()); if (parentProp.hasProperties()) { parentProp.addInclude(assoc.name()); fetchPaths.remove(path); + prop = null; } } } } + if (prop != null && prop.getParentPath() != null) { + nonRemovable.add(prop.getParentPath()); + } } } @@ -375,7 +381,7 @@ SpiQueryManyJoin markQueryJoins(BeanDescriptor beanDescriptor, String lazyLoa sortFetchPaths(beanDescriptor, addIds); - convertIdFetches(beanDescriptor); + convertIdFetches(beanDescriptor, new HashSet<>()); List pairs = sortByFetchPreference(beanDescriptor); for (FetchEntry pair : pairs) {