Do you want to know how to Drupal?

Let's Drupal

How to add a SQL index to existing entity tables

Drupal 8 provides a nice Entity API, which handles a lot of complicated mechanisms for us automatically. For example, the handling of tables for Content Entity. In some cases if we are fetching frequently entity tables and we need to improve the performance by providing additional indexes in database tables. There are different ways to do so. The first option you can think of to add directly using SQL query or Schema API. Obviously, it is not a the best way, because a lot of thing can go wrong here.

Luckily Drupal 8 provides nice way to do it.

First of all we need to define a Storage Schema class. Let's take as an example node module:

<?php

namespace Drupal\node;

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

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

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

    if ($data_table = $this->storage->getDataTable()) {
      $schema[$data_table]['indexes'] += [
        'node__frontpage' => ['promote', 'status', 'sticky', 'created'],
        'node__title_type' => ['title', ['type', 4]],
      ];
    }

    return $schema;
  }

  /**
   * {@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();

    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;
      }
    }

    if ($table_name == 'node_field_data') {
      switch ($field_name) {
        case 'promote':
        case 'status':
        case 'sticky':
        case 'title':
          // Improves the performance of the indexes defined
          // in getEntitySchema().
          $schema['fields'][$field_name]['not null'] = TRUE;
          break;

        case 'changed':
        case 'created':
          // @todo Revisit index definitions:
          //   https://www.drupal.org/node/2015277.
          $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
          break;
      }
    }

    return $schema;
  }

}


I will explain a little bit the code above before we continue. We have few options here

1) Add composite index, which includes several fields like this:
 

 if ($data_table = $this->storage->getDataTable()) {
      $schema[$data_table]['indexes'] += [
        'node__frontpage' => ['promote', 'status', 'sticky', 'created'],
        'node__title_type' => ['title', ['type', 4]],
      ];
}

2) Add an index for specific field:

case 'langcode':
          $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
          break;

3) Add a foreign key

case 'revision_uid':
          $this->addSharedTableFieldForeignKey($storage_definition, $schema, 'users', 'uid');
          break;

4) Add additional properties for the fields

case 'title':
          // Improves the performance of the indexes defined
          // in getEntitySchema().
          $schema['fields'][$field_name]['not null'] = TRUE;
          break;

 

Now, when we have a class for Storage Schema, we need to add it to entity definition. Check Drupal\node\Entity\Node.php

 *   handlers = {
 *     "storage" = "Drupal\node\NodeStorage",
 *     "storage_schema" = "Drupal\node\NodeStorageSchema",
 *     "view_builder" = "Drupal\node\NodeViewBuilder",

No we to update our entity and set Storage Schema class for existing entity. We can do it using hook update

<?php
use Drupal\your_module\YourStorageSchema;

/**
 * Add storage schema to entity type.
 */
function your_module_update_8017() {
  $manager = \Drupal::entityDefinitionUpdateManager();

  // Get the current entity type definition, ensure the storage schema
  // class is set.
  $entity_type = $manager->getEntityType('enity_type')
    ->setHandlerClass('storage_schema', YourStorageSchema::class);

  // Regenerate entity type indexes.
  $manager->updateEntityType($entity_type);
}

Or if we have already had the Storage Schema class before, we can just update Entity type

<?php
use Drupal\your_module\YourStorageSchema;

/**
 * Update entity type.
 */
function your_module_update_8017() {
  $manager = \Drupal::entityDefinitionUpdateManager();
  $entity_type = $manager->getEntityType('enity_type');

  // Regenerate entity type indexes.
  $manager->updateEntityType($entity_type);
}