Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
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;
import io.ebeaninternal.server.el.ElPropertyValue;

import jakarta.persistence.PersistenceException;

import java.io.Serializable;
import java.util.*;

Expand Down Expand Up @@ -326,6 +328,41 @@ 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, Set<String> 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);
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()) {
// 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());
}
}
}

/**
* Mark 'fetch joins' to 'many' properties over to 'query joins' where needed.
*
Expand All @@ -343,6 +380,8 @@ SpiQueryManyJoin markQueryJoins(BeanDescriptor<?> beanDescriptor, String lazyLoa
boolean fetchJoinFirstMany = allowOne;

sortFetchPaths(beanDescriptor, addIds);

convertIdFetches(beanDescriptor, new HashSet<>());
List<FetchEntry> pairs = sortByFetchPreference(beanDescriptor);

for (FetchEntry pair : pairs) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -104,7 +104,7 @@ public OrmQueryProperties(String path, Set<String> included) {
OrmQueryProperties(String path, Set<String> 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();
Expand All @@ -114,7 +114,7 @@ public OrmQueryProperties(String path, Set<String> 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();
}
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ void select_agg_sum() {
List<OrderDetail> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public void testFindListWithSelect() {
List<MainEntityRelation> 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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public void customerWithAddresses_setClientGeneratedIds_expect_selectAndUpdate()

List<String> 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=?");
Expand Down Expand Up @@ -122,7 +122,7 @@ public void customerWithAddresses_newAddress_setClientGeneratedIds_expect_insert

List<String> 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=?");
Expand Down Expand Up @@ -155,7 +155,7 @@ public void customerWithAddresses_newAddressWithId_setClientGeneratedIds_expect_

List<String> 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 = ?");
Expand Down Expand Up @@ -317,7 +317,7 @@ public void fullMonty() {
List<String> 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 (?,?,?,?,?,?,?,?,?,?)");
Expand Down
57 changes: 57 additions & 0 deletions ebean-test/src/test/java/org/tests/query/TestFetchIdOnly.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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 java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

public class TestFetchIdOnly extends BaseTestCase {

@Test
void test_withFetchPath() {
PathProperties root = PathProperties.parse("status,customer(id)");
LoggedSql.start();
Query<Order> query = DB.find(Order.class).apply(root);
query.findList();
List<String> 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() {
LoggedSql.start();
Query<Order> query = DB.find(Order.class).select("status, customer");
query.findList();
List<String> 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() {
LoggedSql.start();
Query<Order> query = DB.find(Order.class).select("status").fetch("customer", "id");
query.findList();
List<String> 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_withChildFetch() {
LoggedSql.start();
Query<Order> query = DB.find(Order.class).select("status").fetch("customer.shippingAddress", "id");
query.findList();
List<String> 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;");
}
}
4 changes: 2 additions & 2 deletions ebean-test/src/test/java/org/tests/query/TestSubQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ public void test_IsInWithFetchSubQuery1() {
Query<OrderDetail> 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<Order> query = DB.find(Order.class).select("shipDate").where().isIn("id", sq).query();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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() {

Expand Down