diff --git a/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java b/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java index a0c17d128..2e9fa404b 100644 --- a/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java @@ -248,10 +248,14 @@ public final class SqlTreeBuilder { List myJoinList = new ArrayList<>(); + List extraProps = new ArrayList<>(); for (STreePropertyAssocOne one : desc.propsOne()) { String propPrefix = SplitName.add(prefix, one.getName()); if (isIncludeBean(propPrefix)) { selectIncludes.add(propPrefix); + if (!one.hasForeignKey()) { + extraProps.add(one); + } buildSelectChain(propPrefix, one, one.target(), myJoinList); } } @@ -273,6 +277,7 @@ public final class SqlTreeBuilder { } addManyWhereJoins(myJoinList); } + extraProps.forEach(props::add); SqlTreeNode selectNode = buildNode(prefix, prop, desc, myJoinList, props); if (joinList != null) { diff --git a/src/test/java/org/tests/lazyforeignkeys/MainEntity.java b/src/test/java/org/tests/lazyforeignkeys/MainEntity.java new file mode 100644 index 000000000..ec92722db --- /dev/null +++ b/src/test/java/org/tests/lazyforeignkeys/MainEntity.java @@ -0,0 +1,55 @@ +package org.tests.lazyforeignkeys; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import io.ebean.annotation.Formula; +import io.ebean.annotation.Platform; +import io.ebean.annotation.SoftDelete; + +@Entity +@Table(name = "main_entity") +public class MainEntity { + + @Id + private String id; + + private String attr1; + + private String attr2; + + @SoftDelete + @Formula(select = "${ta}.id is null") + @Formula(select = "CASE WHEN ${ta}.id is null THEN 1 ELSE 0 END", platforms = Platform.SQLSERVER17) + // evaluates to true in a left join if bean has been deleted. + boolean deleted; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getAttr1() { + return attr1; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr2() { + return attr2; + } + + public void setAttr2(String attr2) { + this.attr2 = attr2; + } + + public boolean isDeleted() { + return deleted; + } +} diff --git a/src/test/java/org/tests/lazyforeignkeys/MainEntityRelation.java b/src/test/java/org/tests/lazyforeignkeys/MainEntityRelation.java new file mode 100644 index 000000000..20c6ed4e9 --- /dev/null +++ b/src/test/java/org/tests/lazyforeignkeys/MainEntityRelation.java @@ -0,0 +1,56 @@ +package org.tests.lazyforeignkeys; + +import java.util.UUID; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import io.ebean.annotation.DbForeignKey; + + +@Entity +@Table(name = "main_entity_relation") +public class MainEntityRelation { + + @Id + private UUID id; + + @ManyToOne + @JoinColumn(name = "id1") + @DbForeignKey(noConstraint = true) + private MainEntity entity1; + + @ManyToOne + @JoinColumn(name = "id2") + @DbForeignKey(noConstraint = true) + private MainEntity entity2; + + private String attr1; + + public MainEntity getEntity1() { + return entity1; + } + + public void setEntity1(MainEntity entity1) { + this.entity1 = entity1; + } + + public MainEntity getEntity2() { + return entity2; + } + + public void setEntity2(MainEntity entity2) { + this.entity2 = entity2; + } + + public String getAttr1() { + return attr1; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } +} diff --git a/src/test/java/org/tests/lazyforeignkeys/TestLazyForeignKeys.java b/src/test/java/org/tests/lazyforeignkeys/TestLazyForeignKeys.java new file mode 100644 index 000000000..ccafc56f5 --- /dev/null +++ b/src/test/java/org/tests/lazyforeignkeys/TestLazyForeignKeys.java @@ -0,0 +1,88 @@ +package org.tests.lazyforeignkeys; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.*; + +import java.util.List; + +import org.ebeantest.LoggedSqlCollector; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import io.ebean.BaseTestCase; +import io.ebean.DB; +import io.ebean.Ebean; +import io.ebean.Query; +import io.ebean.text.PathProperties; + +public class TestLazyForeignKeys extends BaseTestCase { + + @Before + public void prepare() { + MainEntity ent1 = new MainEntity(); + ent1.setId("ent1"); + ent1.setAttr1("attr1"); + DB.save(ent1); + + MainEntityRelation rel1 = new MainEntityRelation(); + MainEntity e1 = new MainEntity(); + e1.setId("ent1"); + MainEntity e2 = new MainEntity(); + e2.setId("ent2"); + + rel1.setEntity1(e1); + rel1.setEntity2(e2); + DB.save(rel1); + } + + @After + public void cleanup() { + DB.find(MainEntity.class).delete(); + DB.find(MainEntityRelation.class).delete(); + } + + @Test + public void testFindOne() throws Exception { + // use findOne without select, so lazy loading will occur + LoggedSqlCollector.start(); + + MainEntityRelation rel1 = DB.find(MainEntityRelation.class).findOne(); + + assertEquals("ent1", rel1.getEntity1().getId()); + assertEquals("ent2", rel1.getEntity2().getId()); + + assertEquals("attr1", rel1.getEntity1().getAttr1()); + assertFalse(rel1.getEntity1().isDeleted()); + assertTrue(rel1.getEntity2().isDeleted()); + + List loggedSql = LoggedSqlCollector.stop(); + assertThat(loggedSql).hasSize(3); + assertThat(loggedSql.get(0)).contains("select t0.id, t0.attr1, t0.id1, t0.id2 from main_entity_relation"); + assertThat(loggedSql.get(1)).contains("select t0.id, t0.attr1, t0.attr2, t0.id is null from main_entity t0"); + assertThat(loggedSql.get(2)).contains("select t0.id, t0.attr1, t0.attr2, t0.id is null from main_entity t0"); + } + + @Test + public void testFindListWithSelect() { + PathProperties pathProp = new PathProperties(); + pathProp.addToPath(null, "attr1"); + pathProp.addToPath("entity1", "id"); + pathProp.addToPath("entity2", "id"); + + Query query = Ebean.find(MainEntityRelation.class).apply(pathProp); + List list = query.findList(); + assertEquals(1, list.size()); + + System.out.println(query.getGeneratedSql()); + assertThat(query.getGeneratedSql()).contains("t0.id, t0.attr1, t0.id1, t0.id2, t1.id, t2.id"); + + MainEntityRelation rel1 = list.get(0); + assertEquals("ent1", rel1.getEntity1().getId()); + assertEquals("ent2", rel1.getEntity2().getId()); + + assertEquals("attr1", rel1.getEntity1().getAttr1()); + assertFalse(rel1.getEntity1().isDeleted()); + assertTrue(rel1.getEntity2().isDeleted()); + } +} diff --git a/src/test/resources/extra-ddl.xml b/src/test/resources/extra-ddl.xml index aaf69b523..b01ed35b2 100644 --- a/src/test/resources/extra-ddl.xml +++ b/src/test/resources/extra-ddl.xml @@ -4,8 +4,8 @@ drop view order_agg_vw if exists; - + create or replace view order_agg_vw as select d.order_id, sum(d.order_qty * d.unit_price) as order_total, sum(d.ship_qty * d.unit_price) as ship_total @@ -21,7 +21,7 @@ from o_order_detail d group by d.order_id; - + create view order_agg_vw as select d.order_id, sum(d.order_qty * d.unit_price) as order_total, @@ -37,7 +37,6 @@ sum(d.ship_qty * d.unit_price) as ship_total from o_order_detail d group by d.order_id; -