Problem/Motivation

SqlContentEntityStorage::save() and delete() wrap their work in startTransaction(). ContentEntityStorageBase::deleteRevision() does not, although it deletes from the revision table, the revision data table and every dedicated field table, and cascades into field type code and hook_entity_revision_delete(). Each statement commits on its own, so an interrupted process leaves the deletion half done.

If the deleted revision is the one the base table points at, the entity becomes permanently unloadable: buildQuery() INNER JOINs the revision table, so load() and loadByProperties() return nothing although the base row is there, and saving throws:

EntityStorageException: Update existing 'paragraph' entity while changing the ID is not supported.

Only direct SQL can repair it.

Steps to reproduce

  1. Create a revisionable entity with several revisions.
  2. In a test module, stop the process inside the delete cascade:
    function mymodule_entity_revision_delete(EntityInterface $entity) {
      exit(1);
    }
  3. Call $storage->deleteRevision($vid) for a non-default revision.
  4. The revision row is gone, the revision data row remains. Nothing rolled back.

Proposed resolution

Wrap deleteRevision() in a transaction, as save() and delete() already are. This also makes the cascade into composite children atomic.

Remaining tasks

None.

User interface changes

None.

Introduced terminology

None.

API changes

Exceptions thrown while deleting a revision are wrapped in EntityStorageException.

Data model changes

None.

Release notes snippet

None.

Issue fork drupal-3631485

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

alex.bukach created an issue. See original summary.

alex.bukach’s picture

Status: Active » Needs review