Add support for dynamic formula with includes

e.g.

DB.find(ChildPerson.class)
  .select("name, coalesce(someBean, parent.someBean) as effectiveBean") // include parent
  .findList();
This commit is contained in:
Rob Bygrave
2023-03-25 15:49:17 +13:00
parent 7feb193e95
commit 55d7eb55d2
5 changed files with 78 additions and 6 deletions
@@ -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<String> 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<String> 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<String> predicateIncludes) {
predicateIncludes.addAll(includes);
}
@Override
public void load(SqlBeanLoad sqlBeanLoad) {
Object value;
@@ -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<String> 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) {
@@ -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<Object> {
* 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<Object> {
*/
void appendFrom(DbSqlContext ctx, SqlJoinType joinType, String manyWhere);
default void extraIncludes(Set<String> predicateIncludes) {
// do nothing
}
}
@@ -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());
}
}
}
}
@@ -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<ChildPerson> children = DB.find(ChildPerson.class)
.select("name, coalesce(someBean, parent.someBean) as effectiveBean") // include parent
.findList();
Map<String,EBasic> 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<String> 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<ChildPerson> children = DB.find(ChildPerson.class)
.select("name, coalesce(someBean, parent.someBean) as effectiveBean") // include parent
.fetch("parent", "name, familyName")
.findList();
Map<String,EBasic> 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<String> 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();