Skip to content

Bugfix/3876 - #3877

Open
AntoineDuComptoirDesPharmacies wants to merge 4 commits into
ebean-orm:masterfrom
LeComptoirDesPharmacies:bugfix/3876
Open

Bugfix/3876#3877
AntoineDuComptoirDesPharmacies wants to merge 4 commits into
ebean-orm:masterfrom
LeComptoirDesPharmacies:bugfix/3876

Conversation

@AntoineDuComptoirDesPharmacies

Copy link
Copy Markdown
Contributor

Aim to fix the following issue : #3876

Would like to know if this is the good direction or if there is some downside of disabling flush at this step of the execution.

Thanks !

… from a persist callback

A join entity owns the foreign key to the bean its delete cascades to, so the join row
has to be deleted first. When a BeanPersistController writes to the database from
preDelete, that write flushes the batch from inside the flush that is already running :
the outer flush has taken the join rows out of their bean holder, so the inner flush
finds only the assets and executes them first, which fails on the foreign key.

    BatchControl flush [DcoLink:0 d:2, DcoAsset:1 d:2]                  <- outer flush
    BatchControl flush [DcoLink:0 d:0, DcoAsset:1 d:2, DcoAudit:2 i:1]  <- from preDelete

The scenario is fixed as a side effect of a2f954a (ebean-orm#3830, released in 18.3.0) which
moved controllerPreDelete() ahead of the cascade. The test pins that down : it fails
with a DataIntegrityException on a2f954a~1 and passes on master. The graph is fetched
up front on purpose, a lazy load would flush the batch on its own and hide the ordering.
…elete on a self referencing tree

The shape reported on the issue in 2019 : a container cascades the delete down a tree of
TreeBean, and the deletes are not issued deepest first. On 18.4.0 :

    delete from dco_tree where id in (?)      -- the root, whose children are still there
    delete from dco_tree where id in (?,?,?)
    delete from dco_tree where id in (?,?)

Referential integrity constraint violation: FK_DCO_TREE_PARENT_ID.

This is a different defect from the batch reordering fixed by a2f954a : nothing is batched
here, the recursion itself walks the tree in the wrong order. Disabled so it does not break
the build, remove the annotation to see the failure.
… batch mid-execution

ebean-orm#3148 stopped a query performed from a callback from flushing the batch that is already
executing : BatchControl.executeNow disables flushOnQuery for the duration. A persist done
from the same callback is not covered. It reaches BatchControl.executeOrQueue, which flushes,
and the statements queued behind the one currently executing are issued early.

Saving a parent/child graph in batch while an audit row is written from preInsert issues the
children before their parent has an id :

    insert into dco_link (parent_id, asset_id) values (?,?)
      NULL not allowed for column "PARENT_ID"

Same defect on the delete side, where the join rows are issued after the beans they reference
(ebean-orm#1852, ebean-orm#3185) — that path no longer reproduces since a2f954a moved controllerPreDelete()
ahead of the cascade, but only preDelete was moved, so preInsert and preUpdate still run
inside the flush.

Guard executeOrQueue with the same reasoning as the existing flushOnQuery guard : while the
batch is executing, queue rather than flush. The statements added meanwhile are picked up by
the do/while loop in executeAll().
@rbygrave

Copy link
Copy Markdown
Member

Looks good to me. I am happy with it.

Only pondering if executeStatementOrBatch() has a similar need or not?

…ck also flushes mid-execution

executeStatementOrBatch() flushes on (batchFlushOnMixed && !isBeansEmpty()), and persistedBeans
is only cleared once executeAll() returns, so during the batch execution that condition holds and
a SqlUpdate run from a BeanPersistController callback re-enters the flush the same way a save does.

Reproduced with the same test, the callback running a SqlUpdate instead of a save :

    insert into dco_link (parent_id, asset_id) values (?,?)
      NULL not allowed for column "PARENT_ID"

The second flush of that method, on pstmtHolder.maxSize() >= batchSize, is left untouched : it can
only trigger when the pstmt holder fills up mid-execution and there is no test covering it.
@AntoineDuComptoirDesPharmacies

Copy link
Copy Markdown
Contributor Author

Good catch ! Yes indeed it will produce the same problem.

I added a test to cover the use case and corrected the code. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants