Ошибка в ORM Kohana 3 при использовании префиксов таблиц
Патч для решения проблемы прилагается
Ситуация: имеем две таблицы: пользователи и подписки:
id INTEGER NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
CREATE TABLE prefix_subscriptions (
id INTEGER NOT NULL PRIMARY KEY,
user_id INTEGER UNSIGNED NOT NULL,
some_data TEXT NOT NULL
);
Один пользователь может иметь несколько подписок, одна подписка может принадлежать только одному пользователю.
Настройки соединения с базой данных (application/config/database.php) имеют следующий вид:
'default' => array(
'type' => 'mysql',
'connection' => array(
'hostname' => 'localhost',
'username' => 'user',
'password' => 'pass',
'persistent' => FALSE,
'database' => 'database',
),
'table_prefix' => 'prefix_',
'charset' => 'utf8',
'caching' => FALSE,
'profiling' => TRUE,
),
);
Модели в Kohana 3 будут иметь следующий вид:
class Model_User extends ORM
{
protected $_has_many = array(
'subscriptions' => array(
'foreign_key' => 'user_id',
'model' => 'Subscription',
),
);
}
// application/classes/model/subscription.php
class Model_Subscription extends ORM
{
protected $_belongs_to = array(
'user' => array(
'foreign_key' => 'user_id',
'model' => 'User',
),
);
}
Допустим, что в контроллере у нас есть вызов наподобие
Что произойдёт? А ничего хорошего:
Database_Exception [ 1054 ]: Unknown column 'prefix_user.id' in 'field list' [ SELECT `prefix_user`.`id` AS `user:id`, `prefix_user`.`name` AS `user:name`, `prefix_subscriptions`.* FROM `prefix_subscriptions` LEFT JOIN `prefix_users` AS `user` ON (`prefix_user`.`id` = `prefix_subscriptions`.`user_id`) ORDER BY `prefix_subscriptions`.`id` ASC ] ~ MODPATH/database/classes/kohana/database/mysql.php [ 183 ]
Почему? Модуль ORM предполагал, что запрос будет выглядеть так:
FROM `subscriptions`
LEFT JOIN `users` AS `user` ON (`user`.`id` = `subscriptions`.`user_id`)
ORDER BY `subscriptions`.`id` ASC
Так бы и было, если бы таблицы не имели префикса. ORM передаёт Database поля в виде user.id, Database их экранирует в `user`.`id`. Проблема в том, что Database ничего не знает о псевдонимах таблиц (users AS user в JOIN), а ORM ничего не знает о префиксах таблиц. Поэтому Database с чистой совестью добавляет префикс ко всему, что находится в именах слева от точки. В результате вместо SELECT `user`.`id` AS `user:id` получаем SELECT `prefix_user`.`id` AS `user:id`.
Есть два решения:
- Унаследовать свой класс ORM от Kohana_ORM и исправить в нём метод
with(); - Исправить метод
Kohana_ORM::with().
Результат получится примерно одинаковым — код будет выглядеть так:
{
if (isset($this->_with_applied[$target_path]))
{
// Don't join anything already joined
return $this;
}
// Split object parts
$aliases = explode(':', $target_path);
$target = $this;
foreach ($aliases as $alias)
{
// Go down the line of objects to find the given target
$parent = $target;
$target = $parent->_related($alias);
if ( ! $target)
{
// Can't find related object
return $this;
}
}
// Target alias is at the end
$target_alias = $alias;
// Pop-off top alias to get the parent path (user:photo:tag becomes user:photo - the parent table prefix)
array_pop($aliases);
$parent_path = implode(':', $aliases);
if (empty($parent_path))
{
// Use this table name itself for the parent path
$parent_path = $this->_table_name;
}
else
{
if( ! isset($this->_with_applied[$parent_path]))
{
// If the parent path hasn't been joined yet, do it first (otherwise LEFT JOINs fail)
$this->with($parent_path);
}
}
// Add to with_applied to prevent duplicate joins
$this->_with_applied[$target_path] = TRUE;
// Use the keys of the empty object to determine the columns
foreach (array_keys($target->_object) as $column)
{
$name = '"'.$target_path.'"."'.$column . '"';
$alias = $target_path.':'.$column;
// Add the prefix so that load_result can determine the relationship
$this->select(array($name, $alias));
}
if (isset($parent->_belongs_to[$target_alias]))
{
// Parent belongs_to target, use target's primary key and parent's foreign key
$join_col1 = '"'.$target_path.'"."'.$target->_primary_key.'"';
$join_col2 = $parent_path.'.'.$parent->_belongs_to[$target_alias]['foreign_key'];
}
else
{
// Parent has_one target, use parent's primary key as target's foreign key
$join_col1 = $parent_path.'.'.$parent->_primary_key;
$join_col2 = '"'.$target_path.'"."'.$parent->_has_one[$target_alias]['foreign_key'] .'"';
}
// Join the related object into the result
$this->join(array($target->_table_name, $target_path), 'LEFT')->on($join_col1, '=', $join_col2);
return $this;
}
Патч по объёму гораздо меньше:
+++ kohana/modules/orm/classes/kohana/orm.php
@@ -637,7 +637,7 @@
// Use the keys of the empty object to determine the columns
foreach (array_keys($target->_object) as $column)
{
- $name = $target_path.'.'.$column;
+ $name = '"'.$target_path.'"."'.$column . '"';
$alias = $target_path.':'.$column;
// Add the prefix so that load_result can determine the relationship
@@ -647,14 +647,14 @@
if (isset($parent->_belongs_to[$target_alias]))
{
// Parent belongs_to target, use target's primary key and parent's foreign key
- $join_col1 = $target_path.'.'.$target->_primary_key;
+ $join_col1 = '"'.$target_path.'"."'.$target->_primary_key.'"';
$join_col2 = $parent_path.'.'.$parent->_belongs_to[$target_alias]['foreign_key'];
}
else
{
// Parent has_one target, use parent's primary key as target's foreign key
$join_col1 = $parent_path.'.'.$parent->_primary_key;
- $join_col2 = $target_path.'.'.$parent->_has_one[$target_alias]['foreign_key'];
+ $join_col2 = '"'.$target_path.'"."'.$parent->_has_one[$target_alias]['foreign_key'] .'"';
}
// Join the related object into the result
Идея в том, что имена псевдонимов заключаются в двойные кавычки — в этом случае Database не занимается «самодеятельностью» в виде добавления префиксов куда надо и не надо.
Проверялось на Kohana 3.0.3
Отчёт об ошибке в трекере Kohana
Вложения:
- orm.php (text/plain)
Фев
2010
Комментарии к статье «Ошибка в ORM Kohana 3 при использовании префиксов таблиц» (6)
Пожалуйста, не используйте эту форму для комментирования! Данная форма предназначена исключительно для ботов.
गते गते पारगते पारसंगते बोधि स्वाहा
Меня зовут Владимир, я программист-фрилансер, специализирующийся на Web-программировании и програмировании под Linux.
По совместительству занимаюсь администрированием LAMP/LNMP-серверов и техническим переводом.


Спасибо! Была такая проблема, теперь нету=)
В принципе не было подобных проблем, но ознакомиться было интересно. Недавно появилось желания разобраться досконально в движке моего блога.
А за подробность и детализацию с иллюстрациями однозначно респект.
Если я не ошибаюсь, то у Вас блог на WordPress, а не на Kohana
Ага именно так.
Я знаю что на одну БД можно повесить несколько сайтов, только для этого нужно менять прификсы.
В общем не судите строго, я читал в этой статье то, чего тут нет
В любом случае материал полезный
А вообще я и не знал что есть такая cms Kohana, в чем прикол этой cms?
Kohana — это не CMS, Kohana — это фреймворк для PHP 5. Подробнее здесь.
Исправлено в http://github.com/kohana/orm/commit/bc1dbaf334c719c752ad9f7d5b94f8d12a21d177.