From 7d85398139c227eb9005cadc351165d4fab7141a Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Mon, 7 Dec 2015 15:21:22 +1300 Subject: [PATCH] #483 - ENH: Draftable - Add FK from live table back to draft table (for top level @Draftable tables) --- .../model/build/ModelBuildContext.java | 14 +++++++- .../build/ModelBuildIntersectionTable.java | 2 +- .../build/ModelBuildPropertyVisitor.java | 2 +- .../server/core/PersistRequestBean.java | 34 ++++++++++++++++++- .../server/persist/DefaultPersister.java | 29 ++++++++++++---- 5 files changed, 70 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildContext.java b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildContext.java index 0d2822024..0f20e7ee5 100644 --- a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildContext.java +++ b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildContext.java @@ -10,6 +10,7 @@ import com.avaje.ebeaninternal.server.deploy.BeanProperty; import com.avaje.ebeaninternal.server.type.ScalarType; import java.util.Collection; +import java.util.List; /** * The context used during DDL generation. @@ -134,11 +135,22 @@ public class ModelBuildContext { /** * Create the draft table for a given table. */ - public void createDraft(MTable table) { + public void createDraft(MTable table, boolean draftable) { MTable draftTable = table.createDraftTable(); draftTable.setPkName(primaryKeyName(draftTable.getName())); + if (draftable) { + // Add a FK from @Draftable live table back to it's draft table) + List pkCols = table.primaryKeyColumns(); + if (pkCols.size() == 1) { + // only doing this for single column PK at this stage + MColumn pk = pkCols.get(0); + pk.setReferences(draftTable.getName() + "." + pk.getName()); + pk.setForeignKeyName(foreignKeyConstraintName(table.getName(), pk.getName(), 0)); + } + } + int fkCount = 0; int ixCount = 0; int uqCount = 0; diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildIntersectionTable.java b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildIntersectionTable.java index 926302b45..97c24633b 100644 --- a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildIntersectionTable.java +++ b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildIntersectionTable.java @@ -44,7 +44,7 @@ public class ModelBuildIntersectionTable { buildFkConstraints(); if (manyProp.getTargetDescriptor().isDraftable()) { - ctx.createDraft(intersectionTable); + ctx.createDraft(intersectionTable, false); } } diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildPropertyVisitor.java b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildPropertyVisitor.java index 8a72d21d0..8878e7fdb 100644 --- a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildPropertyVisitor.java +++ b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildPropertyVisitor.java @@ -112,7 +112,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor { private void addDraftTable() { if (beanDescriptor.isDraftable() || beanDescriptor.isDraftableElement()) { // create a 'Draft' table which looks very similar (change PK, FK etc) - ctx.createDraft(table); + ctx.createDraft(table, !beanDescriptor.isDraftableElement()); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java index a139912c1..025493a35 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java @@ -151,12 +151,29 @@ public final class PersistRequestBean extends PersistRequest implements BeanP } this.concurrencyMode = beanDescriptor.getConcurrencyMode(intercept); this.publish = publish; - if (!publish && beanDescriptor.isDraftable()) { + if (isMarkDraftDirty(publish)) { beanDescriptor.setDraftDirty(entityBean, true); } this.dirty = intercept.isDirty(); } + /** + * Return true if the draftDirty property should be set to true for this request. + */ + private boolean isMarkDraftDirty(boolean publish) { + return !publish && type != Type.DELETE && beanDescriptor.isDraftable(); + } + + /** + * Set the transaction from prior persist request. + * Only used when hard deleting draft & associated live beans. + */ + public void setTrans(SpiTransaction transaction) { + this.transaction = transaction; + this.createdTransaction = false; + this.persistCascade = transaction.isPersistCascade(); + } + /** * Init the transaction and also check for batch on cascade escalation. */ @@ -437,6 +454,21 @@ public final class PersistRequestBean extends PersistRequest implements BeanP return beanDescriptor.isDraftable(); } + /** + * Return true if this request is a hard delete of a draftable bean. + * If this is true Ebean is expected to auto-publish and delete the associated live bean. + */ + public boolean isHardDeleteDraft() { + if (type == Type.DELETE && beanDescriptor.isDraftable() && !beanDescriptor.isDraftableElement()) { + // deleting a top level draftable bean + if (!beanDescriptor.isDraftInstance(entityBean)) { + throw new PersistenceException("Explicit Delete is not allowed on a 'live' bean - only draft beans"); + } + return true; + } + return false; + } + /** * Checks for @Draftable entity beans with @Draft property that the bean is a 'draft'. * Save or Update is not allowed to execute using 'live' beans - must use publish(). diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java index 658d644d3..fc7bf1315 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java @@ -502,21 +502,31 @@ public final class DefaultPersister implements Persister { public boolean delete(EntityBean bean, Transaction t, boolean permanent) { Type deleteType = permanent ? Type.DELETE_PERMANENT : Type.DELETE; - PersistRequestBean request = createRequest(bean, t, deleteType); - boolean deleted = deleteRequest(request); + PersistRequestBean originalRequest = createRequest(bean, t, deleteType); - if (request.isDraftable() && request.getType() == Type.DELETE) { - // we have just deleting a draft bean so now we need to delete the - // associated 'live' bean. This is effectively an 'automatic publish'. - deleteRequest(createPublishRequest(request.createReference(), t, Type.DELETE_PERMANENT, true)); + if (originalRequest.isHardDeleteDraft()) { + // a hard delete of a draftable bean so first we need to delete the associated 'live' bean + // due to FK constraint and then after that execute the original delete of the draft bean + return deleteRequest(createPublishRequest(originalRequest.createReference(), t, Type.DELETE_PERMANENT, true), originalRequest); + + } else { + // normal delete or soft delete + return deleteRequest(originalRequest); } - return deleted; } /** * Execute the delete request returning true if a delete occurred. */ private boolean deleteRequest(PersistRequestBean req) { + return deleteRequest(req, null); + } + + /** + * Execute the delete request support a second delete request for live and draft permanent delete. + * A common transaction is used across both requests. + */ + private boolean deleteRequest(PersistRequestBean req, PersistRequestBean draftReq) { if (req.isRegisteredForDeleteBean()) { // skip deleting bean. Used where cascade is on @@ -530,6 +540,11 @@ public final class DefaultPersister implements Persister { try { req.initTransIfRequiredWithBatchCascade(); boolean deleted = delete(req); + if (draftReq != null) { + // delete the 'draft' bean ('live' bean deleted first) + draftReq.setTrans(req.getTransaction()); + deleted = delete(draftReq); + } req.commitTransIfRequired(); req.flushBatchOnCascade();