diff --git a/src/main/java/io/ebeaninternal/server/deploy/AssocOneHelpRefExported.java b/src/main/java/io/ebeaninternal/server/deploy/AssocOneHelpRefExported.java index 81de5a036..874231645 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/AssocOneHelpRefExported.java +++ b/src/main/java/io/ebeaninternal/server/deploy/AssocOneHelpRefExported.java @@ -7,8 +7,14 @@ import io.ebeaninternal.server.query.SqlJoinType; */ class AssocOneHelpRefExported extends AssocOneHelp { - public AssocOneHelpRefExported(BeanPropertyAssocOne property) { + private final boolean softDelete; + + private final String softDeletePredicate; + + AssocOneHelpRefExported(BeanPropertyAssocOne property) { super(property); + this.softDelete = property.targetDescriptor.isSoftDelete(); + this.softDeletePredicate = (softDelete) ? property.targetDescriptor.getSoftDeletePredicate("") : null; } /** @@ -28,6 +34,10 @@ class AssocOneHelpRefExported extends AssocOneHelp { void appendFrom(DbSqlContext ctx, SqlJoinType joinType) { String relativePrefix = ctx.getRelativePrefix(property.getName()); - property.tableJoin.addJoin(joinType, relativePrefix, ctx); + if (softDelete && !ctx.isIncludeSoftDelete()) { + property.tableJoin.addJoin(joinType, relativePrefix, ctx, softDeletePredicate); + } else { + property.tableJoin.addJoin(joinType, relativePrefix, ctx); + } } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/DbSqlContext.java b/src/main/java/io/ebeaninternal/server/deploy/DbSqlContext.java index 0eca9a112..3fd4e3666 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/DbSqlContext.java +++ b/src/main/java/io/ebeaninternal/server/deploy/DbSqlContext.java @@ -12,8 +12,6 @@ public interface DbSqlContext { */ void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2, String inheritance); - void pushSecondaryTableAlias(String alias); - /** * Push the current table alias onto the stack. */ @@ -91,11 +89,6 @@ public interface DbSqlContext { */ String getContent(); - /** - * Return the current join node. - */ - String peekJoin(); - /** * Push a join node onto the stack. */ @@ -125,6 +118,11 @@ public interface DbSqlContext { */ void appendHistorySysPeriod(); + /** + * Return true if the query includes soft deleted rows. + */ + boolean isIncludeSoftDelete(); + /** * Return true if the query is a 'asDraft' query. */ @@ -139,4 +137,5 @@ public interface DbSqlContext { * Append 'for update' lock hints on FROM clause (sql server only). */ void appendFromForUpdate(); + } diff --git a/src/main/java/io/ebeaninternal/server/deploy/TableJoin.java b/src/main/java/io/ebeaninternal/server/deploy/TableJoin.java index 6a23e300d..eedb2b547 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/TableJoin.java +++ b/src/main/java/io/ebeaninternal/server/deploy/TableJoin.java @@ -126,6 +126,16 @@ public final class TableJoin { return type; } + public SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx, String predicate) { + String[] names = SplitName.split(prefix); + String a1 = ctx.getTableAlias(names[0]); + String a2 = ctx.getTableAlias(prefix); + + SqlJoinType returnJoinType = addJoin(joinType, a1, a2, ctx); + ctx.append("and ").append(a2).append(predicate); + return returnJoinType; + } + public SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx) { String[] names = SplitName.split(prefix); diff --git a/src/main/java/io/ebeaninternal/server/query/DefaultDbSqlContext.java b/src/main/java/io/ebeaninternal/server/query/DefaultDbSqlContext.java index f40b9c2cf..cb5ae732e 100644 --- a/src/main/java/io/ebeaninternal/server/query/DefaultDbSqlContext.java +++ b/src/main/java/io/ebeaninternal/server/query/DefaultDbSqlContext.java @@ -70,6 +70,11 @@ class DefaultDbSqlContext implements DbSqlContext { this.fromForUpdate = fromForUpdate; } + @Override + public boolean isIncludeSoftDelete() { + return alias.isIncludeSoftDelete(); + } + @Override public void appendFromForUpdate() { if (fromForUpdate != null) { @@ -99,11 +104,6 @@ class DefaultDbSqlContext implements DbSqlContext { return encryptedProps.toArray(new BeanProperty[encryptedProps.size()]); } - @Override - public String peekJoin() { - return joinStack.peek(); - } - @Override public void popJoin() { joinStack.pop(); @@ -206,11 +206,6 @@ class DefaultDbSqlContext implements DbSqlContext { return alias.getTableAliasManyWhere(prefix); } - @Override - public void pushSecondaryTableAlias(String alias) { - tableAliasStack.push(alias); - } - @Override public String getRelativePrefix(String propName) { diff --git a/src/main/java/io/ebeaninternal/server/query/SqlTreeAlias.java b/src/main/java/io/ebeaninternal/server/query/SqlTreeAlias.java index ea583c833..39895df70 100644 --- a/src/main/java/io/ebeaninternal/server/query/SqlTreeAlias.java +++ b/src/main/java/io/ebeaninternal/server/query/SqlTreeAlias.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.query; import io.ebean.util.SplitName; +import io.ebeaninternal.api.SpiQuery; import java.util.HashMap; import java.util.HashSet; @@ -17,6 +18,8 @@ class SqlTreeAlias { private static final Pattern TABLE_ALIAS_REPLACE = Pattern.compile("${}", Pattern.LITERAL); + private final SpiQuery.TemporalMode temporalMode; + private int counter; private int manyWhereCounter; @@ -33,8 +36,9 @@ class SqlTreeAlias { private final String rootTableAlias; - SqlTreeAlias(String rootTableAlias) { + SqlTreeAlias(String rootTableAlias, SpiQuery.TemporalMode temporalMode) { this.rootTableAlias = rootTableAlias; + this.temporalMode = temporalMode; } /** @@ -211,4 +215,8 @@ class SqlTreeAlias { boolean isIncludeJoins() { return !aliasMap.isEmpty() || !manyWhereAliasMap.isEmpty(); } + + boolean isIncludeSoftDelete() { + return temporalMode == SpiQuery.TemporalMode.SOFT_DELETED; + } } diff --git a/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java b/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java index e7682b8f8..0dcf9f773 100644 --- a/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java @@ -110,7 +110,7 @@ public final class SqlTreeBuilder { this.queryDetail = query.getDetail(); this.predicates = predicates; - this.alias = new SqlTreeAlias(request.getBaseTableAlias()); + this.alias = new SqlTreeAlias(request.getBaseTableAlias(), temporalMode); this.distinctOnPlatform = builder.isPlatformDistinctOn(); String fromForUpdate = builder.fromForUpdate(query); diff --git a/src/test/java/org/tests/model/onetoone/OtoSdChild.java b/src/test/java/org/tests/model/onetoone/OtoSdChild.java new file mode 100644 index 000000000..2d60a7dfa --- /dev/null +++ b/src/test/java/org/tests/model/onetoone/OtoSdChild.java @@ -0,0 +1,73 @@ +package org.tests.model.onetoone; + +import io.ebean.Finder; +import io.ebean.annotation.SoftDelete; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Version; + +@Entity +public class OtoSdChild { + + public static Finder find = new Finder<>(OtoSdChild.class); + + @Id + long id; + + String child; + + @SoftDelete + boolean deleted; + + @OneToOne + OtoSdMaster master; + + @Version + long version; + + public OtoSdChild(String child) { + this.child = child; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getChild() { + return child; + } + + public void setChild(String child) { + this.child = child; + } + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } + + public OtoSdMaster getMaster() { + return master; + } + + public void setMaster(OtoSdMaster master) { + this.master = master; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } +} diff --git a/src/test/java/org/tests/model/onetoone/OtoSdMaster.java b/src/test/java/org/tests/model/onetoone/OtoSdMaster.java new file mode 100644 index 000000000..95e5f947c --- /dev/null +++ b/src/test/java/org/tests/model/onetoone/OtoSdMaster.java @@ -0,0 +1,55 @@ +package org.tests.model.onetoone; + +import io.ebean.Finder; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Version; + +@Entity +public class OtoSdMaster { + + public static Finder find = new Finder<>(OtoSdMaster.class); + + @Id + long id; + + String name; + + @OneToOne(cascade = CascadeType.ALL, mappedBy = "master")//, fetch = FetchType.LAZY) + OtoSdChild child; + + @Version + long version; + + public OtoSdMaster(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public OtoSdChild getChild() { + return child; + } + + public void setChild(OtoSdChild child) { + this.child = child; + } + +} diff --git a/src/test/java/org/tests/model/onetoone/TestOneToOneSoftDeleteChild.java b/src/test/java/org/tests/model/onetoone/TestOneToOneSoftDeleteChild.java new file mode 100644 index 000000000..326f822dc --- /dev/null +++ b/src/test/java/org/tests/model/onetoone/TestOneToOneSoftDeleteChild.java @@ -0,0 +1,47 @@ +package org.tests.model.onetoone; + +import io.ebean.BaseTestCase; +import io.ebean.Ebean; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestOneToOneSoftDeleteChild extends BaseTestCase { + + @Test + public void deleteChild() { + + + OtoSdChild child = new OtoSdChild("c1"); + OtoSdMaster master = new OtoSdMaster("m1"); + master.setChild(child); + + Ebean.save(master); + + + verifyBeforeDelete(master, child); + + Ebean.delete(child); + + verifyAfterDelete(master, child); + } + + private void verifyBeforeDelete(OtoSdMaster parent, OtoSdChild child) { + assertThat(OtoSdMaster.find.byId(parent.getId()).getChild().getId()) + .isEqualTo(child.getId()); + + assertThat( + OtoSdChild.find.byId(child.getId()).getMaster().getId()) + .isEqualTo(parent.getId()); + } + + private void verifyAfterDelete(OtoSdMaster parent, OtoSdChild child) { + // After delete, finding child by id should return null + assertThat(OtoSdChild.find.byId(child.getId())) + .isNull(); + + // After delete, getting linked child from parent should return null + assertThat(OtoSdMaster.find.byId(parent.getId()).getChild()) + .isNull(); + } +}