8

Goal:

Add a database index to a 'created' field.

Given:

There is a custom entity with a base field created. The field is stored the entity table. Field source code of the created field:

$fields['created'] = BaseFieldDefinition::create('created')
  ->setLabel(t('Created'))
  ->setDescription(t('The time that the entity was created.'))
  ->setDisplayOptions('view', [
    'label' => 'hidden',
    'type' => 'timestamp',
    'weight' => 3,
    'settings' => [
      'date_format' => 'medium',
      'custom_date_format' => '',
      'timezone' => '',
    ],
  ])
  ->setDisplayConfigurable('view', TRUE);

What I researched:

I have not found a way to add index using the \Drupal\Core\Field\BaseFieldDefinition().

As I understand indexes are defined in a @FieldType plugin. The created field is implemented via the \Drupal\Core\Field\Plugin\Field\FieldType\TimestampItem() plugin and obviously does not have have indexes there.

P.S. I know, that I can write an updatescript to add an index to the table manually.

3
  • Another option is to extend the TimestampItem class and use the setClass method to replace the default class.
    – Eyal
    Commented Nov 25, 2016 at 12:12
  • @Eyal, yep, that is also an option. I can create a new @FieldType created_indexed.
    – milkovsky
    Commented Nov 25, 2016 at 13:40
  • Some plugins might check the field type I'd, so (if possible) it would be better to use the created field and only set the new class via the setClass method.
    – Eyal
    Commented Nov 25, 2016 at 15:30

4 Answers 4

19

Currently the only way to do that is to create your own storage_schema entity handler.

See Node as an example, the annotation:

*     "storage_schema" = "Drupal\node\NodeStorageSchema",

There you can alter both the schema for a while table, or you can do it for a specific field. NodeStorageSchema does both:

  /**
   * {@inheritdoc}
   */
  protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
    $schema = parent::getEntitySchema($entity_type, $reset);

    $schema['node_field_data']['indexes'] += array(
      'node__frontpage' => array('promote', 'status', 'sticky', 'created'),
      'node__status_type' => array('status', 'type', 'nid'),
      'node__title_type' => array('title', array('type', 4)),
    );

    return $schema;
  }

And below in getSharedTableFieldSchema():

if ($table_name == 'node_revision') {
  switch ($field_name) {
    case 'langcode':
      $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
      break;

    case 'revision_uid':
      $this->addSharedTableFieldForeignKey($storage_definition, $schema, 'users', 'uid');
      break;
  }
}
2
  • 1
    Since drush entup is no more available, you have to use hook_update, take node_update_8002() as example. By the way NodeStorageSchema::getEntitySchema uses if ($data_table = $this->storage->getDataTable()) { which didn't work for me: getDataTable returns null
    – Teenage
    Commented Dec 11, 2019 at 18:12
  • If that's the only way to do it, then I gather Drupal has no support for using hook_entity_base_field_info() in one module to add a field to a custom entity created in another module, and then create a compound index on the table for that entity using the new field. So the only recourse would be to create the index directly with SQL from outside of Drupal, I suppose.
    – Bob Kline
    Commented Jul 13, 2022 at 22:24
8

For those wondering which part from berdir's reply is necessary for creating an index on a base column - here is a full example on creating index on a string column sku:

ekr_product/Entity/Product.php:

<?php

 * @ContentEntityType(
...
 *     "storage_schema" = "Drupal\ekr_product\ProductStorageSchema",

ekr_product/ProductStorageSchema.php:

<?php

namespace Drupal\ekr_product;

use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
use Drupal\Core\Field\FieldStorageDefinitionInterface;

/**
 * Defines the node schema handler.
 */
class ProductStorageSchema extends SqlContentEntityStorageSchema {

  /**
   * {@inheritdoc}
   */
  protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
    $schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
    $field_name = $storage_definition->getName();

    switch ($field_name) {
      case 'sku':
        $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
        break;
    }

    return $schema;
  }

}

Few comments on this.

You might be thinking why using method getSharedTableFieldSchema when creating index on the base table? Truly, shared word is sorta associated with fields stored in a separate tables. But from the function's documentation it follows that if your schema modification isn't cross-field you should use exactly this function.

Note also that the third parameter to addSharedTableFieldIndex is set to TRUE. Suddenly this makes the column you're creating index on NOT NULL. This is the same as if you'd be doing:

$schema['fields']['your_field']['not null'] = TRUE;

somewhere in schema overrides. This is a way to make the column required at the database level.

HTH.

1
  • Thank you for example! But additionally to those modifications, maybe we need to apply changes via hook_update function? Or they must be applied automatically on cache rebuild or other routine?
    – Murz
    Commented Feb 8, 2022 at 11:06
7

Besides the options berdir posted, configurable fields can have manually added indexes via \Drupal\field\Entity\FieldStorageConfig::$indexes

For example, in field.storage.media.field_media_video_embed_field.yml file you'd have:

indexes:
  field_media_video_embed_field__value:
    - value

to add an index on the value property. Note the second line in this example is just the index name and has no special meaning and could be anything else, it's just a naming convention. But the third line here needs to be the property name and not the SQL database column name (which would be field_media_video_embed_field__value).

2

While the above API approaches are recommended, here's one more raw approach that can be useful for some cases.

In this case, it adds an SQL index to a base field for the MySQL/MariaDB database engine:

/**
 * Add SQL index on published_date base field
 */
function MY_MODULE_update_8010(&$sandbox) {
  // Add index
  // ALTER TABLE $table ADD INDEX $name ($column1, $column2..)
  db_query("ALTER TABLE node_field_data ADD INDEX node_field__published_date (published_date)");
}
3
  • 2
    I use this, too (in mymodule_install()) - because API is painful here. But add {} around the tablename incase you have a prefix set. Commented Jun 26, 2020 at 8:57
  • 1
    This is a bad idea because it just works when modifying existing sites - any new installs of my_module will not have the index defined. A proper solution needs to first change the entity definition so that the index is created AND define an update hook so that existing sites can have their schemas updated.
    – anonymous
    Commented Oct 8, 2021 at 4:29
  • 1
    @anonymous for my case I usually need to add an index to an existing install, however it’s trivial to add the same code to a hook such as hook_install api.drupal.org/api/drupal/… Commented Dec 9, 2022 at 18:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.