I am building two modules: one that provides a field type, and one that provides an entity type that automatically creates an instance of that field on the entity during hook_enable(). In hook_uninstall of the entity's module, I'm calling field_delete_instance() and field_delete_field() to mark the field and instance as "deleted" so it can be cleaned up on the next cron run.

When I uninstall the entity module, it successfully marks the field and instance as "deleted." When I run cron, however, the field is deleted from the {field_config} table, but the instance is NOT deleted from the {field_config_instance} table.

After a little digging, I think it is impossible for this to work as expected, based on the code in field.crud.inc. Here's why...

field_purge_batch() is responsible for cleaning up fields and field instances that have been marked as "deleted" in the database. The first thing it does is loads up all the "deleted" instances:

  // Retrieve all deleted field instances. We cannot use field_info_instances()
  // because that function does not return deleted instances.
  $instances = field_read_instances(array('deleted' => 1), array('include_deleted' => 1));

field_read_instances() is used to look up the field instances that are marked as "deleted". However, the function filters out any instances for entity types that don't exist, so they are not returned back to the field_purge_batch() function, and are never deleted.

Here is the code in field_read_instances() that filters out disabled entities:

...
  $instances = array();
  $results = $query->execute();

  foreach ($results as $record) {
    // Filter out instances on unknown entity types (for instance because the
    // module exposing them was disabled).
    $entity_info = entity_get_info($record['entity_type']);
    if ($include_inactive || $entity_info) {
      $instance = unserialize($record['data']);
      $instance['id'] = $record['id'];
      $instance['field_id'] = $record['field_id'];
      $instance['field_name'] = $record['field_name'];
      $instance['entity_type'] = $record['entity_type'];
      $instance['bundle'] = $record['bundle'];
      $instance['deleted'] = $record['deleted'];

      module_invoke_all('field_read_instance', $instance);
      $instances[] = $instance;
    }
  }
  return $instances;

I will be happy to put together a patch to fix this, but I'm not entirely sure what the best approach is.

Why is field_read_instances() filtering out the disabled instances in the first place?

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

NimbyDagda’s picture

Not quite sure why nobody else has responded to this, but we have just come across the exact same problem, and since it functionally means you can't properly clean up on your modules uninstallation I would have though more people would be worried about this.

As a temporary measure we have written our own version of field_purge_batch to be used, but I think its worth having the discussion about how this should be resolved properly. I am not entirely sure why field_purge_batch doesn't pass the include_inactive flag, I can't think of any reason why you would have a field marked as deleted that shouldn't be purged just because the entity is inactive.

fravemel’s picture

Version: 7.x-dev » 7.12
Issue tags: +field_purge_batch field_read_instances field_config_instance field_config

Same Issue in drupal 7.12.
I agree with you, fields instances from table field_config_instance are marked as deleted but never removed. Although you run cron, execute field_purge_batch() or empty cache.
when you get this situation instances will nerver be removed becouse in field_read_instances() it joins field_config_instance with field_config tables and as field config has been removed ¿?¿?

  $query = db_select('field_config_instance', 'fci', array('fetch' => PDO::FETCH_ASSOC));
  $query->join('field_config', 'fc', 'fc.id = fci.field_id');
  $query->fields('fci');

but why are removed config before instances¿? if my code does it the oposite.

the code to remove field is

  $field= field_info_field('field_name');
  if (!empty ($field)) {
      // Fetch the instance (object)
    $field = field_info_instance('entity_name', 'field_name', 'entity_name');
    // Delete it. 
    field_delete_instance($field);
    // Fetch the instance (object)
    $field = field_info_field('field_name');
    // Delete it
    field_delete_field($field);
  }

Any clue?

marcoka’s picture

confirmed, using field_collection

m.stenta’s picture

Version: 7.12 » 7.x-dev

I haven't tested this recently, but the version should be 7.x-dev. Not 7.12... otherwise it will get swept under the rug.

ghalenir’s picture

I also have the same problem and I don't know if I should fix myself or Drupal core team will fix this problem because we are not supposed to modify any Drupal core files.

nicorac’s picture

Same here, field_config row already deleted while field_config_instance not, so the join prevents any further deletion.
I'm developing a custom module and this lead me to a lot of orphaned rows into field_config_instance table (after some module install/uninstall).

Is anybody working on this?

michfuer’s picture

I ran into the same issue, and as well it's not clear to me why field_read_instances() filters out field instances on unknown entity types.

The workaround I used was to put all of the field/entity type creation hooks into one module, and all of the field base/instance creation (and removal) into another module. Then make the latter module dependent on the former. This ensures the entity type is available when uninstalling your module set.

DannyPfeiffer’s picture

Still appears to be a bug as of Drupal 7.35 :-(

kris-o3’s picture

drush updatedb

liquidcms’s picture

yes, 7.38 and this bug still remains.

i have numerous field_config and field_config_instance records marked as deleted as well as field_deleted_data_ tables which are never removed regardless of how many times i run cron or field_purge_batch().

jamesalvarez’s picture

I'm getting a similar issue, I am trying to delete fields on uninstall, but the instances are not getting removed.

For my problem, I've traced the problem here:

field_delete_field calls, field_info_instance, which calls FieldInfo->getBundleInstance() which calls field_red_instances, which then calls entity_get_info.

The problem is that entity_get_info is calling hook_entity_info, and this hook isn't called on the module that's uninstalling, since it has been disabled already. Presumably, because it can't find the entity's info, it causes field_read_instances to return an empty array, and now cannot delete.

Way around:

In .install file create a function return array of instance arrays (e.g. with field_name, entity_type, bundle etc), which is access by book hook_install and hook_uninstall.
For the uninstall call field_delete_instance in each of the instance arrays, before deleting the field) - so basically delete them manually e.g.:

Install:
$instances = my_default_field_instances();
foreach ($instances as $instance) {
field_create_instance($instance);
}

Uninstall:
$instances = my_default_field_instances();
foreach ($instances as $instance) {
field_delete_instance($instance, TRUE);
}

donquixote’s picture

Title: "Deleted" field instances cannot be removed for uninstalled entities during cron cleanup. » "Deleted" but "inactive" fields and instances cannot be removed for uninstalled entities during cron cleanup.

I have entries in field_config with deleted = 1 and active = 0.
These are never deleted on cron.

donquixote’s picture

Title: "Deleted" but "inactive" fields and instances cannot be removed for uninstalled entities during cron cleanup. » "Deleted" but "inactive" fields and instances cannot be removed during cron cleanup (e.g. for uninstalled entities).

I don't know if "uninstalled entities" is the only use case. So I rename the issue title.
The problem occurs whenever fields are marked as inactive but disabled.

I think this is a follow-up to #943772: field_delete_field() and others fail for inactive fields (which was 5 years ago)

donquixote’s picture

Btw,

 * Reads in fields that match an array of conditions.
 *
 * @param array $params
 *   An array of conditions to match against. Keys are columns from the
 *   'field_config' table, values are conditions to match. Additionally,
 *   conditions on the 'entity_type' and 'bundle' columns from the
 *   'field_config_instance' table are supported (select fields having an
 *   instance on a given bundle).
 * @param array $include_additional
 *   The default behavior of this function is to not return fields that
 *   are inactive or have been deleted. Setting
 *   $include_additional['include_inactive'] or
 *   $include_additional['include_deleted'] to TRUE will override this
 *   behavior.
 * @return
 *   An array of fields matching $params. If
 *   $include_additional['include_deleted'] is TRUE, the array is keyed
 *   by field id, otherwise it is keyed by field name.
 */
function field_read_fields($params = array(), $include_additional = array()) {

"Dynamic" return type, yeah!

donquixote’s picture

And one can ask why this is called with 1 instead of TRUE.
It does not make a difference in behavior, it is just weird.

function field_purge_batch($batch_size) {
  // Retrieve all deleted field instances. We cannot use field_info_instances()
  // because that function does not return deleted instances.
  $instances = field_read_instances(array('deleted' => 1), array('include_deleted' => 1));
donquixote’s picture

From #998048: field_info_field only works for active fields - document that comment #11 (yched):

Fields are inactive if the module that defines the field type is disabled, or if the module that defines their storage backend is disabled.

Disabled fields have always been a massive pain to deal with, because, the module being disabled, there's really not much we can do with them.

I agree field_delete_field() is currently not really bulletproof when called on an inactive field. Care to open a separate issue ?

From #2826320: Inactive fields cause "This module has been deleted from the file system":

Maybe the only solution is to restore the module code and re-enable it, then properly delete and purge the field, then disable and remove the module again.
If we cannot find a better solution, we should at least document this, show a warning somewhere, etc.

greenSkin’s picture

Status: Active » Needs review
FileSize
757 bytes

Ran into this issue when looking at purging instances created by our custom module when the custom module is uninstalled.

The field_purge_batch() currently has an issue where a field can be purged when inactive and end up leaving rows of instances within the {field_config_instance} table that are otherwise marked for deletion. The field_read_instances() filters out instances belonging to inactive fields resulting in field_purge_batch() not being able to purge instances of inactive fields but can still purge the field despite it. Once the field has been deleted, the query in field_read_instances() function will never return instances that don't have a field to join with.

This patch simply allows the field_purge_batch() function to purge inactive instances when loading instances marked to be deleted.

Darren Oh’s picture

Status: Needs review » Reviewed & tested by the community

Patch looks good. It allows inactive instances to be deleted, but we still need the patch in #2337139: Fields with instances attached to unknown entities are deleted on cron run if the batch size is smaller than the number of instances that need to be deleted.

JParkinson1991’s picture

Patch throws FatalErrors when run in the context of the entity that was using the instance being removed from the system.

Essentially, provide a fieldable entity via a module, install it, add a bundle and fields to that bundle, add entities of that bundle, uninstall the module.

Options are either, don't use this patch and accept orphan data in field_config_instance and all used field_data_FIELD_NAME and field_revision_FIELD_NAME or manually clear out this data in the uninstallation hook.


function example_uninstall() {
    // Example vars for simplicity
    $entityType = 'my_entity_type',
    $bundles = [
        'first_bundle',
        'second_bundle'
    ];

    foreach ($bundles as $bundle) {
        field_attach_delete_bundle($entityType, $bundle);
    }

    // Get all the fields attached to our entity type bundles
    if (db_table_exists('field_config_instance')) {
        $fieldNamesResult = db_select('field_config_instance', 'fc')
            ->fields('fc', ['id', 'field_name'])
            ->condition('entity_type', $entityType)
            ->execute()
            ->fetchAllKeyed();

        // Extract the config instance ids and delete them from the field_config_instance table
        $configInstanceIds = array_keys($fieldNamesResult);
        db_delete('field_config_instance')
            ->condition('id', $configInstanceIds, 'IN')
            ->execute();

        // Get a unique list of the field names (ids) attached to the entity type bundles
        $fieldNames = array_unique($fieldNamesResult);
        foreach($fieldNames as $fieldName) {
            if (db_table_exists('field_data_'.$fieldName)) {
                db_delete('field_data_'.$fieldName)
                    ->condition('entity_type', $entityType)
                    ->execute();
            }

            if (db_table_exists('field_revision_'.$fieldName)) {
                db_delete('field_revision_'.$fieldName)
                    ->condition('entity_type', $entityType)
                    ->execute();
            }
        }

        field_cache_clear();
    }
}

poker10’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: -field_purge_batch field_read_instances field_config_instance field_config

Tested the patch and I can confirm fatal error as mentioned in #19.