Разбираемся с FOREIGN KEYS в Друпале

Разбираемся с FOREIGN KEYS в Друпале

Нередко приходилось наблюдать ситуацию, когда в контрибах и в модулях ядра указывали FOREIGN KEYS. Неужели это реально работает? Хотелось попробовать реализовать целостность данных с помощью внешних ключей и я стал разбираться с этим вопросом.
Ответ будем искать, конечно же, в ядре самой CMS. Рассмотрим простую ситуацию на примере инсталляции кастомного модуля.
Структура таблицы, вместе с индексами и ключами (первичными, внешними) описывается в хуке hook_schema(). В процессе инсталляции модуля вызывается функция drupal_create_table(), отвечающая за создание таблицы модуля. Далее в этой функции (после установки соединения с базой данных) будет вызван метод createTableSql() класса DatabaseSchema_mysql, в котором за создание ключей и индексов отвечает другой метод createKeysSql(). Ниже приведено его содержимое.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected function createKeysSql($spec) {
  $keys = array();
 
  if (!empty($spec['primary key'])) {
    $keys[] = 'PRIMARY KEY (' . $this->createKeysSqlHelper($spec['primary key']) . ')';
  }
  if (!empty($spec['unique keys'])) {
    foreach ($spec['unique keys'] as $key => $fields) {
      $keys[] = 'UNIQUE KEY `' . $key . '` (' . $this->createKeysSqlHelper($fields) . ')';
    }
  }
  if (!empty($spec['indexes'])) {
    foreach ($spec['indexes'] as $index => $fields) {
      $keys[] = 'INDEX `' . $index . '` (' . $this->createKeysSqlHelper($fields) . ')';
    }
  }
 
  return $keys;
}

Как легко заметить обработки внешних ключей просто нет! Отсюда и соответствующий вывод: FOREIGN KEYS в Drupal не работают из коробки. Но тогда возникает вопрос, раз обработки внешних ключей нет, то зачем вообще их указывать в hook_schema()?
Оказывается все просто. Внешние ключи добавляют исключительно в целях документирования связей таблиц друг с другом. Это подтверждает следующая строка из официальной документации[1]

1
Note: Foreign key definitions were added in Drupal 7 for documentation purposes only, and do not modify the database.

Но что делать, если есть необходимость использовать FOREIGN KEYS? Выход - заимплементить самостоятельно.
Добавим внешний ключ через hook_install() кастомного модуля. Создадим связь таблицы table1 с таблицей table2 через поля table1_id и table2_id.

1
2
3
4
5
6
/**
 * Implements hook_install().
 */
function MYMODULE_install() {
    db_query('ALTER TABLE {table1} ADD CONSTRAINT fk_key FOREIGN KEY (table1_id) REFERENCES {table2} (table2_id) ON DELETE CASCADE');
}

При создании связей с помощью внешних ключей нужно учитывать следующие особенности:

  • таблицы, участвующие в связи должны иметь тип InnoDB, для таблиц иного механизма хранения конструкция с объявлением внешнего ключа будет проигнорирована.
  • графы, по которым происходит связь должны быть проиндексированы каждый в своей таблице. Но если вы этого не сделали, InnoDB сгенерирует их автоматически.
  • типы данных граф, используемых для связи посредством внешних ключей должны быть одинаковыми.

Удалить таблицу table2 будет невозможно, так как внешний ключ, созданный в table1 не позволит этого сделать. Для того чтобы избавиться от внешнего ключа можно пойти двумя путями:

  • удалить таблицу table1
  • удалить внешний ключ через ALTER TABLE

Пример удаление внешнего ключа через альтер таблицы table1.

1
db_query('ALTER TABLE {table1} DROP FOREIGN KEY fk_key');

Дополнительная информация по статье

  1. https://www.drupal.org/node/146939 - описание структуры Drupal schema.
  2. http://www.w3schools.com/sql/sql_foreignkey.asp - примеры создания/удаления FOREIGN KEYS на сайте w3schools.com
  3. https://dev.mysql.com/doc/refman/5.5/en/create-table-foreign-keys.html - полное описание внешних ключей из официальной документации MySQL.