diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index 38d979e14..8d8c17d7a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -85,6 +85,7 @@ import javax.persistence.PersistenceException; import java.io.IOException; import java.lang.reflect.Modifier; import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; import java.sql.Types; import java.util.ArrayList; import java.util.Collection; @@ -868,7 +869,9 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { public void initialiseFkeys() { for (BeanPropertyAssocOne aPropertiesOneImported : propertiesOneImported) { - aPropertiesOneImported.addFkey(); + if (!aPropertiesOneImported.isFormula()) { + aPropertiesOneImported.addFkey(); + } } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java index 2e85c8377..f41e5839d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java @@ -330,7 +330,7 @@ public abstract class BeanPropertyAssoc extends BeanProperty { if (descriptor.isRawSqlBased()) { String dbColumn = owner.getDbColumn(); - return new ImportedIdSimple(owner, dbColumn, idProp, 0); + return new ImportedIdSimple(owner, dbColumn, null, idProp, 0); } TableJoinColumn[] cols = join.columns(); @@ -373,16 +373,17 @@ public abstract class BeanPropertyAssoc extends BeanProperty { String matchColumn = col.getForeignDbColumn(); String localColumn = col.getLocalDbColumn(); + String localSqlFormula = col.getLocalSqlFormula(); for (int j = 0; j < props.length; j++) { if (props[j].getDbColumn().equalsIgnoreCase(matchColumn)) { - return new ImportedIdSimple(owner, localColumn, props[j], j); + return new ImportedIdSimple(owner, localColumn, localSqlFormula, props[j], j); } } for (int j = 0; j < others.length; j++) { if (others[j].getDbColumn().equalsIgnoreCase(matchColumn)) { - return new ImportedIdSimple(owner, localColumn, others[j], j + props.length); + return new ImportedIdSimple(owner, localColumn, localSqlFormula, others[j], j + props.length); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java index 66b57efed..ef723bd65 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -570,6 +570,9 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) { if (!isTransient) { localHelp.appendFrom(ctx, joinType); + if (sqlFormulaJoin != null) { + ctx.appendFormulaJoin(sqlFormulaJoin, joinType); + } } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanTable.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanTable.java index 6e4a0f4ed..b19123965 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanTable.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanTable.java @@ -76,7 +76,7 @@ public class BeanTable { return beanType; } - public void createJoinColumn(String foreignKeyPrefix, DeployTableJoin join, boolean reverse) { + public void createJoinColumn(String foreignKeyPrefix, DeployTableJoin join, boolean reverse, String sqlFormulaSelect) { boolean complexKey = false; BeanProperty[] props = idProperties; @@ -104,8 +104,12 @@ public class BeanTable { logger.debug(msg); fk = lc; } + if (sqlFormulaSelect != null) { + fk = sqlFormulaSelect; + } DeployTableJoinColumn joinCol = new DeployTableJoinColumn(lc, fk); + joinCol.setForeignSqlFormula(sqlFormulaSelect); if (reverse) { joinCol = joinCol.reverse(); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumn.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumn.java index d64679402..4c5314a72 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumn.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumn.java @@ -13,14 +13,19 @@ public class TableJoinColumn { */ private final String localDbColumn; + private final String localSqlFormula; + /** * The foreign database column name. */ private final String foreignDbColumn; + + private final String foreignSqlFormula; private final boolean insertable; private final boolean updateable; + /** * Hash for including in a query plan @@ -33,6 +38,9 @@ public class TableJoinColumn { public TableJoinColumn(DeployTableJoinColumn deploy) { this.localDbColumn = InternString.intern(deploy.getLocalDbColumn()); this.foreignDbColumn = InternString.intern(deploy.getForeignDbColumn()); + this.localSqlFormula = InternString.intern(deploy.getLocalSqlFormula()); + this.foreignSqlFormula = InternString.intern(deploy.getForeignSqlFormula()); + this.insertable = deploy.isInsertable(); this.updateable = deploy.isUpdateable(); this.queryHash = hash(); @@ -41,8 +49,11 @@ public class TableJoinColumn { int hash() { int result = localDbColumn != null ? localDbColumn.hashCode() : 0; result = 92821 * result + (foreignDbColumn != null ? foreignDbColumn.hashCode() : 0); + result = 92821 * result + (localSqlFormula != null ? localSqlFormula.hashCode() : 0); + result = 92821 * result + (foreignSqlFormula != null ? foreignSqlFormula.hashCode() : 0); result = 92821 * result + (insertable ? 1 : 0); result = 92821 * result + (updateable ? 1 : 0); + return result; } @@ -64,7 +75,8 @@ public class TableJoinColumn { } public String toString() { - return localDbColumn + " = " + foreignDbColumn; + return (localSqlFormula == null ? localDbColumn : localSqlFormula) + " = " + + (foreignSqlFormula == null ? foreignDbColumn : foreignSqlFormula); } /** @@ -101,4 +113,13 @@ public class TableJoinColumn { public boolean isUpdateable() { return updateable; } + + public String getLocalSqlFormula() { + return localSqlFormula; + } + + public String getForeignSqlFormula() { + return foreignSqlFormula; + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedIdSimple.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedIdSimple.java index 85fdf7b17..06ac324b9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedIdSimple.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedIdSimple.java @@ -37,6 +37,8 @@ public final class ImportedIdSimple implements ImportedId, Comparable owner; protected final String localDbColumn; + + protected final String localSqlFormula; protected final String logicalName; @@ -44,9 +46,10 @@ public final class ImportedIdSimple implements ImportedId, Comparable owner, String localDbColumn, BeanProperty foreignProperty, int position) { + public ImportedIdSimple(BeanPropertyAssoc owner, String localDbColumn, String localSqlFormula, BeanProperty foreignProperty, int position) { this.owner = owner; this.localDbColumn = InternString.intern(localDbColumn); + this.localSqlFormula = InternString.intern(localSqlFormula); this.foreignProperty = foreignProperty; this.position = position; this.logicalName = InternString.intern(owner.getName() + "." + foreignProperty.getName()); @@ -103,7 +106,11 @@ public final class ImportedIdSimple implements ImportedId, Comparable extends DeployBeanPropertyAssoc { tableJoin.setLocalColumn(dbColumn); } } + + @Override + public void setSqlFormula(String formulaSelect, String formulaJoin) { + super.setSqlFormula(formulaSelect, formulaJoin); + DeployTableJoinColumn[] columns = tableJoin.columns(); + if (columns.length == 1) { + columns[0].setLocalSqlFormula(formulaSelect); + } + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoinColumn.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoinColumn.java index cc2a8ca50..293857cdc 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoinColumn.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoinColumn.java @@ -15,14 +15,26 @@ public class DeployTableJoinColumn { */ String localDbColumn; + /** + * SQL formula used for local column + */ + String localSqlFormula; + /** * The foreign database column name. */ String foreignDbColumn; + + /** + * SQL formula used for foreign column + */ + String foreignSqlFormula; - final boolean insertable; + boolean insertable; + + boolean updateable; + - final boolean updateable; /** * Construct when automatically determining the join. @@ -43,7 +55,34 @@ public class DeployTableJoinColumn { this.insertable = insertable; this.updateable = updateable; } - + + public void setLocalSqlFormula(String localSqlFormula) { + if (localSqlFormula != null) { + this.localSqlFormula = localSqlFormula; + this.localDbColumn = null; + this.insertable = false; + this.updateable = false; + } + } + + public String getLocalSqlFormula() { + return localSqlFormula; + } + + public void setForeignSqlFormula(String foreignSqlFormula) { + if (foreignSqlFormula != null) { + this.foreignSqlFormula = foreignSqlFormula; + this.foreignDbColumn = null; + this.insertable = false; + this.updateable = false; + } + } + + public String getForeignSqlFormula() { + return foreignSqlFormula; + } + + public DeployTableJoinColumn(boolean order, JoinColumn jc, BeanTable beanTable) { this(jc.referencedColumnName(), jc.name(), jc.insertable(), jc.updatable()); setReferencedColumn(beanTable); @@ -68,6 +107,10 @@ public class DeployTableJoinColumn { String temp = localDbColumn; localDbColumn = foreignDbColumn; foreignDbColumn = temp; + + temp = localSqlFormula; + localSqlFormula = foreignSqlFormula; + foreignSqlFormula = temp; return this; } @@ -86,12 +129,18 @@ public class DeployTableJoinColumn { // Note that the insertable and updateable are just copied // which may not always be the correct thing to do // but will leave it like this for now + DeployTableJoinColumn ret; if (reverse){ - return new DeployTableJoinColumn(foreignDbColumn, localDbColumn, insertable, updateable); + ret = new DeployTableJoinColumn(foreignDbColumn, localDbColumn, insertable, updateable); + ret.setLocalSqlFormula(foreignSqlFormula); + ret.setForeignSqlFormula(localSqlFormula); } else { - return new DeployTableJoinColumn(localDbColumn, foreignDbColumn, insertable, updateable); + ret = new DeployTableJoinColumn(localDbColumn, foreignDbColumn, insertable, updateable); + ret.setLocalSqlFormula(localSqlFormula); + ret.setForeignSqlFormula(foreignSqlFormula); } + return ret; } public String toString() { @@ -132,4 +181,5 @@ public class DeployTableJoinColumn { public void setLocalDbColumn(String localDbColumn) { this.localDbColumn = localDbColumn; } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java index f5385890f..e597d8a12 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java @@ -136,7 +136,7 @@ public class AnnotationAssocManys extends AnnotationParser { // Use the owning bean table to define the join BeanTable owningBeanTable = factory.getBeanTable(descriptor.getBeanType()); - owningBeanTable.createJoinColumn(fkeyPrefix, prop.getTableJoin(), false); + owningBeanTable.createJoinColumn(fkeyPrefix, prop.getTableJoin(), false, prop.getSqlFormulaSelect()); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java index 6ade29cba..64242e408 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java @@ -142,7 +142,7 @@ public class AnnotationAssocOnes extends AnnotationParser { fkeyPrefix = nc.getColumnFromProperty(beanType, prop.getName()); } - beanTable.createJoinColumn(fkeyPrefix, prop.getTableJoin(), true); + beanTable.createJoinColumn(fkeyPrefix, prop.getTableJoin(), true, prop.getSqlFormulaSelect()); } } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java index ac5172534..8e04be070 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -103,6 +103,11 @@ public class AnnotationFields extends AnnotationParser { } readEmbeddedAttributeOverrides((DeployBeanPropertyAssocOne) prop); } + + Formula formula = get(prop, Formula.class); + if (formula != null) { + prop.setSqlFormula(formula.select(), formula.join()); + } } private void readField(DeployBeanProperty prop) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java index 3465ffe8f..00f3b1703 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java @@ -152,6 +152,7 @@ public class CQueryPredicates { // this is a find by id type query... request.getBeanDescriptor().bindId(dataBind, idValue); dataBind.append(idValue); + dataBind.append(", "); } if (bindParams != null) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultDbSqlContext.java b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultDbSqlContext.java index 67a3e7ceb..5896f6165 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultDbSqlContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultDbSqlContext.java @@ -136,9 +136,20 @@ class DefaultDbSqlContext implements DbSqlContext { if (i > 0) { sb.append(" and "); } - sb.append(a2).append(".").append(pair.getForeignDbColumn()); + + if (pair.getForeignSqlFormula() != null) { + sb.append(StringHelper.replaceString(pair.getForeignSqlFormula(), tableAliasPlaceHolder, a2)); + } else { + sb.append(a2).append(".").append(pair.getForeignDbColumn()); + } + sb.append(" = "); - sb.append(a1).append(".").append(pair.getLocalDbColumn()); + + if (pair.getLocalSqlFormula() != null) { + sb.append(StringHelper.replaceString(pair.getLocalSqlFormula(), tableAliasPlaceHolder, a1)); + } else { + sb.append(a1).append(".").append(pair.getLocalDbColumn()); + } } // add on any inheritance where clause @@ -244,6 +255,8 @@ class DefaultDbSqlContext implements DbSqlContext { sb.append(COMMA); sb.append(converted); + + appendColumnAlias(); } @Override diff --git a/src/test/java/com/avaje/tests/family/TestInheritance.java b/src/test/java/com/avaje/tests/family/TestInheritance.java new file mode 100644 index 000000000..093a3ae0c --- /dev/null +++ b/src/test/java/com/avaje/tests/family/TestInheritance.java @@ -0,0 +1,223 @@ + +package com.avaje.tests.family; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Ignore; +import org.junit.Test; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.tests.lib.EbeanTestCase; +import com.avaje.tests.model.basic.EBasic; +import com.avaje.tests.model.family.ChildPerson; +import com.avaje.tests.model.family.GrandParentPerson; +import com.avaje.tests.model.family.ParentPerson; + + + +public class TestInheritance extends BaseTestCase { + + + @Test + public void testDescriptor() { + BeanDescriptor desc = spiEbeanServer().getBeanDescriptor(GrandParentPerson.class); + BeanProperty prop1 = desc.getBeanProperty("someBean"); + BeanProperty prop2 = desc.getBeanProperty("effectiveBean"); + + assertEquals(BeanPropertyAssocOne.class, prop1.getClass()); + assertEquals(BeanPropertyAssocOne.class, prop2.getClass()); + } + + /** + *
+   * grandparent1   Josef Foo(65) from Munich
+   * +- parent1     Maria Bar (40) has someBean1
+   * |  +- child1   Fred(13) has someBean2
+   * |  '- child2   Julia Baz(8)
+   * +- parent2     Sandra (50) from Berlin
+   * |  '- child3   Roland(36)
+   * +- parent3     Michael(60)
+   * 
+ */ + @Test + public void testInheritance() { + // create some other beans + EBasic someBean1 = new EBasic(); + someBean1.setName("A Bean"); + server().save(someBean1); + + EBasic someBean2 = new EBasic(); + someBean2.setName("An other Bean"); + server().save(someBean2); + + ChildPerson child1 = new ChildPerson(); + child1.setAge(13); + child1.setName("Fred"); + + ChildPerson child2 = new ChildPerson(); + child2.setAge(8); + child2.setFamilyName("Baz"); + child2.setName("Julia"); + + ChildPerson child3 = new ChildPerson(); + child3.setAge(36); + child3.setName("Roland"); + + ParentPerson parent1 = new ParentPerson(); + parent1.setAge(40); + parent1.setName("Maria"); + parent1.setFamilyName("Bar"); + parent1.setSomeBean(someBean1); + child1.setSomeBean(someBean2); + parent1.getChildren().add(child1); + parent1.getChildren().add(child2); + + ParentPerson parent2 = new ParentPerson(); + parent2.setAge(50); + parent2.setName("Sandra"); + parent2.getChildren().add(child3); + parent2.setAddress("Berlin"); + + + ParentPerson parent3 = new ParentPerson(); + parent3.setAge(60); + parent3.setName("Michael"); + + GrandParentPerson grandparent1 = new GrandParentPerson(); + grandparent1.setAge(65); + grandparent1.setName("Josef"); + grandparent1.setFamilyName("Foo"); + grandparent1.setAddress("Munich"); + + grandparent1.getChildren().add(parent1); + grandparent1.getChildren().add(parent2); + grandparent1.getChildren().add(parent3); + + + server().save(grandparent1); + // Test setup complete, so retrieve bean from db + //grandparent1 = server().find(GrandParentPerson.class).setId(grandparent1.getIdentifier()).where() + // .in("effectiveBean.id",2,null) // geht nicht! + // .findUnique(); + parent1 = server().find(ParentPerson.class).setId(1).fetch("someBean").findUnique(); + assertEquals("A Bean", parent1.getSomeBean().getName()); + grandparent1 = server().find(GrandParentPerson.class) + .fetch("children","*") // FIXME If I do not fetch childrenBean, I will get an exception + .fetch("children.effectiveBean","*") + .fetch("children.children","*") + .fetch("children.children.effectiveBean","*") + .setId(grandparent1.getIdentifier()).where() + .or() + .in("effectiveBean.id",1) + .isNull("effectiveBean.id") + .endOr() + .findUnique(); + assertNotNull(grandparent1); + + // check if aggregation works + assertEquals(3, grandparent1.getChildCount().intValue()); + assertEquals(150, grandparent1.getTotalAge().intValue()); + + // check normal properties + assertEquals("Josef", grandparent1.getName()); + assertEquals("Foo", grandparent1.getFamilyName()); + assertEquals("Munich", grandparent1.getAddress()); + assertEquals(1, grandparent1.getEffectiveBean().getId().intValue()); + + // now check children of grandparent + parent1 = grandparent1.getChildren().get(0); + parent2 = grandparent1.getChildren().get(1); + parent3 = grandparent1.getChildren().get(2); + + assertNotNull(parent1); + assertNotNull(parent2); + assertNotNull(parent3); + // parent1 + assertEquals("Maria", parent1.getName()); + assertEquals("Bar", parent1.getFamilyName()); // overwritten family name + assertEquals("Bar", parent1.getEffectiveFamilyName()); // test inheritance + assertNull(parent1.getAddress()); // no alternative address set + assertEquals("Munich", parent1.getEffectiveAddress()); // -> inherit munich + assertEquals(2, parent1.getChildCount().intValue()); + assertEquals(21, parent1.getTotalAge().intValue()); + assertEquals("A Bean", parent1.getEffectiveBean().getName()); + + // parent2 + assertEquals("Sandra", parent2.getName()); + assertNull(parent2.getFamilyName()); + assertEquals("Foo", parent2.getEffectiveFamilyName()); + assertEquals("Berlin", parent2.getAddress()); + assertEquals("Berlin", parent2.getEffectiveAddress()); + assertEquals(1, parent2.getChildCount().intValue()); + assertEquals(36, parent2.getTotalAge().intValue()); + assertNull(parent2.getEffectiveBean()); + + // parent3 + assertEquals("Michael", parent3.getName()); + assertNull(parent3.getFamilyName()); + assertEquals("Foo", parent3.getEffectiveFamilyName()); + assertNull(parent3.getAddress()); + assertEquals("Munich", parent3.getEffectiveAddress()); + assertEquals(0, parent3.getChildCount().intValue()); + assertEquals(0, parent3.getTotalAge().intValue()); + assertNull(parent3.getEffectiveBean()); + + child1 = parent1.getChildren().get(0); + child2 = parent1.getChildren().get(1); + + child3 = parent2.getChildren().get(0); + + assertNotNull(child1); + assertNotNull(child2); + assertNotNull(child3); + + assertEquals("Bar", child1.getEffectiveFamilyName()); + assertEquals("Munich", child1.getEffectiveAddress()); + assertEquals("An other Bean", child1.getSomeBean().getName()); + assertEquals("An other Bean", child1.getEffectiveBean().getName()); + + assertTrue(child1.getSomeBean() == child1.getEffectiveBean()); + + assertEquals("Baz", child2.getEffectiveFamilyName()); + assertEquals("Munich", child2.getEffectiveAddress()); + assertNull(child2.getSomeBean()); + assertEquals("A Bean", child2.getEffectiveBean().getName()); + + assertEquals("Foo", child3.getEffectiveFamilyName()); + assertEquals("Berlin", child3.getEffectiveAddress()); + assertNull(child3.getEffectiveBean()); + + // Now start from bottom up + child2 = server().find(ChildPerson.class).where().eq("name", "Julia").select("parent.name").findUnique(); + assertEquals("Baz", child2.getEffectiveFamilyName()); + assertEquals("Munich", child2.getEffectiveAddress()); + assertNull(child2.getSomeBean()); + assertEquals("A Bean", child2.getEffectiveBean().getName()); + + parent1 = child2.getParent(); + assertEquals("Maria", parent1.getName()); + assertEquals("Bar", parent1.getFamilyName()); // overwritten family name + assertEquals("Bar", parent1.getEffectiveFamilyName()); // test inheritance + assertNull(parent1.getAddress()); // no alternative address set + assertEquals("Munich", parent1.getEffectiveAddress()); // -> inherit munich + assertEquals(2, parent1.getChildCount().intValue()); + assertEquals(21, parent1.getTotalAge().intValue()); + assertEquals("A Bean", parent1.getEffectiveBean().getName()); + + grandparent1 = parent1.getParent(); + // check if aggregation works + assertEquals(3, grandparent1.getChildCount().intValue()); + assertEquals(150, grandparent1.getTotalAge().intValue()); + + // check normal properties + assertEquals("Josef", grandparent1.getName()); + assertEquals("Foo", grandparent1.getFamilyName()); + assertEquals("Munich", grandparent1.getAddress()); + assertEquals(1, grandparent1.getEffectiveBean().getId().intValue()); + } +} diff --git a/src/test/java/com/avaje/tests/model/family/ChildPerson.java b/src/test/java/com/avaje/tests/model/family/ChildPerson.java new file mode 100644 index 000000000..77032e59a --- /dev/null +++ b/src/test/java/com/avaje/tests/model/family/ChildPerson.java @@ -0,0 +1,72 @@ + +package com.avaje.tests.model.family; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; + +import com.avaje.ebean.annotation.Formula; +import com.avaje.tests.model.basic.EBasic; + +@Entity +public class ChildPerson extends InheritablePerson { + + private static final String PARENTS_JOIN = "join parent_person j1 on j1.identifier = ${ta}.parent_identifier " + + "join grand_parent_person j2 on j2.identifier = j1.parent_identifier"; + + @ManyToOne(cascade = CascadeType.ALL) + private ParentPerson parent; + + private String familyName; + + private String address; + + //@Coalesce({ "familyName", "parent.familyName", "parent.parent.familyName" }) + @Formula(select = "coalesce(${ta}.family_name, j1.family_name, j2.family_name)", join = PARENTS_JOIN ) + private String effectiveFamilyName; + + //@Coalesce({ "address", "parent.address", "parent.parent.address" }) + @Formula(select = "coalesce(${ta}.address, j1.address, j2.address)", join = PARENTS_JOIN ) + private String effectiveAddress; + + @Formula(select = "coalesce(${ta}.some_bean_id, j1.some_bean_id, j2.some_bean_id)") + @ManyToOne + private EBasic effectiveBean; + + public ParentPerson getParent() { + return parent; + } + + public void setParent(ParentPerson parent) { + this.parent = parent; + } + + public String getFamilyName() { + return familyName; + } + + public void setFamilyName(String familyName) { + this.familyName = familyName; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getEffectiveFamilyName() { + return effectiveFamilyName; + } + + public String getEffectiveAddress() { + return effectiveAddress; + } + + public EBasic getEffectiveBean() { + return effectiveBean; + } + +} diff --git a/src/test/java/com/avaje/tests/model/family/GrandParentPerson.java b/src/test/java/com/avaje/tests/model/family/GrandParentPerson.java new file mode 100644 index 000000000..8e27aae3b --- /dev/null +++ b/src/test/java/com/avaje/tests/model/family/GrandParentPerson.java @@ -0,0 +1,82 @@ + +package com.avaje.tests.model.family; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; + +import com.avaje.ebean.annotation.Formula; +import com.avaje.tests.model.basic.EBasic; + +@Entity +public class GrandParentPerson extends InheritablePerson { + + @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL) + @OrderBy("identifier") + private List children = new ArrayList<>(); + + // This rather complex formulas should be built later by CustomAnnotationParser + private static final String PARENT_PERSON_AGGREGATE_JOIN = "left join " + + "(select i1.parent_identifier, count(*) as child_count, sum(i1.age) as child_age from parent_person i1 group by i1.parent_identifier) " + + "as f1 on f1.parent_identifier = ${ta}.identifier"; + + //@Count("children") + @Formula(select = "coalesce(f1.child_count, 0)", join = PARENT_PERSON_AGGREGATE_JOIN ) + private Integer childCount; + + //@Sum("children.age") + @Formula(select = "coalesce(f1.child_age, 0)", join = PARENT_PERSON_AGGREGATE_JOIN ) + private Integer totalAge; + + private String familyName; + + private String address; + + + // Demonstrate formula usage + @Formula(select = "coalesce(${ta}.some_bean_id,1)") + @ManyToOne + private EBasic effectiveBean; + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + + public Integer getTotalAge() { + return totalAge; + } + + public String getFamilyName() { + return familyName; + } + + public void setFamilyName(String familyName) { + this.familyName = familyName; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public Integer getChildCount() { + return childCount; + } + + public EBasic getEffectiveBean() { + return effectiveBean; + } +} diff --git a/src/test/java/com/avaje/tests/model/family/InheritablePerson.java b/src/test/java/com/avaje/tests/model/family/InheritablePerson.java new file mode 100644 index 000000000..8a174932b --- /dev/null +++ b/src/test/java/com/avaje/tests/model/family/InheritablePerson.java @@ -0,0 +1,55 @@ + +package com.avaje.tests.model.family; + +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; + +import com.avaje.tests.model.basic.EBasic; + +@MappedSuperclass +public abstract class InheritablePerson { + + @Id + private Integer identifier; + + private String name; + + private Integer age; + + @ManyToOne + private EBasic someBean; + + public Integer getIdentifier() { + return identifier; + } + + public void setIdentifier(Integer identifier) { + this.identifier = identifier; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public void setSomeBean(EBasic someBean) { + this.someBean = someBean; + } + + public EBasic getSomeBean() { + return someBean; + } + +} diff --git a/src/test/java/com/avaje/tests/model/family/ParentPerson.java b/src/test/java/com/avaje/tests/model/family/ParentPerson.java new file mode 100644 index 000000000..98d46b09e --- /dev/null +++ b/src/test/java/com/avaje/tests/model/family/ParentPerson.java @@ -0,0 +1,106 @@ + +package com.avaje.tests.model.family; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; + +import com.avaje.ebean.annotation.Formula; +import com.avaje.tests.model.basic.EBasic; + + +@Entity +public class ParentPerson extends InheritablePerson { + + //This rather complex formulas should be built later by CustomAnnotationParser + private static final String CHILD_PERSON_AGGREGATE_JOIN = "left join " + + "(select i2.parent_identifier, count(*) as child_count, sum(i2.age) as child_age from child_person i2 group by i2.parent_identifier) " + + "as f2 on f2.parent_identifier = ${ta}.identifier"; + + private static final String GRAND_PARENT_PERSON_JOIN = "join grand_parent_person j1 on j1.identifier = ${ta}.parent_identifier"; + + @ManyToOne(cascade = CascadeType.ALL) + private GrandParentPerson parent; + + @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL) + @OrderBy("identifier") + private List children = new ArrayList<>(); + + //@Count("children") + @Formula(select = "coalesce(f2.child_count, 0)", join = CHILD_PERSON_AGGREGATE_JOIN ) + private Integer childCount; + + //@Sum("children.age") + @Formula(select = "coalesce(f2.child_age, 0)", join = CHILD_PERSON_AGGREGATE_JOIN ) + private Integer totalAge; + + private String familyName; + + private String address; + + //@Coalesce({ "familyName", "parent.familyName" }) + @Formula(select = "coalesce(${ta}.family_name, j1.family_name)", join = GRAND_PARENT_PERSON_JOIN ) + private String effectiveFamilyName; + + //@Coalesce({ "address", "parent.address" }) + @Formula(select = "coalesce(${ta}.address, j1.address)", join = GRAND_PARENT_PERSON_JOIN ) + private String effectiveAddress; + + @Formula(select = "coalesce(${ta}.some_bean_id, j1.some_bean_id)", join = GRAND_PARENT_PERSON_JOIN ) + @ManyToOne + private EBasic effectiveBean; + + public GrandParentPerson getParent() { + return parent; + } + + public void setParent(GrandParentPerson parent) { + this.parent = parent; + } + + public String getFamilyName() { + return familyName; + } + + public void setFamilyName(String familyName) { + this.familyName = familyName; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public List getChildren() { + return children; + } + + public Integer getChildCount() { + return childCount; + } + + public Integer getTotalAge() { + return totalAge; + } + + public String getEffectiveFamilyName() { + return effectiveFamilyName; + } + + public String getEffectiveAddress() { + return effectiveAddress; + } + + public EBasic getEffectiveBean() { + return effectiveBean; + } + +}