field_purge_batch() should be in core components, not in field module

Created on 20 April 2020, over 4 years ago
Updated 12 August 2024, 5 months ago

field_purge_batch() takes care of deleting Field API tables that have been renamed for batch deletion. But it's in Field module, which is optional.

However, code-defined fields can have dedicated tables: multi-valued entity base fields, and bundle fields, for instance.

These fields can exist even if Field module isn't enabled.

Therefore, it looks to me like in that case, the field tables would never get cleaned up.

πŸ› Bug report
Status

Active

Version

11.0 πŸ”₯

Component
FieldΒ  β†’

Last updated 1 day ago

Created by

πŸ‡¬πŸ‡§United Kingdom joachim

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    Should this be reopened based on the last few comments?

  • πŸ‡¦πŸ‡ΊAustralia acbramley

    Doesn't seem like a bug to me, please correct me and provide some more info if not. This has come up in BSI a few times.

  • Status changed to Active 5 months ago
  • πŸ‡¬πŸ‡§United Kingdom joachim

    The docs say:

     * When a bundle, field or field storage is deleted, it is not practical to
     * perform those operations immediately on every affected entity in a single
     * page request; there could be thousands or millions of them. Instead, the
     * appropriate field data items, fields, and/or field storages are marked as
     * deleted so that subsequent load or query operations will not return them.
     * Later, a separate process cleans up, or "purges", the marked-as-deleted data
     * by going through the three-step process described above and, finally,
     * removing deleted field storage and field records.
    

    This situation can be created without field module being present:

    1 define an entity type which uses bundles with a config entity
    2 define a multi-valued base field on the entity type. This will therefore get a field table
    3 create a bundle entity
    4 create some entities
    5 delete the bundle entity

    I can make a kernel test which sets this up, but I think this is actually showing a deeper problem -- nothing is going to mark the field table's items as 'deleted' anyway, since SqlContentEntityStorage and FieldDefinitionListener are working with field storage and field definitions.

    <?php
    
    declare(strict_types=1);
    
    namespace Drupal\KernelTests\Core\Field;
    
    use Drupal\Core\Field\BaseFieldDefinition;
    use Drupal\KernelTests\KernelTestBase;
    
    /**
     * Tests TODO.
     *
     * @group Entity
     */
    class BulkDeleteTest extends KernelTestBase {
    
      /**
       * {@inheritdoc}
       */
      protected static $modules = [
        'entity_test',
        'system',
        'user',
      ];
    
      /**
       * The entity type manager.
       *
       * @var \Drupal\Core\Entity\EntityTypeManagerInterface
       */
      protected $entityTypeManager;
    
      /**
       * {@inheritdoc}
       */
      protected function setUp(): void {
        parent::setUp();
    
        $this->entityTypeManager = $this->container->get('entity_type.manager');
    
        $definitions = [];
        $definitions['multi'] = BaseFieldDefinition::create('string')
          ->setLabel('Multiple')
          ->setCardinality(2);
    
        $this->container->get('state')->set('entity_test.additional_base_field_definitions', $definitions);
    
        $this->entityTypeManager->clearCachedDefinitions();
    
        $this->installEntitySchema('entity_test');
        $this->installEntitySchema('entity_test_bundle');
      }
    
      public function testDeleteMultiBaseFieldTables() {
        // Create a bundle.
        $alpha = $this->entityTypeManager->getStorage('entity_test_bundle')->create([
          'id' => 'alpha',
        ]);
        $alpha->save();
    
        // Create an entity.
        $storage = $this->entityTypeManager->getStorage('entity_test');
        $entity = $storage->create([
          'type' => 'alpha',
          'name' => 'One',
          'multi' => [
            'one',
            'two',
          ],
        ]);
        $entity->save();
    
        $database = \Drupal::database();
        $query = $database->query("SELECT count(*) FROM {entity_test__multi}");
        $field_table_row_count = $query->fetchField();
        $this->assertEquals(2, $field_table_row_count);
    
        $alpha->delete();
    
        // What should the entity and field tables contain now?
        $query = $database->query("SELECT * FROM {entity_test}");
        $rows = $query->fetchAll();
        dump($rows);
    
        $query = $database->query("SELECT * FROM {entity_test__multi}");
        $rows = $query->fetchAll();
        dump($rows);
      }
    
    }
    

    Another reason for moving field_purge_batch() to core-Field though is that all of the calls it makes are to core-Field:

      /** @var \Drupal\Core\Field\DeletedFieldsRepositoryInterface $deleted_fields_repository */
      $deleted_fields_repository = \Drupal::service('entity_field.deleted_fields_repository');
    

    apart from field_purge_field_storage() which lives in the same file.

Production build 0.71.5 2024