2008-03-05 22:07:23

CatalystアプリケーションのUnicode化2

[ Catalyst ] [ DBIx::Class ] [ MySQL ]

・FillInForm安全対策

cpan[1]> install Catalyst::Plugin::FillInForm::ForceUTF8
ex) http://blog.hide-k.net/archives/2007/03/catalyst_2.php
※Catalyst::Plugin::FillInForm だと Catalyst::Plugin::Unicode の前にロードする必要があり、多分いつかハマるのでNG

・DBIx::Class で mysql 使う時

方法1 - DBD::mysql のオプションを使う
(http://search.cpan.org/~capttofu/DBD-mysql-4.006/lib/DBD/mysql.pm)
$ vim lib/MyApp/Model/MyAppDB.pm

package MyApp::Model::MyAppDB;
use strict;
use base 'Catalyst::Model::DBIC::Schema';
__PACKAGE__->config(
    schema_class => 'MyApp::Schema',
    connect_info => [
        'dbi:mysql:myappdb',
        'user',
        'password',
        {
            mysql_enable_utf8 => 1
        }
    ],
);
1;

方法2 - DBIx::Class::UTF8Columns を使う
(http://search.cpan.org/~jrobinson/DBIx-Class-0.08009/lib/DBIx/Class/UTF8Columns.pm)
$ vim lib/MyApp/Schema/Hoge.pm

package MyApp::Schema::Hoge;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components(qw/UTF8Columns Core/);
__PACKAGE__->table("tag");
__PACKAGE__->add_columns(
  "shop",
  { data_type => "INT", default_value => "", is_nullable => 0, size => 11 },
  "id",
  { data_type => "SMALLINT", default_value => "", is_nullable => 0, size => 5 },
  "name",
  { data_type => "VARCHAR", default_value => "", is_nullable => 0, size => 255 },
);
__PACKAGE__->set_primary_key("shop", "id");
__PACKAGE__->utf8_columns(qw/name/);    #static で作った場合、add_columns の後に記述すること
1;

どちらでもフラグ付きで放り込んで、フラグ付きで取り出せる。
もちろんmysqlでも普通に見える。
備考:方法1のほうが便利・・・しかし
This option is experimental and may change in future versions.
ex) http://search.cpan.org/~capttofu/DBD-mysql-4.006/lib/DBD/mysql.pm

さらに参考
http://search.cpan.org/~cfranks/HTML-FormFu-0.02004/lib/HTML/FormFu/Manual/Unicode.pod

2008-03-05 22:05:01

DBIC 本番稼動後の カラムの追加

[ DBIx::Class ]

1.DBにSQLでカラム追加
ALTER TABLE table_name ADD column_name varchar(255);

2.スキーマにカラム追加(staticで作った場合)
  "column_name",
  {
    data_type => "VARCHAR",
    default_value => undef,
    is_nullable => 1,
    size => 255,
  },

3.アプリケーションの記述変更
mod_perl 等、必要なら apache の restart

2008-03-05 22:00:31

DBICでMySQLのtimestamp

[ Catalyst ] [ DBIx::Class ] [ MySQL ]

TIMESTAMP型のカラムは CatalystのHelperでスキーマを作ると

  "time",
  {
    data_type => "TIMESTAMP",
    default_value => "CURRENT_TIMESTAMP",
    is_nullable => 0,
    size => 14,
  },

こんなのができる。
insert や他のカラムをupdateすると現在時刻が入る。

意識的に入れるときは undef を入れる。
$c->model('MyAppDB::Hoge')->update_or_create({
    time => undef,
});

Fedora-7,MySQL-5.0.41,DBIC-0.08009,Perl-5.10.0

2008-03-05 21:48:44

Catalystテストサーバでの DBICのSQLログ表示

[ Catalyst ] [ DBIx::Class ]

一時的には
環境変数をenvセットでテストサーバ駆動
$ env DBIC_TRACE=1 ./script/myapp_server.pl -r

いつも出す時は
MyApp.pm に
$ENV{'DBIC_TRACE'} = 1;
と書き込んでおく。

Apache等の本番環境に移した後は気になるところだけ
$c->model('DBIC')->storage->debug(1);
$c->model('DBIC')->storage->debugfh(IO::File->new('/log/DBIC.log', 'w'));

通常のDBIC(non Catalyst)では
my $schema = DB::Hoge->connect('dbi:mysql:dbname;localhost','ID','PW');

$schema->storage->debug(1);
$schema->storage->debugfh(IO::File->new('/log/DBIC.log', 'w'));

2008-01-05 11:36:34

prefetch と join の使い分け

[ DBIx::Class ]

とてもわかりやすい
http://perldoc.jp/docs/modules/DBIx-Class-0.07006/lib/DBIx/Class/Manual/Cookbook.pod
リレーション先の値を、後々出力で使う場合は prefetch 、検索・ソートにしか使用しない場合は join 。

2008-01-05 11:35:22

DBICでLEFT JOIN

[ DBIx::Class ]

from アトリビュートを使用する。
http://search.cpan.org/~ash/DBIx-Class-0.08008/lib/DBIx/Class/ResultSet.pm#from

belongs_to を使用してリレーションしていると
普通の join (INNER JOIN) となる。
場合によって、left join を使用しなければならない時、
以下のように from アトリビュートを使用する。

my $it = $schema->resultset('Goods')->search(
 {
  'stock.quantity' => {is => undef},
 },
 {
  columns => [qw/goods_id/],
  from => [
   { me => 'goods' }, #ここで goods テーブルを指定するのがポイント
   [
    { stock => 'stock', -join_type => 'left' },
    { 'stock.goods' => 'me.goods_id' }
   ],
  ],
 }
);

これで
SELECT me.goods_id FROM goods me LEFT JOIN stock stock ON ( stock.goods = me.goods_id ) WHERE ( stock.quantity IS NULL );
となる。

どんなSQLが発行されているかは DBIC_TRACE をオンにすると見ることが出来る。
$ENV{DBIC_TRACE} = 1;
ex) http://www.rwds.net/wiki?page=DBIx%3A%3AClass%A4%CE%B3%D0%BD%F1

2007-07-25 23:57:20

MeCab-Senna-TritonnのRPMインストールとCatalystでのSearch

[ Catalyst ] [ DBIx::Class ] [ MySQL ]

※Fedora7を使用

[mecab導入]
楽だ。
yum -y mecab mecab-devel mecab-ipadic perl-mecab


[senna導入]
cd /home/hoge/rpmbuild/SOURCES
wget http://iij.dl.sourceforge.jp/senna/25607/senna-1.0.7.tar.gz
tar xzvf senna-1.0.7.tar.gz
SPEC発見。
cp senna-1.0.7/senna.spec /home/hoge/rpmbuild/SPECS/.
cd ../
rpmbuild -bb SPECS/senna.spec

あら、エラーが出た。
error: File /usr/src/redhat/SOURCES/libsenna-1.0.7.tar.gz: No such file or directory

SPEC修正。
vi SPECS/senna.spec

@@ -2,7 +2,7 @@
 %define release 1

 Summary: An Embeddable Fulltext Search Engine
-Name: libsenna
+Name: senna
 Version: %{version}
 Release: %{release}
 License: LGPL

再度ビルド。
rpmbuild -bb SPECS/senna.spec

以下のメッセージが出てRPMができた。
Wrote: /home/hoge/rpmbuild/RPMS/i386/senna-1.0.7-1.i386.rpm
Wrote: /home/hoge/rpmbuild/RPMS/i386/senna-devel-1.0.7-1.i386.rpm
Wrote: /home/hoge/rpmbuild/RPMS/i386/senna-debuginfo-1.0.7-1.i386.rpm

インストール
cd RPMS/i386/
rpm -ivh senna-1.0.7-1.i386.rpm senna-debuginfo-1.0.7-1.i386.rpm senna-devel-1.0.7-1.i386.rpm


[Tritonn + MySQL導入]
SennaのMySQLバインディングパッチが Tritonn という名の別プロジェクトになったらしい。
ex) http://qwik.jp/tritonn/about.html

cd SOURCES/
wget http://iij.dl.sourceforge.jp/tritonn/26391/mysql-5.0.41-tritonn-1.0.3.tar.gz
tar xzvf mysql-5.0.41-tritonn-1.0.3.tar.gz
現在使用中のディストリ版MySQLを上書きして使いたいので、RPM化する際に mysql-5.0.41 になってもらうことにする。
mv mysql-5.0.41-tritonn-1.0.3 mysql-5.0.41

Fedoraのtestにmysql-5.0.45-1があがっていたのでSRPMをもらう。
cd ../SRPMS/
wget ftp://ftp.riken.jp/Linux/fedora/updates/testing/7/SRPMS/mysql-5.0.45-1.fc7.src.rpm
rpm -ivh mysql-5.0.45-1.fc7.src.rpm
SPECを流用。
cd ../SPECS/
cp mysql.spec mysql-tritonn.spec
vi mysql-tritonn.spec

以下のように変更。
make testは通らないのでコメントアウトした(普通に./configureしても make test はエラーになったのであきらめた)。
--- mysql.spec  2007-07-23 07:16:27.000000000 +0900
+++ mysql-tritonn.spec  2007-07-25 10:31:57.000000000 +0900
@@ -1,15 +1,15 @@
 Name: mysql
-Version: 5.0.45
+Version: 5.0.41
 Release: 1%{?dist}
-Summary: MySQL client programs and shared libraries
+Summary: MySQL client programs and shared libraries and tritonn
 License: GPL
 Group: Applications/Databases
 URL: http://www.mysql.com

 # Regression tests take a long time, you can skip 'em with this
 %{!?runselftest:%define runselftest 1}

-Source0: http://dev.mysql.com/get/Downloads/MySQL-5.0/mysql-%{version}.tar.gz
+Source0: mysql-5.0.41.tar.gz
 Source1: mysql.init
 Source3: my.cnf
 Source4: scriptstub.c
@@ -153,6 +153,8 @@
 export CFLAGS CXXFLAGS

 %configure \
+       --with-senna=/usr \
+       --with-mecab=/usr \
        --with-readline \
        --with-openssl \
        --without-debug \
@@ -189,7 +191,7 @@
   esac
   export MTR_BUILD_THREAD

-  make test
+#  make test
 %endif

 %install


必要なファイルをコピーする(このままではman 関連が無くてエラーになる - 2007.7.26 0:16追記 mysql-5.0.41-tritonn-1.0.3.tar.gz のソース修正されてman入ったかも~未確認)。
cd ../SOURCES/
tar xzvf mysql-5.0.45.tar.gz
cp mysql-5.0.45/man/* mysql-5.0.41/man/.

mysql-5.041(Tritonn内蔵版)のtarボール作成する。
tar czvf mysql-5.0.41.tar.gz mysql-5.0.41

Build時必要なものをインストール。
yum install gperf readline-devel ncurses-devel

ビルド開始。
cd ../SPECS/
rpmbuild -ba mysql-tritonn.spec

できた。
Wrote: /home/hoge/rpmbuild/SRPMS/mysql-5.0.41-1.fc7.src.rpm
Wrote: /home/hoge/rpmbuild/RPMS/i386/mysql-5.0.41-1.fc7.i386.rpm
Wrote: /home/hoge/rpmbuild/RPMS/i386/mysql-libs-5.0.41-1.fc7.i386.rpm
Wrote: /home/hoge/rpmbuild/RPMS/i386/mysql-server-5.0.41-1.fc7.i386.rpm
Wrote: /home/hoge/rpmbuild/RPMS/i386/mysql-devel-5.0.41-1.fc7.i386.rpm
Wrote: /home/hoge/rpmbuild/RPMS/i386/mysql-bench-5.0.41-1.fc7.i386.rpm
Wrote: /home/hoge/rpmbuild/RPMS/i386/mysql-test-5.0.41-1.fc7.i386.rpm
Wrote: /home/hoge/rpmbuild/RPMS/i386/mysql-debuginfo-5.0.41-1.fc7.i386.rpm

ここに置いておきます。
mysql-5.0.41-1.fc7.src.rpm
mysql-5.0.41-1.fc7.i386.rpm
mysql-bench-5.0.41-1.fc7.i386.rpm
mysql-debuginfo-5.0.41-1.fc7.i386.rpm
mysql-devel-5.0.41-1.fc7.i386.rpm
mysql-libs-5.0.41-1.fc7.i386.rpm
mysql-server-5.0.41-1.fc7.i386.rpm
mysql-test-5.0.41-1.fc7.i386.rpm

インストールする。
cd /home/hoge/rpmbuild/RPMS/i386/
rpm -Uvh mysql-*

以下URL参照に動作確認した。
http://qwik.jp/senna/check_install.html

念のためyumで上書きされないように yum.conf を編集。
vi /etc/yum.conf
exclude=mysql*


[Catalystで使ってみる]
FULLTEXTインデックスを作る。
mysql> CREATE FULLTEXT INDEX review_index ON goods_att(review);

カラムが text 型の場合、最大値255以下で指定
mysql> drop index review_index on goods_att;
mysql> CREATE FULLTEXT INDEX review_index ON goods_att(review(255));

以下の2パターンが使えるようだ。
ex1) http://asakura.g.hatena.ne.jp/asakura-t/comment?date=20070405
use SQL::Abstract::Fulltext::MySQL;
my $rs = $c->model('GoodsDB::GoodsAtt')->search({
    -fulltext => {
        -match   => 'review',
        -against => $q,
        -boolean => 1,
    },
});

ex2) http://blog.hide-k.net/archives/2006/08/dbixclassfullte.php
my $rs = $c->model('GoodsDB::GoodsAtt')->search({
})->search_literal('match(review) against(?)', $q);

ex1を使うことにする。
SQL::Abstract::Fulltext::MySQLを use して使用することにする。

Bench Mark してみる(マシンスペック - Celeron2.5GHz, Memory 384MB)。
Records: 121141

まずは普通に mysql クライアントから。
mysql> select count(*) from goods_att where match(review) against('強力');
+----------+
| count(*) |
+----------+
|     1492 |
+----------+
1 row in set (0.01 sec)

like 検索と比べる。
mysql> select count(*) from goods_att where review LIKE "%強力%";;
+----------+
| count(*) |
+----------+
|     1492 |
+----------+
1 row in set (0.62 sec)

次にCatalystで比較。4ワードで試した。
senna
0.339656s
0.840683s
6.052170s
0.880074s

like
2.199280s
1.740971s
12.650366s
1.220464s

2007-04-07 15:32:23

Cache::Memcached と DBIx::Class と TT

[ DBIx::Class ] [ Template-Toolkit ] [ Perl ]

"Cache::Memcached と DBIx::Class"の補足。
TT使用時にテンプレート上でDBIx::Classの belongs_to ,has_many 項目は呼び出せない。
理由は上に同じで、キャッシュされていないから。

2007-04-07 12:31:08

Cache::Memcached と DBIx::Class

[ DBIx::Class ] [ Perl ]

Cache::Memcached (Cache::FileCache,Catalyst::Plugin::Cache::Memcachedも) はオブジェクトがキャッシュできる。とても便利。
でもDBIx::Classのオブジェクトは、キャッシュできないことがある。

サンプル:
#!/usr/bin/perl
use strict;
use Cache::Memcached;
use TestDB;

my $memd = new Cache::Memcached {'servers' => [ "127.0.0.1:11211" ]};
my $schema = TestDB->connect("dbi:mysql:testdb;localhost",'user','password');

#OK pattern
my $ok = $schema->resultset('Test')->find(1);
$memd->set('key', $ok);
print $memd->get('key')->message,"\n";

#NG pattern
my $ng = $schema->resultset('Test')->search({id => 1});
$memd->set('key', $ng); #ここでエラー
print $memd->get('key')->next->message,"\n";

$ngはSQL実行結果の値を持っていない。
DBIx::Classのイテレータが素晴らしいのはSQLを最後の最後で実行することだし・・。

↓以下なら動く。Cache::Memcached使用時、イテレータは要注意。

#NG->OK pattern 1
my $ng = $schema->resultset('Test')->search({id => 1})->next;
$memd->set('key', $ng);
print $memd->get('key')->message,"\n";

#NG->OK pattern 2
my @ng = $schema->resultset('Test')->search({id => 1});
$memd->set('key', \@ng);
foreach (@{$memd->get('key')}) {
        print $_->message,"\n";
}