Ошибка в ORM Kohana 3 при использовании префиксов таблиц

Патч для решения проблемы прилагается

Ситуация: имеем две таблицы: пользователи и подписки:

[-]
View Code MySQL
CREATE TABLE prefix_users (
    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) имеют следующий вид:

[-]
View Code PHP
return array(
    '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,
    ),
);

Модели в 3 будут иметь следующий вид:

[-]
View Code PHP
// application/classes/model/user.php
    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',
            ),
        );
    }

Допустим, что в контроллере у нас есть вызов наподобие

[-]
View Code PHP
ORM::factory('Subscription')->with('user')->find_all();

Что произойдёт? А ничего хорошего:

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 ]

Почему? Модуль предполагал, что запрос будет выглядеть так:

[-]
View Code MySQL
SELECT `user`.`id` AS `user:id`, `user`.`name` AS `user:name`, `subscriptions`.*
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`.

Есть два решения:

  1. Унаследовать свой класс ORM от Kohana_ORM и исправить в нём метод with();
  2. Исправить метод Kohana_ORM::with().

Результат получится примерно одинаковым — код будет выглядеть так:

[-]
View Code PHP
    public function with($target_path)
    {
        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;
    }

по объёму гораздо меньше:

[-]
View Code Diff
--- kohana/modules/orm/classes/kohana/orm.php
+++ 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

Скачать патч (unified diff).

Идея в том, что имена псевдонимов заключаются в двойные кавычки — в этом случае Database не занимается «самодеятельностью» в виде добавления префиксов куда надо и не надо.

Проверялось на Kohana 3.0.3

Отчёт об ошибке в трекере Kohana

Вложения:

Автор: ; опубликовано в: Kohana; метки: Kohana, Kohana 3, ORM, ошибка, патч
19
Фев
2010

RSS Комментарии к статье «Ошибка в ORM Kohana 3 при использовании префиксов таблиц» (6)

  1. murzik

    Спасибо! Была такая проблема, теперь нету=)

  2. В принципе не было подобных проблем, но ознакомиться было интересно. Недавно появилось желания разобраться досконально в движке моего блога.
    А за подробность и детализацию с иллюстрациями однозначно респект.

  3. Ага именно так.
    Я знаю что на одну БД можно повесить несколько сайтов, только для этого нужно менять прификсы.
    В общем не судите строго, я читал в этой статье то, чего тут нет :)
    В любом случае материал полезный :)
    А вообще я и не знал что есть такая cms Kohana, в чем прикол этой cms?

Пожалуйста, не используйте эту форму для комментирования! Данная форма предназначена исключительно для ботов.

गते गते पारगते पारसंगते बोधि स्वाहा