diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 689ee5f2e..196ca602f 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -72,7 +72,7 @@ io.ebean ebean-ddl-generator - 12.9.0 + 12.9.3a test diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/LoadManyRequest.java b/ebean-core/src/main/java/io/ebeaninternal/api/LoadManyRequest.java index aeb9ff7dd..0343b8579 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/LoadManyRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/LoadManyRequest.java @@ -119,7 +119,7 @@ public class LoadManyRequest extends LoadRequest { if (extraWhere != null) { // replace special ${ta} placeholder with the base table alias // which is always t0 and add the extra where clause - query.where().raw(extraWhere.replace("${ta}", "t0")); + query.where().raw(extraWhere.replace("${ta}", "t0").replace("${mta}", "int_")); } query.setLazyLoadForParents(many); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index 260985417..63599b7ad 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -2016,6 +2016,13 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { return owner.getBeanDescriptor(otherType); } + /** + * Returns true, if the table is managed (i.e. an existing m2m relation). + */ + public boolean isTableManaged(String tableName) { + return owner.isTableManaged(tableName); + } + /** * Return the order column property. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java index b660c1718..84b412513 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -445,6 +445,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTypeMana return tableToDescMap.get(tableName.toLowerCase()); } + @Override + public boolean isTableManaged(String tableName) { + return tableToDescMap.get(tableName.toLowerCase()) != null + || tableToViewDescMap.get(tableName.toLowerCase()) != null; + } + /** * Invalidate entity beans based on views via their dependent tables. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorMap.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorMap.java index 7c7dd6e0c..94a9fded9 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorMap.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorMap.java @@ -76,4 +76,10 @@ public interface BeanDescriptorMap { * Return true if Jackson core is present on the classpath. */ boolean isJacksonCorePresent(); + + /** + * Returns true, if the given table (or view) is managed by ebean + * (= an entity exists) + */ + boolean isTableManaged(String tableName); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java index 8e1ac1a14..c823e9a7e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java @@ -439,6 +439,7 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc implements ST @Override public String getAssocIsEmpty(SpiExpressionRequest request, String path) { boolean softDelete = targetDescriptor.isSoftDelete(); + boolean needsX2Table = softDelete || getExtraWhere() != null; StringBuilder sb = new StringBuilder(50); SpiQuery query = request.getQueryRequest().getQuery(); if (hasJoinTable()) { @@ -446,7 +447,7 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc implements ST } else { sb.append(targetDescriptor.getBaseTable(query.getTemporalMode())); } - if (softDelete && hasJoinTable()) { + if (needsX2Table && hasJoinTable()) { sb.append(" x join "); sb.append(targetDescriptor.getBaseTable(query.getTemporalMode())); sb.append(" x2 on "); @@ -461,6 +462,16 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc implements ST } exportedProperties[i].appendWhere(sb, "x.", path); } + if (getExtraWhere() != null) { + sb.append(" and "); + if (hasJoinTable()) { + sb.append(getExtraWhere().replace("${ta}", "x2").replace("${mta}", "x")); + } else { + sb.append(getExtraWhere().replace("${ta}", "x")); + } + } + + if (softDelete) { String alias = hasJoinTable() ? "x2" : "x"; sb.append(" and ").append(targetDescriptor.getSoftDeletePredicate(alias)); @@ -1061,4 +1072,16 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc implements ST public void bindElementValue(SqlUpdate insert, Object value) { targetDescriptor.bindElementValue(insert, value); } + + /** + * Returns true, if we must create a m2m join table. + */ + public boolean createJoinTable() { + if (hasJoinTable() && getMappedBy() == null) { + // only create on other 'owning' side + return !descriptor.isTableManaged(intersectionJoin.getTable()); + } else { + return false; + } + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DbSqlContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DbSqlContext.java index 257893820..1e078c0fd 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DbSqlContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DbSqlContext.java @@ -10,7 +10,7 @@ public interface DbSqlContext { /** * Add a join to the sql query. */ - void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2); + void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2, String extraWhere); /** * Push the current table alias onto the stack. diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/TableJoin.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/TableJoin.java index e88e56034..8c1624d05 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/TableJoin.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/TableJoin.java @@ -34,6 +34,8 @@ public final class TableJoin { private final int queryHash; private final PropertyForeignKey foreignKey; + + private final String extraWhere; public TableJoin(DeployTableJoin deploy) { this(deploy, null); @@ -44,6 +46,7 @@ public final class TableJoin { */ public TableJoin(DeployTableJoin deploy, PropertyForeignKey foreignKey) { this.foreignKey = foreignKey; + this.extraWhere = deploy.getExtraWhere(); this.table = InternString.intern(deploy.getTable()); this.type = deploy.getType(); this.inheritInfo = deploy.getInheritInfo(); @@ -57,6 +60,7 @@ public final class TableJoin { private TableJoin(TableJoin source, String overrideColumn) { this.foreignKey = null; + this.extraWhere = source.extraWhere; this.table = source.table; this.type = source.type; this.inheritInfo = source.inheritInfo; @@ -146,7 +150,7 @@ public final class TableJoin { public SqlJoinType addJoin(SqlJoinType joinType, String a1, String a2, DbSqlContext ctx) { String joinLiteral = joinType.getLiteral(type); - ctx.addJoin(joinLiteral, table, columns(), a1, a2); + ctx.addJoin(joinLiteral, table, columns(), a1, a2, extraWhere); return joinType.autoToOuter(type); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java index 472a71db3..be4b77aa0 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java @@ -86,6 +86,7 @@ public abstract class DeployBeanPropertyAssoc extends DeployBeanProperty { * collection. */ public void setExtraWhere(String extraWhere) { + this.tableJoin.setExtraWhere(extraWhere); this.extraWhere = extraWhere; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployTableJoin.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployTableJoin.java index d24024774..c3451f9f4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployTableJoin.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployTableJoin.java @@ -33,6 +33,8 @@ public class DeployTableJoin { private ArrayList columns = new ArrayList<>(4); private InheritInfo inheritInfo; + + private String extraWhere; /** * Create a DeployTableJoin. @@ -137,6 +139,18 @@ public class DeployTableJoin { this.type = type; } + /** + * Returns the clause of an extra @Where annotation. + * @return + */ + public String getExtraWhere() { + return extraWhere; + } + + public void setExtraWhere(String extraWhere) { + this.extraWhere = extraWhere; + } + public DeployTableJoin createInverse(String tableName) { DeployTableJoin inverse = new DeployTableJoin(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java index b47612d65..1ca59dbc8 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java @@ -118,7 +118,7 @@ class AnnotationAssocManys extends AnnotationAssoc { Where where = prop.getMetaAnnotationWhere(platform); if (where != null) { - prop.setExtraWhere(where.clause()); + prop.setExtraWhere(processFormula(where.clause())); } FetchPreference fetchPreference = get(prop, FetchPreference.class); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java index b5929d6a0..36fa99b57 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java @@ -96,7 +96,7 @@ public class AnnotationAssocOnes extends AnnotationAssoc { Where where = prop.getMetaAnnotationWhere(platform); if (where != null) { // not expecting this to be used on assoc one properties - prop.setExtraWhere(where.clause()); + prop.setExtraWhere(processFormula(where.clause())); } PrimaryKeyJoinColumn primaryKeyJoin = get(prop, PrimaryKeyJoinColumn.class); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java index 8079f156f..5a0caa287 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -153,7 +153,7 @@ public class AnnotationFields extends AnnotationParser { Formula formula = prop.getMetaAnnotationFormula(platform); if (formula != null) { - prop.setSqlFormula(formula.select(), formula.join()); + prop.setSqlFormula(processFormula(formula.select()), processFormula(formula.join())); } initWhoProperties(prop); @@ -334,7 +334,7 @@ public class AnnotationFields extends AnnotationParser { } Formula formula = prop.getMetaAnnotationFormula(platform); if (formula != null) { - prop.setSqlFormula(formula.select(), formula.join()); + prop.setSqlFormula(processFormula(formula.select()), processFormula(formula.join())); } final Aggregation aggregation = prop.getMetaAnnotation(Aggregation.class); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java index af015e1f5..ea880a18b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java @@ -129,4 +129,11 @@ public abstract class AnnotationParser extends AnnotationBase { } return columnNames; } + + /** + * Process any formula from @Formula or @Where. + */ + protected String processFormula(String source) { + return source == null ? null : source.replace("${dbTableName}", descriptor.getBaseTable()); + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultDbSqlContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultDbSqlContext.java index 981abae74..9ee2c253c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultDbSqlContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultDbSqlContext.java @@ -111,7 +111,7 @@ class DefaultDbSqlContext implements DbSqlContext { } @Override - public void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2) { + public void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2, String extraWhere) { if (tableJoins == null) { tableJoins = new HashSet<>(); @@ -164,6 +164,14 @@ class DefaultDbSqlContext implements DbSqlContext { if (addAsOfOnClause) { sb.append(" and ").append(historySupport.getAsOfPredicate(a2)); } + + if (extraWhere != null && !extraWhere.isEmpty()) { + sb.append(" and "); + // we will also need a many-table alias here + sb.append(extraWhere.replace(tableAliasPlaceHolder, a2).replace("${mta}", a1)); + } + + } private void appendTable(String table, String draftTable) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java index 8c5517a76..d920b57ef 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java @@ -47,16 +47,6 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean { ctx.popTableAlias(); } - /** - * append extraWhere to the join. - */ - @Override - protected SqlJoinType appendFromAsJoin(DbSqlContext ctx, SqlJoinType joinType) { - SqlJoinType join = super.appendFromAsJoin(ctx, joinType); - super.appendExtraWhere(ctx); - return join; - } - @Override protected void appendExtraWhere(DbSqlContext ctx) { // extraWhere is already appended to the tableJoin diff --git a/ebean-core/src/test/java/org/tests/model/m2m/MnyEdge.java b/ebean-core/src/test/java/org/tests/model/m2m/MnyEdge.java new file mode 100644 index 000000000..2e86cb9df --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/m2m/MnyEdge.java @@ -0,0 +1,57 @@ +package org.tests.model.m2m; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import io.ebean.annotation.Index; + +@Entity +@Index(unique = true, columnNames = { "from_id", "to_id" }) +@Index(unique = true, columnNames = { "to_id", "from_id" }) +public class MnyEdge { + + @Id + private Integer id; + + @ManyToOne + private MnyNode from; + + @ManyToOne + private MnyNode to; + + private int flags; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public MnyNode getFrom() { + return from; + } + + public void setFrom(MnyNode from) { + this.from = from; + } + + public MnyNode getTo() { + return to; + } + + public void setTo(MnyNode to) { + this.to = to; + } + + public int getFlags() { + return flags; + } + + public void setFlags(int flags) { + this.flags = flags; + } + +} diff --git a/ebean-core/src/test/java/org/tests/model/m2m/MnyNode.java b/ebean-core/src/test/java/org/tests/model/m2m/MnyNode.java new file mode 100644 index 000000000..e51c5f417 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/m2m/MnyNode.java @@ -0,0 +1,129 @@ +package org.tests.model.m2m; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; + +import io.ebean.annotation.Platform; +import io.ebean.annotation.Where; + +import java.util.List; + +@Entity +public class MnyNode { + + @Id + Integer id; + + String name; + + @ManyToMany + @JoinTable(name = "mny_edge", + joinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"), + inverseJoinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id")) + List allRelations; + + @ManyToMany + @JoinTable(name = "mny_edge", + joinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"), + inverseJoinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id")) + List allReverseRelations; + + @ManyToMany + @JoinTable(name = "mny_edge", + joinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"), + inverseJoinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id")) + @Where(clause = "${mta}.flags & 1 != 0") + @Where(clause = "BITAND(${mta}.flags, 1) != 0", platforms = Platform.H2) + List bit1Relations; + + @ManyToMany + @JoinTable(name = "mny_edge", + joinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"), + inverseJoinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id")) + @Where(clause = "${mta}.flags & 1 != 0") + @Where(clause = "BITAND(${mta}.flags, 1) != 0", platforms = Platform.H2) + List bit1ReverseRelations; + + @ManyToMany + @JoinTable(name = "mny_edge", + joinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"), + inverseJoinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id")) + @Where(clause = "${mta}.flags & 2 != 0") + @Where(clause = "BITAND(${mta}.flags, 2) != 0", platforms = Platform.H2) + List bit2Relations; + + @ManyToMany + @JoinTable(name = "mny_edge", + joinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"), + inverseJoinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id")) + @Where(clause = "${mta}.flags & 2 != 0") + @Where(clause = "BITAND(${mta}.flags, 2) != 0", platforms = Platform.H2) + List bit2ReverseRelations; + + @ManyToMany + @JoinTable(name = "mny_edge", + joinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"), + inverseJoinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id")) + @Where(clause = "'${dbTableName}' = ${ta}.name") + List withDbTableName; + + public MnyNode() { + + } + + public MnyNode(String name) { + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getAllRelations() { + return allRelations; + } + + public List getAllReverseRelations() { + return allReverseRelations; + } + + public List getBit1Relations() { + return bit1Relations; + } + + public List getBit1ReverseRelations() { + return bit1ReverseRelations; + } + + public List getBit2Relations() { + return bit2Relations; + } + + public List getBit2ReverseRelations() { + return bit2ReverseRelations; + } + + public List getWithDbTableName() { + return withDbTableName; + } + + public void setWithDbTableName(List withDbTableName) { + this.withDbTableName = withDbTableName; + } + +} diff --git a/ebean-core/src/test/java/org/tests/model/m2m/TestM2MWithWhere.java b/ebean-core/src/test/java/org/tests/model/m2m/TestM2MWithWhere.java new file mode 100644 index 000000000..da8d83dd5 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/m2m/TestM2MWithWhere.java @@ -0,0 +1,159 @@ +package org.tests.model.m2m; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.junit.Test; +import org.tests.model.m2m.MnyEdge; +import org.tests.model.m2m.MnyNode; + +import io.ebean.BaseTestCase; +import io.ebean.DB; +import io.ebeantest.LoggedSql; + +/** + * Tests M2M with complex where queries. + * + * @author Roland Praml, FOCONIS AG + * + */ +public class TestM2MWithWhere extends BaseTestCase { + + @Test + public void testQuery() throws Exception { + createTestData(); + MnyNode node = DB.find(MnyNode.class, 1); + + + List result = DB.find(MnyNode.class).where().eq("allRelations", node).findList(); + assertThat(result).extracting(MnyNode::getId).containsExactly(1, 2, 3, 4, 5); + + result = DB.find(MnyNode.class).where().eq("allReverseRelations", node).findList(); + assertThat(result).extracting(MnyNode::getId).containsExactly(1, 2, 3, 4, 5); + + result = DB.find(MnyNode.class).where().eq("bit1Relations", node).findList(); + assertThat(result).isEmpty(); // -> to = 1 column: 2 0 2 0 2 + + result = DB.find(MnyNode.class).where().eq("bit2Relations", node).findList(); + assertThat(result).extracting(MnyNode::getId).containsExactly(1, 3, 5); + + result = DB.find(MnyNode.class).where().eq("bit1ReverseRelations", node).findList(); + // -> from = 1 column: 2 1 3 1 3 + assertThat(result).extracting(MnyNode::getId).containsExactly(2, 3, 4, 5); + + result = DB.find(MnyNode.class).where().eq("bit2ReverseRelations", node).findList(); + assertThat(result).hasSize(3).extracting(MnyNode::getId).containsExactly(1, 3, 5); + + result = DB.find(MnyNode.class).where().eq("bit2ReverseRelations", node).findList(); + assertThat(result).hasSize(3).extracting(MnyNode::getId).containsExactly(1, 3, 5); + } + + @Test + public void testGetter() throws Exception { + createTestData(); + MnyNode node = DB.find(MnyNode.class, 3); + + assertThat(node.getAllRelations()).extracting(MnyNode::getId).containsExactly(1, 2, 3, 4, 5); + + assertThat(node.getAllReverseRelations()).extracting(MnyNode::getId).containsExactly(1, 2, 3, 4, 5); + + assertThat(node.getBit1Relations()).extracting(MnyNode::getId).containsExactly(4, 5); + + assertThat(node.getBit1ReverseRelations()).extracting(MnyNode::getId).containsExactly(1, 2); + + assertThat(node.getBit2Relations()).extracting(MnyNode::getId).containsExactly(1, 3, 5); + + LoggedSql.start(); + assertThat(node.getBit2ReverseRelations()).extracting(MnyNode::getId).containsExactly(1, 3, 5); + List sqls = LoggedSql.stop(); + assertThat(sqls).hasSize(1); // lazy load + + // prefetch everything + LoggedSql.start(); + node = DB.find(MnyNode.class) + .fetch("bit1Relations","*") + .fetch("bit1ReverseRelations","*") + .where().idEq(3).findOne(); + sqls = LoggedSql.stop(); + assertThat(sqls).hasSize(2); + + // no lazyLoad expected + LoggedSql.start(); + assertThat(node.getBit1Relations()).extracting(MnyNode::getId).containsExactly(4, 5); + assertThat(node.getBit1ReverseRelations()).extracting(MnyNode::getId).containsExactly(1, 2); + sqls = LoggedSql.stop(); + assertThat(sqls).hasSize(0); + + } + + // to = | 1 2 3 4 5 + // ---------+--------------- + // from = 1 | 2 1 3 1 3 + // from = 2 | 0 2 1 3 1 + // from = 3 | 2 0 2 1 3 + // from = 4 | 0 2 0 2 1 + // from = 5 | 2 0 2 0 2 + private void createTestData() { + DB.find(MnyEdge.class).delete(); + DB.find(MnyNode.class).delete(); + for (int i = 1; i <= 5; i++) { + MnyNode node = new MnyNode(); + node.setId(i); + node.setName("Node #" + i); + DB.save(node); + } + StringBuilder sb = new StringBuilder(); + for (int from = 1; from <= 5; from++) { + sb.append("from = ").append(from).append(" |"); + for (int to = 1; to <= 5; to++) { + MnyEdge edge = new MnyEdge(); + edge.setFrom(DB.getReference(MnyNode.class, from)); + edge.setTo(DB.getReference(MnyNode.class, to)); + int flags = 0; + if (from < to) { + flags |= 1; + } + if ((from + to) % 2 == 0) { + flags |= 2; + } + edge.setFlags(flags); + DB.save(edge); + sb.append(" ").append(flags); + } + sb.append('\n'); + } + // System.out.println(sb); dump the table + } + + + @Test + public void testWithDbTableName() { + LoggedSql.start(); + DB.find(MnyNode.class).where().isNotNull("withDbTableName.name").findList(); + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("'mny_node' = u1.name"); + + LoggedSql.start(); + DB.find(MnyNode.class).where().isNotEmpty("withDbTableName").findList(); + sql = LoggedSql.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("'mny_node' = x2.name"); + } + + @Test + public void testLazyLoad() throws Exception { + MnyNode el = new MnyNode("testLazyLoad"); + DB.save(el); + LoggedSql.start(); + el = DB.find(MnyNode.class).select("name").where().eq("name", "testLazyLoad").findOne(); + el.getWithDbTableName().size(); // trigger Lazy load + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(2); + assertThat(sql.get(0)).contains("select t0.id, t0.name from mny_node"); + assertThat(sql.get(1)).contains("where 'mny_node' = t0.name"); + DB.delete(el); + } + +} diff --git a/ebean-core/src/test/java/org/tests/o2m/OmBasicParent.java b/ebean-core/src/test/java/org/tests/o2m/OmBasicParent.java index 76faeca0e..15b117bd0 100644 --- a/ebean-core/src/test/java/org/tests/o2m/OmBasicParent.java +++ b/ebean-core/src/test/java/org/tests/o2m/OmBasicParent.java @@ -4,6 +4,9 @@ import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Version; + +import io.ebean.annotation.Where; + import java.util.List; import static javax.persistence.CascadeType.ALL; @@ -26,6 +29,10 @@ public class OmBasicParent { @OneToMany(cascade = ALL, mappedBy = "parent") private List children; + @OneToMany(cascade = ALL, mappedBy = "parent") + @Where(clause = "'${dbTableName}' = ${ta}.name") + private List childrenWithWhere; + public OmBasicParent(String name) { this.name = name; } @@ -57,4 +64,13 @@ public class OmBasicParent { public void setChildren(List children) { this.children = children; } + + public List getChildrenWithWhere() { + return childrenWithWhere; + } + + public void setChildrenWithWhere(List childrenWithWhere) { + this.childrenWithWhere = childrenWithWhere; + } + } diff --git a/ebean-core/src/test/java/org/tests/o2m/TestOneToManyWhere.java b/ebean-core/src/test/java/org/tests/o2m/TestOneToManyWhere.java new file mode 100644 index 000000000..e2dca664a --- /dev/null +++ b/ebean-core/src/test/java/org/tests/o2m/TestOneToManyWhere.java @@ -0,0 +1,44 @@ +package org.tests.o2m; + +import io.ebean.BaseTestCase; +import io.ebean.DB; +import io.ebeantest.LoggedSql; + +import org.junit.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestOneToManyWhere extends BaseTestCase { + + @Test + public void testWithDbTableName() { + LoggedSql.start(); + DB.find(OmBasicParent.class).where().isNotNull("childrenWithWhere.name").findList(); + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("'om_basic_parent' = u1.name"); + + LoggedSql.start(); + DB.find(OmBasicParent.class).where().isNotEmpty("childrenWithWhere").findList(); + sql = LoggedSql.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("'om_basic_parent' = x.name"); + } + + @Test + public void testLazyLoad() throws Exception { + OmBasicParent el = new OmBasicParent("testLazyLoad"); + DB.save(el); + LoggedSql.start(); + el = DB.find(OmBasicParent.class).select("name").where().eq("name", "testLazyLoad").findOne(); + el.getChildrenWithWhere().size(); // trigger Lazy load + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(2); + assertThat(sql.get(0)).contains("select t0.id, t0.name from om_basic_parent"); + assertThat(sql.get(1)).contains("where 'om_basic_parent' = t0.name"); + DB.delete(el); + } + +} diff --git a/ebean-core/src/test/java/org/tests/query/TestImplicitJoinOnParentRelationship.java b/ebean-core/src/test/java/org/tests/query/TestImplicitJoinOnParentRelationship.java index c4aa242eb..eac3303fb 100644 --- a/ebean-core/src/test/java/org/tests/query/TestImplicitJoinOnParentRelationship.java +++ b/ebean-core/src/test/java/org/tests/query/TestImplicitJoinOnParentRelationship.java @@ -24,11 +24,11 @@ public class TestImplicitJoinOnParentRelationship extends BaseTestCase { query.findList(); if (isPostgres()) { - String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id join o_order_detail u2 on u2.order_id = u1.id join o_product u3 on u3.id = u2.product_id where u3.name = ?"; + String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 join o_product u3 on u3.id = u2.product_id where u3.name = ?"; assertThat(sqlOf(query, 1)).contains(expectedSql); } else { - String expectedSql = "select distinct t0.id, t0.name from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id join o_order_detail u2 on u2.order_id = u1.id join o_product u3 on u3.id = u2.product_id where u3.name = ?"; + String expectedSql = "select distinct t0.id, t0.name from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 join o_product u3 on u3.id = u2.product_id where u3.name = ?"; assertThat(sqlOf(query, 1)).contains(expectedSql); } @@ -55,10 +55,10 @@ public class TestImplicitJoinOnParentRelationship extends BaseTestCase { query.findList(); if (isPostgres()) { - String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id left join o_order_detail u2 on u2.order_id = u1.id left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)"; + String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null left join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)"; assertThat(sqlOf(query, 1)).contains(expectedSql); } else { - String expectedSql = "select distinct t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id left join o_order_detail u2 on u2.order_id = u1.id left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)"; + String expectedSql = "select distinct t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null left join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)"; assertThat(sqlOf(query, 1)).contains(expectedSql); } } @@ -76,11 +76,11 @@ public class TestImplicitJoinOnParentRelationship extends BaseTestCase { query.findList(); if (isPostgres()) { - String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id left join o_order_detail u2 on u2.order_id = u1.id left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)"; + String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null left join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)"; assertThat(sqlOf(query, 1)).contains(expectedSql); } else { - String expectedSql = "select distinct t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id left join o_order_detail u2 on u2.order_id = u1.id left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)"; + String expectedSql = "select distinct t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null left join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)"; assertThat(sqlOf(query, 1)).contains(expectedSql); } } diff --git a/ebean-core/src/test/java/org/tests/query/TestManyWhereJoin.java b/ebean-core/src/test/java/org/tests/query/TestManyWhereJoin.java index 250a32709..b90751c83 100644 --- a/ebean-core/src/test/java/org/tests/query/TestManyWhereJoin.java +++ b/ebean-core/src/test/java/org/tests/query/TestManyWhereJoin.java @@ -41,7 +41,7 @@ public class TestManyWhereJoin extends BaseTestCase { } assertThat(sql).contains("join o_order "); assertThat(sql).contains(".status = ?"); - assertThat(sql).contains("t0.id, t0.status from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id where u1.status = ?"); + assertThat(sql).contains("t0.id, t0.status from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null where u1.status = ?"); } @Test diff --git a/ebean-core/src/test/java/org/tests/query/TestQueryFilterMany.java b/ebean-core/src/test/java/org/tests/query/TestQueryFilterMany.java index be58fb8c4..01275cfa0 100644 --- a/ebean-core/src/test/java/org/tests/query/TestQueryFilterMany.java +++ b/ebean-core/src/test/java/org/tests/query/TestQueryFilterMany.java @@ -183,7 +183,7 @@ public class TestQueryFilterMany extends BaseTestCase { List sqlList = LoggedSqlCollector.stop(); assertEquals(1, sqlList.size()); - assertThat(sqlList.get(0)).contains("from o_customer t0 left join o_order t1 on t1.kcustomer_id = t0.id and t1.order_date is not null left join o_customer t2 on t2.id = t1.kcustomer_id where exists (select 1 from o_order x where x.kcustomer_id = t0.id) and 1=0 order by t0.id"); + assertThat(sqlList.get(0)).contains("from o_customer t0 left join o_order t1 on t1.kcustomer_id = t0.id and t1.order_date is not null left join o_customer t2 on t2.id = t1.kcustomer_id where exists (select 1 from o_order x where x.kcustomer_id = t0.id and x.order_date is not null) and 1=0 order by t0.id"); } @Test diff --git a/ebean-core/src/test/java/org/tests/query/TestQueryIsNull.java b/ebean-core/src/test/java/org/tests/query/TestQueryIsNull.java index d2689337b..f80aee9f5 100644 --- a/ebean-core/src/test/java/org/tests/query/TestQueryIsNull.java +++ b/ebean-core/src/test/java/org/tests/query/TestQueryIsNull.java @@ -1,14 +1,15 @@ package org.tests.query; import io.ebean.BaseTestCase; -import io.ebean.Ebean; +import io.ebean.DB; import io.ebean.Query; import org.tests.model.basic.Order; import org.tests.model.basic.ResetBasicData; import org.tests.model.m2m.Role; import org.junit.Test; -import static org.junit.Assert.assertTrue; + +import static org.assertj.core.api.Assertions.assertThat; public class TestQueryIsNull extends BaseTestCase { @@ -17,90 +18,90 @@ public class TestQueryIsNull extends BaseTestCase { public void queryShouldContainIsNullOnColumn() { ResetBasicData.reset(); - Query query = Ebean.find(Order.class).where().isNull("customerName").query(); + Query query = DB.find(Order.class).where().isNull("customerName").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains("name is null")); + assertThat(query.getGeneratedSql()).contains("name is null"); } @Test public void isNotNull_when_OneToMany_expect_existsSubquery() { ResetBasicData.reset(); - Query query = Ebean.find(Order.class).where().isNotNull("details").query(); + Query query = DB.find(Order.class).where().isNotNull("details").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains(" where exists (select 1 from o_order_detail x where x.order_id = t0.id)")); + assertThat(query.getGeneratedSql()).contains(" where exists (select 1 from o_order_detail x where x.order_id = t0.id and x.id > 0)"); } @Test public void isNotEmpty_when_OneToMany_expect_existsSubquery() { ResetBasicData.reset(); - Query query = Ebean.find(Order.class).where().isNotEmpty("details").query(); + Query query = DB.find(Order.class).where().isNotEmpty("details").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains(" where exists (select 1 from o_order_detail x where x.order_id = t0.id)")); + assertThat(query.getGeneratedSql()).contains(" where exists (select 1 from o_order_detail x where x.order_id = t0.id and x.id > 0)"); } @Test public void isNull_when_OneToMany_expect_notExistsSubquery() { ResetBasicData.reset(); - Query query = Ebean.find(Order.class).where().isNull("details").query(); + Query query = DB.find(Order.class).where().isNull("details").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains(" where not exists (select 1 from o_order_detail x where x.order_id = t0.id)")); + assertThat(query.getGeneratedSql()).contains(" where not exists (select 1 from o_order_detail x where x.order_id = t0.id and x.id > 0)"); } @Test public void isEmpty_when_OneToMany_expect_notExistsSubquery() { ResetBasicData.reset(); - Query query = Ebean.find(Order.class).where().isEmpty("details").query(); + Query query = DB.find(Order.class).where().isEmpty("details").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains(" where not exists (select 1 from o_order_detail x where x.order_id = t0.id)")); + assertThat(query.getGeneratedSql()).contains(" where not exists (select 1 from o_order_detail x where x.order_id = t0.id and x.id > 0)"); } @Test public void isEmpty_when_ManyToMany_expect_notExistsSubqueryAndNoJoin() { ResetBasicData.reset(); - Query query = Ebean.find(Role.class).where().isEmpty("permissions").query(); + Query query = DB.find(Role.class).where().isEmpty("permissions").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains("from mt_role t0 where not exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)")); + assertThat(query.getGeneratedSql()).contains("from mt_role t0 where not exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)"); } @Test public void isNull_when_ManyToMany_expect_notExistsSubqueryAndNoJoin() { ResetBasicData.reset(); - Query query = Ebean.find(Role.class).where().isNull("permissions").query(); + Query query = DB.find(Role.class).where().isNull("permissions").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains("from mt_role t0 where not exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)")); + assertThat(query.getGeneratedSql()).contains("from mt_role t0 where not exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)"); } @Test public void isNotEmpty_when_ManyToMany_expect_existsSubqueryAndNoJoin() { ResetBasicData.reset(); - Query query = Ebean.find(Role.class).where().isNotEmpty("permissions").query(); + Query query = DB.find(Role.class).where().isNotEmpty("permissions").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains("from mt_role t0 where exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)")); + assertThat(query.getGeneratedSql()).contains("from mt_role t0 where exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)"); } @Test public void isNotNull_when_ManyToMany_expect_existsSubqueryAndNoJoin() { ResetBasicData.reset(); - Query query = Ebean.find(Role.class).where().isNotNull("permissions").query(); + Query query = DB.find(Role.class).where().isNotNull("permissions").query(); query.findList(); - assertTrue(query.getGeneratedSql().contains("from mt_role t0 where exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)")); + assertThat(query.getGeneratedSql()).contains("from mt_role t0 where exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)"); } } diff --git a/ebean-core/src/test/java/org/tests/query/other/TestQueryRawExpressionMany.java b/ebean-core/src/test/java/org/tests/query/other/TestQueryRawExpressionMany.java index af52302d2..55901a720 100644 --- a/ebean-core/src/test/java/org/tests/query/other/TestQueryRawExpressionMany.java +++ b/ebean-core/src/test/java/org/tests/query/other/TestQueryRawExpressionMany.java @@ -30,6 +30,6 @@ public class TestQueryRawExpressionMany extends BaseTestCase { query.findCount(); List sql = LoggedSqlCollector.stop(); - assertThat(trimSql(sql.get(0), 1)).contains("select count(*) from ( select distinct t0.id from o_order t0 left join o_order_detail t1 on t1.order_id = t0.id where t1.order_qty = ?)"); + assertThat(trimSql(sql.get(0), 1)).contains("select count(*) from ( select distinct t0.id from o_order t0 left join o_order_detail t1 on t1.order_id = t0.id and t1.id > 0 where t1.order_qty = ?)"); } } diff --git a/ebean-core/src/test/java/org/tests/query/other/TestQueryRowCountWithMany.java b/ebean-core/src/test/java/org/tests/query/other/TestQueryRowCountWithMany.java index a9d18476b..44c8a78f0 100644 --- a/ebean-core/src/test/java/org/tests/query/other/TestQueryRowCountWithMany.java +++ b/ebean-core/src/test/java/org/tests/query/other/TestQueryRowCountWithMany.java @@ -69,7 +69,7 @@ public class TestQueryRowCountWithMany extends BaseTestCase { assertEquals(list.size(), rowCount); assertEquals(2, sqlLogged.size()); assertThat(trimSql(sqlLogged.get(1), 1)).contains( - "select count(*) from ( select distinct t0.id from o_order t0 join o_order_detail u1 on u1.order_id = t0.id where u1.product_id = ?)"); + "select count(*) from ( select distinct t0.id from o_order t0 join o_order_detail u1 on u1.order_id = t0.id and u1.id > 0 where u1.product_id = ?)"); } @@ -95,7 +95,7 @@ public class TestQueryRowCountWithMany extends BaseTestCase { List sqlLogged = LoggedSqlCollector.stop(); assertEquals(1, sqlLogged.size()); - assertThat(trimSql(sqlLogged.get(0), 1)).contains("select count(*) from ( select distinct t0.id from o_order t0 join o_order_detail u1 on u1.order_id = t0.id where u1.product_id = ?)"); + assertThat(trimSql(sqlLogged.get(0), 1)).contains("select count(*) from ( select distinct t0.id from o_order t0 join o_order_detail u1 on u1.order_id = t0.id and u1.id > 0 where u1.product_id = ?)"); query.findList(); } diff --git a/ebean-core/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java b/ebean-core/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java index f476ed084..dd230b866 100644 --- a/ebean-core/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java +++ b/ebean-core/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java @@ -64,7 +64,7 @@ public class TestQuerySingleAttribute extends BaseTestCase { assertThat(sqlOf(query)).contains("select r1.attribute_, count(*) " + "from (select distinct t0.id, t0.name as attribute_ " + - "from o_customer t0 left join contact u1 on u1.customer_id = t0.id left join o_order u2 on u2.kcustomer_id = t0.id " + + "from o_customer t0 left join contact u1 on u1.customer_id = t0.id left join o_order u2 on u2.kcustomer_id = t0.id and u2.order_date is not null " + "where t0.name = ? and (u2.status = ? or u1.first_name = ?)) r1 " + "group by r1.attribute_ " + "order by count(*) desc, r1.attribute_"); diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java index 3a7457a56..6530611dd 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java @@ -149,7 +149,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor { @Override public void visitMany(BeanPropertyAssocMany p) { - if (p.hasJoinTable() && p.getMappedBy() == null) { + if (p.createJoinTable()) { // only create on other 'owning' side // build the create table and fkey constraints