Работа в Drupal с несколькими базами данных


Если вам необходимо на друпал-сайте использовать данные из другой базы (может быть даже с другого хоста), то это достаточно просто сделать. Предоставленные для этого возможности не затрагивают кода ядра и могут быть использованы, как в виде вставок PHP-кода в материалы сайта, так и в собственных модулях и темах.

Находим в папке sites/default файл settings.php.
Редактируем его. Вместо строчки

$db_url = 'mysql://username:password@localhost/databasename';

ставим
$db_url = array(
  'default'=>'mysql://username:pass@localhost/databasename',
  'db1'=>'mysql://username1:pass@host1/databasename1',
  'db2'=>'mysql://username2:pass@host2/databasename2',
/*
**  Сколько угодно параметров для требуемого количества баз данных
**  Индексы кроме 'default' могут быть любыми.  
*/

);

Теперь если в сниппете, модуле, блоке или материале вам понадобятся данные из других баз, используем следующий код:
/*
** Делаем активной базу с параметрами под индексом 'db1'
*/

db_set_active('db1');
$result = db_query("Здесь нужный вам запрос к таблицам в db1")
/*
**  Здесь обрабатываем результат первого запроса
*/

// Делаем активной базу с параметрами под индексом 'db2'
db_set_active('db2');
$result = db_query("Здесь нужный вам запрос к таблицам в db2")
/*
**  Здесь обрабатываем результат второго запроса
*/


/*
**  В конце ОБЯЗАТЕЛЬНО делаем активной «родную» базу,
**  чтобы Drupal мог нормально завершить обработку страницы.
*/

db_set_active('default');

Переключаться между базами можно сколько угодно раз – после первого обращения Drupal кеширует ресурс соединения с БД в массиве $db_conns и повторного соединения не производится.

Если вам недоступно редактирование файла установок, то можно установить требуемое соединение сразу в PHP-коде:

/*
** Получаем глобальные переменные Drupal
*/

global $db_url;
/*
** Добавляем свою строку подключения к БД, а родную оставляем под индексом 'default'
*/

$db_url = array(
  "default"=>$db_url,
  "db1"=>"mysql://username1:pass@host1/databasename1"
);
/*
** Делаем активной базу с параметрами под индексом 'db1'
*/

db_set_active('db1');
$result = db_query("Здесь нужный вам запрос к таблицам в db1")
/*
**  Здесь обрабатываем результат запроса
*/


/*
**  В конце ОБЯЗАТЕЛЬНО делаем активной «родную» базу,
**  чтобы Drupal мог нормально завершить обработку страницы.
*/

db_set_active('default');

Указанные решения работают в D5 и D6.

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

$db_url = array(
  'default'=>'mysqli://username:pass@localhost/databasename',
  'db1'=>'pgsql://username1:pass@host1/databasename1',
);

Вы получите: Cannot redeclare db_status_report() (previously declared in /var/www/mysite/includes/database.mysqli.inc:23) in /var/www/mysite.ru/includes/database.pgsql.inc

Устранить данное ограничение можно только хаком ядра Drupal. Хак получается весьма объемистым. С указанной ошибкой все дело не в функции db_status_report(), а со способом которым Drupal подключает интерфейс требуемого типа баз данных. Дело в том, что названия всех функций (за исключением одной – db_check_setup) находящихся в файлах database.pgsql.inc, database.mysql.inc, database.mysqli.inc и database.mysql-common.inc совпадают, что приводит к конфликту в именах 41 функции. Поэтому использовать несколько подключений к базам разных типов можно только полностью переписав слой абстракции баз данных в Drupal.

Чтобы сделать это откроем все упомянутые файлы в редакторе.

Из файлов database.mysql.inc и database.mysqli.inc удаляем строчку

require_once './includes/database.mysql-common.inc';

Содержимое файла database.mysql-common.inc копируем в файлы database.mysql.inc и database.mysqli.inc, а сам файл делаем пустым.

Далее во всех трех файлах database.pgsql.inc, database.mysql.inc, database.mysqli.inc переименовываем все функции добавляя к их именам постфикс типа базы данных: в файле database.pgsql.inc – _pgsql; в файле database.mysql.inc – _mysql; в файле database.mysqli.inc – _mysqli. Переименовывать не надо только функцию db_check_setup – она уникальна для файла database.pgsql.inc.

Открываем файл database.inc и создаем там 41(!!!) функцию со следующими именами.

_db_create_field_sql()
_db_create_key_sql()
_db_create_keys_sql()
_db_process_field()
_db_process_field()
_db_query()
db_add_field()
db_add_index()
db_add_primary_key()
db_add_unique_key()
db_affected_rows()
db_change_field()
db_column_exists()
db_connect()
db_create_table_sql()
db_decode_blob()
db_distinct_field()
db_drop_field()
db_drop_index()
db_drop_primary_key()
db_drop_table()
db_drop_unique_key()
db_encode_blob()
db_error ()
db_escape_string()
db_fetch_array()
db_fetch_object()
db_field_set_default()
db_field_set_no_default()
db_last_insert_id()
db_lock_table()
db_query()
db_query_range()
db_query_temporary()
db_rename_table()
db_result()
db_status_report()
db_table_exists()
db_type_map()
db_unlock_tables()
db_version()

Все функции выглядят однотипно, различаясь только названием.

function имя_функции () {
  global $db_type;
  $args = func_get_args();
  return call_user_func_array("имя_функции_".$db_type, $args);
}

Несомненно, большой объем правок кода ядра может отпугнуть желающих использовать это. Есть еще один способ, требующий меньшего объема кода. Основная проблема в том, что в PHP обычно нет возможности удаления или переопределения (перегрузки) функций. Правда, одно из расширений PHP – Runkit позволяет сделать это. Если runkit подключен, то достаточно сделать небольшую вставку в код функции db_set_active(). Находим строчки:

    $db_type = substr($connect_url, 0, strpos($connect_url, '://'));
    $handler = "./includes/database.$db_type.inc";
    if (is_file($handler)) {
      include_once $handler;
    }
    else {
      _db_error_page("The database type '". $db_type ."' is unsupported. Please use either
'mysql' or 'mysqli' for MySQL, or 'pgsql' for PostgreSQL databases."
);
    }

    $db_conns[$name] = db_connect($connect_url);
  }

и добавляем свой код:
    $db_type = substr($connect_url, 0, strpos($connect_url, '://'));
    $handler = "./includes/database.$db_type.inc";
    if (is_file($handler)) {
      $fnames = array(
        '_db_create_field_sql', '_db_create_key_sql', '_db_create_keys_sql',
        '_db_process_field', '_db_process_field', '_db_query',
        'db_add_field', 'db_add_index', 'db_add_primary_key',
        'db_add_unique_key', 'db_affected_rows', 'db_change_field',
        'db_column_exists', 'db_connect', 'db_create_table_sql',
        'db_decode_blob', 'db_distinct_field', 'db_drop_field',
        'db_drop_index', 'db_drop_primary_key', 'db_drop_table',
        'db_drop_unique_key', 'db_encode_blob', 'db_error',
        'db_escape_string', 'db_fetch_array', 'db_fetch_object',
        'db_field_set_default', 'db_field_set_no_default', 'db_last_insert_id',
        'db_lock_table', 'db_query', 'db_query_range', 'db_query_temporary',
        'db_rename_table', 'db_result', 'db_status_report',
        'db_table_exists', 'db_type_map', 'db_unlock_tables', 'db_version',
      );
      foreach ($fnames as $fname) {
        @runkit_function_remove($fname);
      }
      include $handler;
    }
    else {
      _db_error_page("The database type '". $db_type ."' is unsupported. Please use either
'mysql' or 'mysqli' for MySQL, or 'pgsql' for PostgreSQL databases."
);
    }

    $db_conns[$name] = db_connect($connect_url);
  }

В этом случае определения всех функций баз данных будут удалены, а при подключении файла другого типа БД определены заново.

Источники:

db_set_active()
runkit_function_remove()
2 соединения с БД
Переключение между базами

спасибо за статью,

спасибо за статью, прояснилось хоть что-то из того как подсоединить свою базу к друпалу, но помимо этого мне нужно ее отображать с помощью вьюза, как я уже поняла для этого нужно писать свой модуль, но при этом должна быть базовая таблица, к которой джойнятся все остальные таблицы для того, чтобы их можно было отобразить, так вот я не очень понимаю - мне надо как-то создать базовую таблицу для своей базы или каким-то образом делать базовой уже существующую в моей базе таблицу?
Заранее спасибо за помощь.

Добрый день, написал коммент

Добрый день, написал коммент на замечание Влада на drupal.ru (на странице вашей статьи http://www.drupal.ru/node/19457).
Но в качестве вопроса хотелось бы автору этот текст написать. Возможно для 7 такую схему реализовать?
Вариант использования данной схемы:
Есть 2-е и более баз данных. Основная master остальные реплицирующие.
1. Для распределения нагрузки всех анонимов (запросы на чтение) направляем на реплицирующие базы, а зарегистрированных на основную с возможностью писать в базу (материалы и комменты).
2. Распределение анонимов по реплицирующим базам, быстрая доставка контента в зависимости от географического положения юзера (соответствующий модуль белым домом разработан и отдан сообществу, нечто на подобии CDN - сеть доставки (и дистрибуции) контента англ. Content Delivery Network или Content Distribution Network, CDN).
Как я понимаю код обращения к базе нужно не в нодах и блоках писать, а использовать свой класс, 7 особенно это хорошо понимает.

Годная идея на мой взгляд. Но

Годная идея на мой взгляд. Но мне не приходилось с настолько большими проектами работать, чтобы утверждать это. Анонимов выгодней кэшировать на уровне сервера, чтобы вообще без запросов к базам обходилось.