理系学生日記

おまえはいつまで学生気分なのか

DBIC でリレーションが自動設定されない訳

実は明日 7/24 が第何回だかの勉強会の日で、ぼくのテーマは O/R マッパなわけで、準備してねーこれはヤバいとか思って家でちょっとしたプログラムを動かしてみてたんですけど、ヤベーうまく動かないみたいなことになりました。
何が困ったかっていうと、DBIC::Schema::Loader でリレーションが設定されないよ、ってところです。

原因

結論からいうと、MySQL のデフォルトのテーブルタイプである MyISAM では、外部キー制約がサポートされていないのが原因でした。

リレーションが自動設定されない

DBIC::Schema::Loader を使うと、DB のスキーマ定義から自動でテーブルクラスを作ってくれます。

use base 'DBIx::Class::Schema';
__PACKAGE__->load_classes;

ところが、以下のようなテーブルに対して、リレーションが設定されないという事態に遭遇してしまったりしたのです。

create table person (
       person_id    integer(5)  primary key,
       person_name  varchar(20) not null,
       division_id  integer(5),
       foreign key (division_id) references division(div_id) on delete set null
);

これが DBIC::Schema::Loader::make_schema_at で生成した上記テーブルのマッピング定義。belongs_to が呼ばれていないことが分かります。

package Schema::Person;

use strict;
use warnings;

use base 'DBIx::Class';

__PACKAGE__->load_components("Core");
__PACKAGE__->table("person");
__PACKAGE__->add_columns(
  "person_id",
  { data_type => "INT", default_value => undef, is_nullable => 0, size => 5 },
  "person_name",
  {
    data_type => "VARCHAR",
    default_value => undef,
    is_nullable => 0,
    size => 20,
  },
  "division_id",
  { data_type => "INT", default_value => undef, is_nullable => 1, size => 5 },
);
__PACKAGE__->set_primary_key("person_id");


# Created by DBIx::Class::Schema::Loader v0.04006 @ 2009-07-25 15:20:26
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:eDGxBYlTsYV53cv0fTkomw

# You can replace this text with custom content, and it will be preserved on regeneration
1;

なぜ設定されないのか

MyISAM では、上述の通り外部キー制約をサポートしてないようです。実際に上掲の create table 文で生成されたテーブルに対して show create table すると、foreign key の記述が見えません。

mysql> show create table person\G
*************************** 1. row ***************************
       Table: person
Create Table: CREATE TABLE `person` (
  `person_id` int(5) NOT NULL,
  `person_name` varchar(20) NOT NULL,
  `division_id` int(5) DEFAULT NULL,
  PRIMARY KEY (`person_id`),
  KEY `division_id` (`division_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1

Mysql を対象にして DBIC::Schema::Loader を使う場合、テーブル定義のパースは DBIC::Schema::Loader::DBI::mysql が使われますが、このモジュールでは下記のように "FOREIGN KEY" の文字列をマッチングして、外部キー制約の有無を確認します。

    # DBIC::Schema::Loader::DBI::mysql::sub _table_fk_info 
    my (@reldata) = ($table_def =~ /CONSTRAINT `.*` FOREIGN KEY \(`(.*)`\) REFERENCES `(.*)` \(`(.*)`\)/ig);

で、MyISAM の場合には create table の FOREIGN KEY ってのは無視されるみたい。結局以下が原因だと思われます。

  • MyISAM に対して外部キー制約を宣言しても外部キー制約が設定されないため

というわけで

結局、MyISAM を使うときは自分できちんと書かないといけないのかな。

__PACKAGE__->belongs_to( division => 'Schema::Division', { 'foreign.div_id' => 'self.division_id' } );

※もちろん Schema::Division 側にも has_many。