diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyAggregationFormulaMTO.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyAggregationFormulaMTO.java index 2b7127d8c..463a114f2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyAggregationFormulaMTO.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyAggregationFormulaMTO.java @@ -2,14 +2,18 @@ package io.ebeaninternal.server.deploy; import io.ebeaninternal.server.query.SqlBeanLoad; +import java.util.Set; + @SuppressWarnings("rawtypes") public final class DynamicPropertyAggregationFormulaMTO extends DynamicPropertyAggregationFormula { private final BeanPropertyAssocOne prop; + private final Set includes; - DynamicPropertyAggregationFormulaMTO(BeanPropertyAssocOne prop, String name, String parsedFormula, boolean aggregate, BeanProperty asTarget, String alias) { + DynamicPropertyAggregationFormulaMTO(BeanPropertyAssocOne prop, String name, String parsedFormula, boolean aggregate, BeanProperty asTarget, String alias, Set includes) { super(name, prop.idScalarType(), parsedFormula, aggregate, asTarget, alias); this.prop = prop; + this.includes = includes; } @Override @@ -17,6 +21,11 @@ public final class DynamicPropertyAggregationFormulaMTO extends DynamicPropertyA return true; } + @Override + public void extraIncludes(Set predicateIncludes) { + predicateIncludes.addAll(includes); + } + @Override public void load(SqlBeanLoad sqlBeanLoad) { Object value; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/FormulaPropertyPath.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/FormulaPropertyPath.java index 40db63c8d..197895557 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/FormulaPropertyPath.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/FormulaPropertyPath.java @@ -5,6 +5,7 @@ import io.ebeaninternal.server.el.ElPropertyDeploy; import io.ebeaninternal.server.query.STreeProperty; import java.sql.Types; +import java.util.Set; final class FormulaPropertyPath { @@ -18,6 +19,7 @@ final class FormulaPropertyPath { private final String internalExpression; private final ElPropertyDeploy firstProp; private final String parsedAggregation; + private final Set includes; private boolean countDistinct; private String cast; private String alias; @@ -47,6 +49,7 @@ final class FormulaPropertyPath { // fetch("machineStats", "sum(hours), sum(totalKms)") parsed = parsed.replace("${}", "${" + path + "}"); } + this.includes = parser.includes(); this.parsedAggregation = buildFormula(parsed); this.firstProp = parser.firstProp(); } @@ -129,7 +132,7 @@ final class FormulaPropertyPath { @SuppressWarnings("rawtypes") private DynamicPropertyAggregationFormula createManyToOne(BeanProperty property) { String logicalName = logicalName(); - return new DynamicPropertyAggregationFormulaMTO((BeanPropertyAssocOne) property, logicalName, parsedAggregation, isAggregate(), target(logicalName), alias); + return new DynamicPropertyAggregationFormulaMTO((BeanPropertyAssocOne) property, logicalName, parsedAggregation, isAggregate(), target(logicalName), alias, includes); } private BeanProperty target(String logicalName) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/STreeProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/STreeProperty.java index c13126858..df7ee17d1 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/STreeProperty.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/STreeProperty.java @@ -6,6 +6,7 @@ import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; import java.util.List; +import java.util.Set; /** * A property in the SQL Tree. @@ -28,7 +29,7 @@ public interface STreeProperty extends ScalarDataReader { * Return true if the property is the Id. */ boolean isId(); - + /** * Returns true, if this is a lob property from db-perspective. */ @@ -96,4 +97,7 @@ public interface STreeProperty extends ScalarDataReader { */ void appendFrom(DbSqlContext ctx, SqlJoinType joinType, String manyWhere); + default void extraIncludes(Set predicateIncludes) { + // do nothing + } } 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 36647fda4..d9bd82e42 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 @@ -406,7 +406,7 @@ public final class SqlTreeBuilder { } int basePos = propName.indexOf('.'); - if (basePos > -1) { + if (basePos > -1 && !propName.contains(" as ")) { // property on an embedded bean. Embedded beans do not yet // support being partially populated so we include the // 'base' property and make sure we only do that once @@ -457,6 +457,9 @@ public final class SqlTreeBuilder { } } else { selectProps.add(p); + if (p.isAggregationManyToOne()) { + p.extraIncludes(predicates.predicateIncludes()); + } } } } diff --git a/ebean-test/src/test/java/org/tests/family/TestInheritance.java b/ebean-test/src/test/java/org/tests/family/TestInheritance.java index 58e3d81a3..937cd195f 100644 --- a/ebean-test/src/test/java/org/tests/family/TestInheritance.java +++ b/ebean-test/src/test/java/org/tests/family/TestInheritance.java @@ -1,9 +1,10 @@ package org.tests.family; -import io.ebean.xtest.BaseTestCase; import io.ebean.DB; -import io.ebean.xtest.IgnorePlatform; import io.ebean.annotation.Platform; +import io.ebean.test.LoggedSql; +import io.ebean.xtest.BaseTestCase; +import io.ebean.xtest.IgnorePlatform; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; @@ -13,6 +14,11 @@ import org.tests.model.family.ChildPerson; import org.tests.model.family.GrandParentPerson; import org.tests.model.family.ParentPerson; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; @@ -97,6 +103,10 @@ public class TestInheritance extends BaseTestCase { server().save(grandparent1); + + testMyDynamicFormulaWithIncludes(); + testMyDynamicFormulaWithIncludesAndFetchJoin(); + // Test setup complete, so retrieve bean from db //grandparent1 = server().find(GrandParentPerson.class).setId(grandparent1.getIdentifier()).where() // .in("effectiveBean.id",2,null) // geht nicht! @@ -222,6 +232,49 @@ public class TestInheritance extends BaseTestCase { DB.find(EBasic.class).delete(); } + private void testMyDynamicFormulaWithIncludes() { + LoggedSql.start(); + List children = DB.find(ChildPerson.class) + .select("name, coalesce(someBean, parent.someBean) as effectiveBean") // include parent + .findList(); + + Map childToBasic = new HashMap<>(); + for (ChildPerson child : children) { + childToBasic.put(child.getName(), child.getEffectiveBean()); + } + + assertThat(childToBasic.get("Fred").getName()).isEqualTo("An other Bean"); + assertThat(childToBasic.get("Julia").getName()).isEqualTo("A Bean"); + assertThat(childToBasic.get("Roland")).isNull(); + List sql = LoggedSql.stop(); + + assertThat(sql).hasSize(2); + assertThat(sql.get(0)).contains("select t0.identifier, t0.name, coalesce(t0.some_bean_id, t1.some_bean_id) effectiveBean from child_person t0 left join parent_person t1 on t1.identifier = t0.parent_identifier"); + assertThat(sql.get(1)).contains("select t0.id, t0.status, t0.name, t0.description, t0.some_date from e_basic t0 where t0.id"); + } + + private void testMyDynamicFormulaWithIncludesAndFetchJoin() { + LoggedSql.start(); + List children = DB.find(ChildPerson.class) + .select("name, coalesce(someBean, parent.someBean) as effectiveBean") // include parent + .fetch("parent", "name, familyName") + .findList(); + + Map childToBasic = new HashMap<>(); + for (ChildPerson child : children) { + childToBasic.put(child.getName(), child.getEffectiveBean()); + } + + assertThat(childToBasic.get("Fred").getName()).isEqualTo("An other Bean"); + assertThat(childToBasic.get("Julia").getName()).isEqualTo("A Bean"); + assertThat(childToBasic.get("Roland")).isNull(); + List sql = LoggedSql.stop(); + + assertThat(sql).hasSize(2); + assertThat(sql.get(0)).contains("select t0.identifier, t0.name, coalesce(t0.some_bean_id, t1.some_bean_id) effectiveBean, t1.identifier, t1.name, t1.family_name from child_person t0 left join parent_person t1 on t1.identifier = t0.parent_identifier"); + assertThat(sql.get(1)).contains("select t0.id, t0.status, t0.name, t0.description, t0.some_date from e_basic t0 where t0.id"); + } + @Test public void testFindCount() { EBasic basicA = new EBasic();