diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index 08d873a4f..d6c6d722a 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -2576,7 +2576,7 @@ public class BeanDescriptor implements BeanType, STreeType { */ private STreeProperty findSqlTreeFormula(String formula, String path) { String key = formula + "-" + path; - return dynamicProperty.computeIfAbsent(key, (fullKey) -> new FormulaPropertyPath(this, formula, path).build()); + return dynamicProperty.computeIfAbsent(key, (fullKey) -> FormulaPropertyPath.create(this, formula, path)); } /** diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java index a5ffe9413..1e23e340b 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -23,6 +23,7 @@ import io.ebeaninternal.server.query.SqlBeanLoad; import io.ebeaninternal.server.query.SqlJoinType; import io.ebeaninternal.server.type.DataReader; import io.ebeaninternal.server.type.ScalarDataReader; +import io.ebeaninternal.server.type.ScalarType; import javax.persistence.PersistenceException; import java.io.IOException; @@ -465,6 +466,10 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc implements STr return targetDescriptor.getIdProperty(); } + public ScalarType getIdScalarType() { + return targetDescriptor.getIdProperty().scalarType; + } + /** * Return the Id values from the given bean. */ diff --git a/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyAggregationFormula.java b/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyAggregationFormula.java index 394266489..5a584f15a 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyAggregationFormula.java +++ b/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyAggregationFormula.java @@ -16,7 +16,7 @@ class DynamicPropertyAggregationFormula extends DynamicPropertyBase { private final boolean aggregate; - private final BeanProperty asTarget; + final BeanProperty asTarget; private final String alias; diff --git a/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyAggregationFormulaMTO.java b/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyAggregationFormulaMTO.java new file mode 100644 index 000000000..d539dfef5 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyAggregationFormulaMTO.java @@ -0,0 +1,28 @@ +package io.ebeaninternal.server.deploy; + +import io.ebeaninternal.server.query.SqlBeanLoad; + +public class DynamicPropertyAggregationFormulaMTO extends DynamicPropertyAggregationFormula { + + private final BeanPropertyAssocOne prop; + + DynamicPropertyAggregationFormulaMTO(BeanPropertyAssocOne prop, String name, String parsedFormula, boolean aggregate, BeanProperty asTarget, String alias) { + super(name, prop.getIdScalarType(), parsedFormula, aggregate, asTarget, alias); + this.prop = prop; + } + + @Override + public void load(SqlBeanLoad sqlBeanLoad) { + Object value; + try { + value = prop.read(sqlBeanLoad.ctx()); + } catch (Exception e) { + sqlBeanLoad.ctx().handleLoadError(fullName, e); + return; + } + if (asTarget != null) { + sqlBeanLoad.load(asTarget, value); + } + } + +} diff --git a/src/main/java/io/ebeaninternal/server/deploy/FormulaPropertyPath.java b/src/main/java/io/ebeaninternal/server/deploy/FormulaPropertyPath.java index 20926ceb0..183e17ffd 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/FormulaPropertyPath.java +++ b/src/main/java/io/ebeaninternal/server/deploy/FormulaPropertyPath.java @@ -6,7 +6,7 @@ import io.ebeaninternal.server.type.ScalarType; import java.sql.Types; -class FormulaPropertyPath { +final class FormulaPropertyPath { private static final String[] AGG_FUNCTIONS = {"count", "max", "min", "avg", "sum"}; @@ -20,17 +20,22 @@ class FormulaPropertyPath { private final String internalExpression; - private final String path; + private final ElPropertyDeploy firstProp; + + private final String parsedAggregation; private boolean countDistinct; private String cast; private String alias; + static STreeProperty create(BeanDescriptor descriptor, String formula, String path) { + return new FormulaPropertyPath(descriptor, formula, path).build(); + } + FormulaPropertyPath(BeanDescriptor descriptor, String formula, String path) { this.descriptor = descriptor; this.formula = formula; - this.path = path; int openBracket = formula.indexOf('('); int closeBracket = formula.lastIndexOf(')'); @@ -38,13 +43,21 @@ class FormulaPropertyPath { throw new IllegalStateException("Unable to parse formula [" + formula + "]"); } outerFunction = formula.substring(0, openBracket).trim(); - internalExpression = trimDistinct(formula.substring(openBracket+1, closeBracket)); + internalExpression = trimDistinct(formula.substring(openBracket + 1, closeBracket)); - if (closeBracket < formula.length() -1) { + if (closeBracket < formula.length() - 1) { // ::CastType as foo - String suffix = formula.substring(closeBracket+1).trim(); - parseSuffix(suffix); + parseSuffix(formula.substring(closeBracket + 1).trim()); } + + DeployPropertyParser parser = descriptor.parser().setCatchFirst(true); + String parsed = parser.parse(internalExpression); + if (path != null) { + // fetch("machineStats", "sum(hours), sum(totalKms)") + parsed = parsed.replace("${}", "${" + path + "}"); + } + this.parsedAggregation = buildFormula(parsed); + this.firstProp = parser.getFirstProp(); } private void parseSuffix(String suffix) { @@ -93,42 +106,50 @@ class FormulaPropertyPath { STreeProperty build() { - DeployPropertyParser parser = descriptor.parser().setCatchFirst(true); - - String parsed = parser.parse(internalExpression); - if (path != null) { - // fetch("machineStats", "sum(hours), sum(totalKms)") - parsed = parsed.replace("${}", "${" + path + "}"); - } - - ElPropertyDeploy firstProp = parser.getFirstProp(); - - ScalarType scalarType; if (cast != null) { - scalarType = descriptor.getScalarType(cast); + ScalarType scalarType = descriptor.getScalarType(cast); if (scalarType == null) { - throw new IllegalStateException("Unable to find scalarType for cast of ["+cast+"] on formula [" + formula + "] for type " + descriptor); - } - - } else if (isCount()) { - scalarType = descriptor.getScalarType(Types.BIGINT); - - } else if (isConcat()) { - scalarType = descriptor.getScalarType(Types.VARCHAR); - - } else { - // determine scalarType based on first property found by parser - if (firstProp != null) { - scalarType = firstProp.getBeanProperty().getScalarType(); - } else { - throw new IllegalStateException("unable to determine scalarType of formula [" + formula + "] for type " + descriptor + " - maybe use a cast like ::String ?"); + throw new IllegalStateException("Unable to find scalarType for cast of [" + cast + "] on formula [" + formula + "] for type " + descriptor); } + return create(scalarType); + } + if (isCount()) { + return create(descriptor.getScalarType(Types.BIGINT)); + } + if (isConcat()) { + return create(descriptor.getScalarType(Types.VARCHAR)); + } + if (firstProp == null) { + throw new IllegalStateException("unable to determine scalarType of formula [" + formula + "] for type " + descriptor + " - maybe use a cast like ::String ?"); } - String logicalName = (alias == null) ? internalExpression : alias; - BeanProperty targetProperty = descriptor._findBeanProperty(logicalName); - String parsedAggregation = buildFormula(parsed); - return new DynamicPropertyAggregationFormula(logicalName, scalarType, parsedAggregation, isAggregate(), targetProperty, alias); + // determine scalarType based on first property found by parser + final BeanProperty property = firstProp.getBeanProperty(); + if (!property.isAssocId()) { + return create(property.getScalarType()); + } else { + return createManyToOne(property); + } + } + + private DynamicPropertyAggregationFormula create(ScalarType scalarType) { + + String logicalName = logicalName(); + return new DynamicPropertyAggregationFormula(logicalName, scalarType, parsedAggregation, isAggregate(), target(logicalName), alias); + } + + private DynamicPropertyAggregationFormula createManyToOne(BeanProperty property) { + + String logicalName = logicalName(); + return new DynamicPropertyAggregationFormulaMTO((BeanPropertyAssocOne) property, logicalName, parsedAggregation, isAggregate(), target(logicalName), alias); + } + + private BeanProperty target(String logicalName) { + return descriptor._findBeanProperty(logicalName); + } + + private String logicalName() { + return (alias == null) ? internalExpression : alias; } private boolean isAggregate() { diff --git a/src/main/java/io/ebeaninternal/server/query/STreePropertyAssocOne.java b/src/main/java/io/ebeaninternal/server/query/STreePropertyAssocOne.java index c7d9c8f85..d53879fce 100644 --- a/src/main/java/io/ebeaninternal/server/query/STreePropertyAssocOne.java +++ b/src/main/java/io/ebeaninternal/server/query/STreePropertyAssocOne.java @@ -23,5 +23,5 @@ public interface STreePropertyAssocOne extends STreePropertyAssoc { /** * Return the property value as an entity bean from the parent. */ - public EntityBean getValueAsEntityBean(EntityBean parentBean); + EntityBean getValueAsEntityBean(EntityBean parentBean); } diff --git a/src/test/java/io/ebeaninternal/server/deploy/FormulaPropertyPathTest.java b/src/test/java/io/ebeaninternal/server/deploy/FormulaPropertyPathTest.java index 92b839fbb..681e82bf1 100644 --- a/src/test/java/io/ebeaninternal/server/deploy/FormulaPropertyPathTest.java +++ b/src/test/java/io/ebeaninternal/server/deploy/FormulaPropertyPathTest.java @@ -76,8 +76,6 @@ public class FormulaPropertyPathTest extends BaseTestCase { assertThat(propertyPath.alias()).isNull(); } - STreeProperty treeProperty = propertyPath.build(); - - assertThat(treeProperty).isNotNull(); + assertThat(propertyPath.build()).isNotNull(); } } diff --git a/src/test/java/org/tests/aggregateformula/TestAggregateFormula.java b/src/test/java/org/tests/aggregateformula/TestAggregateFormula.java index 4605e283e..471171e79 100644 --- a/src/test/java/org/tests/aggregateformula/TestAggregateFormula.java +++ b/src/test/java/org/tests/aggregateformula/TestAggregateFormula.java @@ -14,6 +14,58 @@ import static org.assertj.core.api.Assertions.assertThat; public class TestAggregateFormula extends BaseTestCase { + @Test + public void minOnManyToOne() { + + ResetBasicData.reset(); + + LoggedSqlCollector.start(); + + List contacts = Ebean.find(Contact.class) + .select("lastName, min(customer)") + .findList(); + + List sql = LoggedSqlCollector.stop(); + assertThat(sql.get(0)).contains("select t0.last_name, min(t0.customer_id) from contact t0 group by t0.last_name"); + + assertThat(contacts).isNotEmpty(); + + Contact contact = contacts.get(0); + assertThat(contact.getLastName()).isNotNull(); + assertThat(contact.getCustomer()).isNotNull(); + assertThat(contact.getCustomer().getId()).isNotNull(); + } + +// @Test +// public void minOnManyToOne_withFetchQuery() { +// +// ResetBasicData.reset(); +// +// LoggedSqlCollector.start(); +// +// List contacts = Ebean.find(Contact.class) +// .select("lastName, min(customer)") +// .fetchQuery("customer", "name, status") +// //.fetch("customer.billingAddress", "city, country") +// .findList(); +// +// for (Contact contact : contacts) { +// contact.getCustomer().getName(); +// } +// +// List sql = LoggedSqlCollector.stop(); +// assertThat(sql).hasSize(2); +// assertThat(sql.get(0)).contains("select t0.last_name, min(t0.customer_id) from contact t0 group by t0.last_name"); +// assertThat(sql.get(1)).contains("select t0.id, t0.name, t0.status from o_customer t0 where"); +// +// assertThat(contacts).isNotEmpty(); +// +// Contact contact = contacts.get(0); +// assertThat(contact.getLastName()).isNotNull(); +// assertThat(contact.getCustomer()).isNotNull(); +// assertThat(contact.getCustomer().getId()).isNotNull(); +// } + @Test public void sum_withoutAlias() {