- Nekokak's core dump:Perl/DBIC:チュートリアル
- 101号室:DBIx::Classのリレーション:テーブル間の関係を図で説明
- PeroDoc和訳:DBIx::Class
- 今日のCPANモジュール:use DBIx::Class;
本番のアプリケーションを動かすのはMySQLかPostgreSQLだけど、テストはSQLiteでやりたい。
まずは、SQLiteでテストができる環境をDebian GNU/Linux Lenny上に用意。
% sudo aptitude install libdbix-class-perl sqlite3 libdbd-sqlite3-perl
次にNekokak's core dump:Perl/DBICにしたがい練習してみる。ただし、SQLiteで。PeroDoc和訳:DBIx::Class::Manual::Example - シンプルなCDデータベースの例にディレクトリを構成。
% mkdir -p app/db % cd app/db
データベースのスキーマを以下のようにuser.sqlとして作成。
create table user( id int(10) NOT NULL autoincrement, name varchar(256) NOT NULL, PRIMARY KEY (id) );
これを元にデータベース作成。
% sqlite3 example.db < user.sql SQL error near line 1: near "autoincrement": syntax error
さっそく挫折。Googleで「sqlite autoincrement」で検索した結果見つけたページによると、SQLiteではINTEGER PRIMARY KEYと指定するとその列をNULLでINSERTすると、その列中の最大数+1が入力され、INTEGER PRIMARY KEY AUTOINCREMENTとすると、その列をNULLでINSERTすると、テーブルのその列においてユニークな数字が挿入されるらしい出典)。SQLiteとMySQLだとSQLの書き方からして違うのね。
user.sqlを以下のように変更。
create table user( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL );
これを元にデータベース作成。
% sqlite3 example.db < user.sql
あとは、Nekokak's core dump:Perl/DBICにしたがい練習してみる。
% cd .. % mkdir -p Neko/Schema
Neko/Schema.pmを作成する。
package Neko::Schema; use strict; use warnings; use base 'DBIx::Class::Schema'; __PACKAGE__->load_classes(qw/User/); 1;
次にNeko/Schema/User.pmを作成する(SQLiteなのでPK::Auto、MySQLだとPK::Auto::MySQLらしい)。
package Neko::Schema::User; use strict; use warnings; use base 'DBIx::Class'; __PACKAGE__->load_components(qw/ PK::Auto Core/); __PACKAGE__->table('user'); __PACKAGE__->add_columns(qw/id name/); __PACKAGE__->set_primary_key('id'); 1;
次にapp/test.plを用意し、insert, select, update, deleteを試す
#! /usr/bin/perl use strict; use warnings; use Neko::Schema; my $schema = Neko::Schema->connect('dbi:SQLite:db/example.db'); #my $schema = Neko::Schema->connect('dbi:mysql:nekodb','nekokak','******'); # insert $schema->resultset('User')->create({id => 1, name => 'nekokak'}); # select my $user = $schema->resultset('User')->find(1); print $user->id,"\n"; print $user->name,"\n"; # update $user->name('nomaneko'); $user->update; # select $user = $schema->resultset('User')->find(1); print $user->id,"\n"; print $user->name,"\n"; # delete $user->delete;
読み進めて、1対多の関係の部分。まずは、テーブルを追加する。app/db/bookmark.sqlを以下の内容で作成する。
create table bookmark ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES user(id), url TEXT NOT NULL );
これを元にテーブル追加。
% sqlite3 db/example.db < db/bookmark.sql
app/Neko/Schema/Bookmark.pmを以下のように作成。
package Neko::Schema::Bookmark; use strict; use warnings; use base 'DBIx::Class'; __PACKAGE__->load_components(qw/ PK::Auto Core/); __PACKAGE__->table('bookmark'); __PACKAGE__->add_columns(qw/id user_id url/); __PACKAGE__->set_primary_key('id'); __PACKAGE__->belongs_to(user_id => 'Neko::Schema::User'); 1;
app/Neko/Schema/User.pmを以下のように変更。
package Neko::Schema::User; use strict; use warnings; use base 'DBIx::Class'; __PACKAGE__->load_components(qw/ PK::Auto Core/); __PACKAGE__->table('user'); __PACKAGE__->add_columns(qw/id name/); __PACKAGE__->set_primary_key('id'); __PACKAGE__->has_many(bookmark => 'Neko::Schema::Bookmark', 'user_id'); 1;
app/Neko/Schema.pmを以下のように変更。
package Neko::Schema; use strict; use warnings; use base 'DBIx::Class::Schema'; __PACKAGE__->load_classes(qw/User Bookmark/); 1;
リレーションを試してみるapp/test2.plを以下のように作成
#! /usr/bin/perl use strict; use warnings; use Neko::Schema; my $schema = Neko::Schema->connect('dbi:SQLite:db/example.db'); # insert $schema->resultset('User')->create({id => 1, name => 'nekokak'}); $schema->resultset('User')->create({id => 2, name => 'nomaneko'}); # select my $userit = $schema->resultset('User'); while(my $user = $userit->next){ print $user->id,',',$user->name,"\n"; } # insert $schema->resultset('Bookmark')->create({id => 1, user_id => 1, url => 'http://hogehoge.jp'}); $schema->resultset('Bookmark')->create({id => 2, user_id => 1, url => 'http://hugahuga.jp'}); $schema->resultset('Bookmark')->create({id => 3, user_id => 2, url => 'http://kurukuru.jp'}); $schema->resultset('Bookmark')->create({id => 4, user_id => 2, url => 'http://borabora.jp'}); # select my $bookmarkit = $schema->resultset('Bookmark'); while(my $bookmark = $bookmarkit->next){ print $bookmark->id,",",$bookmark->user_id,",",$bookmark->url,"\n"; } $userit = $schema->resultset('User')->search({name => 'nekokak'}); while ( my $user = $userit->next ) { my $bkit = $user->bookmark; while ( my $bookmark = $bkit->next ) { print $bookmark->id,':',$user->id,':',$bookmark->url,"\n"; } } my $bkit = $schema->resultset('Bookmark')->search(); while ( my $bookmark = $bkit->next ) { my $user = $bookmark->user_id; print $user->id,':',$bookmark->id,':',$user->name,':',$bookmark->url,"\n"; } #delete $userit = $schema->resultset('User'); while(my $user = $userit->next){ $user->delete; } $bookmarkit = $schema->resultset('Bookmark'); while(my $bookmark = $bookmarkit->next){ $bookmark->delete; } exit;
inflateとdeflateが何のことやらわからなかったのでGoogle様にお伺い。Walrus, Digit.:Perlメモ/DBIx::Classモジュール: データベース入出時にフィールド値を変換する(inflate、deflate)によるとINSERTやUPDATEをしたときの自動記録に便利そうな機能であるということがわかった。
DateTime::Format::SQLiteなんて無いようなので、DateTime::Format::MySQLを使う。
- 参考:発声練習:Perlにおける各種日付フォーマットの変換と比較
- 参考:hide-k.net#blog: DBICでdatetime型カラムの検索条件ではまった件
- 参考:つゆだくのSQLite3:SQLiteのデータ型
app/db/login.sqlを以下のように用意する。
create table login ( id INTEGER PRIMARY KEY REFERENCES user(id), login_time DATETIME );
app/Neko/Schema/Login.pmを以下のように用意する。
package Neko::Schema::Login; use strict; use warnings; use base 'DBIx::Class'; use DateTime::Format::MySQL; __PACKAGE__->load_components(qw/PK::Auto Core/); __PACKAGE__->table('login'); __PACKAGE__->add_columns(qw/id login_time/); __PACKAGE__->set_primary_key('id'); __PACKAGE__->might_have(id => 'Neko::Schema::User'); __PACKAGE__->inflate_column('login_time', { inflate => sub { DateTime::Format::MySQL->parse_datetime(shift); }, deflate => sub { DateTime::Format::MySQL->format_datetime(shift); }, });
app/Neko/Schema/User.pmを以下のように変更。
package Neko::Schema::User; use strict; use warnings; use base 'DBIx::Class'; __PACKAGE__->load_components(qw/ PK::Auto Core/); __PACKAGE__->table('user'); __PACKAGE__->add_columns(qw/id name/); __PACKAGE__->set_primary_key('id'); __PACKAGE__->has_many(bookmark => 'Neko::Schema::Bookmark', 'user_id'); __PACKAGE__->might_have(id => 'Neko::Schema::Login'); 1;
app/Neko/Schema.pmを以下のように変更
package Neko::Schema; use strict; use warnings; use base 'DBIx::Class::Schema'; __PACKAGE__->load_classes(qw/User Bookmark Login/); 1;
inflateとdeflateを試すスクリプトをapp/test3.plとして以下のように書く。
#! /usr/bin/perl use strict; use warnings; use Neko::Schema; my $schema = Neko::Schema->connect('dbi:SQLite:db/example.db'); $schema->storage->debug(1); $schema->storage->debugfh(IO::File->new('/tmp/trace.out', 'a')); my $i = $schema->resultset('User')->create({name => 'nekokak'}); $schema->resultset('Login')->create({id => $i->last_insert_id, login_time => '2006-02-03'}); my $it = $schema->resultset('Login')->search(); while ( my $login = $it->next ) { my $u = $login->user_id; print $login->id,':',$u->name,':',$login->login_time,"\n"; } exit;
これで実行するとどうもうまくいかない。
% perl test3.pl Can't locate object method "last_insert_id" via package "Neko::Schema::User" at test3.pl line 10.
検索しても原因不明。とりあえずここまで。