Practice of Programming

プログラム とか Linuxとかの話題

AEとQudoを組み合わせる

別に書くほどのことは無いのですが。

QudoをAEとの組み合わせで使ったので、work メソッドの中身をAE::timer に渡すことにしました。

my $qudo = Qudo->new(...);
$w = AE::timer 0, $qudo->{work_delay}, sub {
    my $manager = $qudo->manager;
    unless ($manager->has_abilities) {
        Carp::croak 'manager dose not have abilities.';
    }
    $manager->work_once;
  }

これだけ。

AE::signal とかでも良いような気もする。

Text::Parts 0.13 リリース

Text::Parts 0.13

会社の隣の人に、write_filesが完全にバグってると指摘されたので、直しましたorz
テストでサイズだけチェックしてたんですが、まぁ、サイズ分書き出してるんだから、意味ないよね…
md5_hexでファイル内容をチェックするように変更しました。

後、typoがやたら目に付くので、ispellでチェックして直しました。たくさんあったorz

また、Windows環境でのテストがこけてたのを放置してましたが、修正しました。

Text::Parts 0.09 リリース

Text::Parts

2000くらいファイル分割しようと思ったら、"Too many open files"と言われてしまったので。
no_open オプションを使って、write_files を使うのであれば、特に問題は無かったかと思いますが、split メソッドを使って、自分で write_file を使うと、お亡くなりになってたみたいなので、その対応をしました。


no_open オプションを付けても、write_file や all の際には、勝手に open_and_seek /close するようにしました。

    use Text::Parts;
    
    my $splitter = Text::Parts->new(file => $file, no_open => 1);
    my (@parts) = $splitter->split(num => 2000);
    my $i;
    foreach my $part (@parts) {
       $part->write_file("file_name" . $i++);
    }

getline の場合は、open_and_seek と close を最初と最後に自分でよんで下さい。

    use Text::Parts;
    
    my $splitter = Text::Parts->new(file => $file, no_open => 1);
    my (@parts) = $splitter->split(num => 2000);
    foreach my $part (@parts) {
       $part->open_and_seek;
       while(my $l = $part->getline) {
          # ...
       }
       $part->close;
    }


ちなみに、Linuxであれば、1020を超えるような分割をする場合に、no_open オプションをつけてください。
ulimit -n 2000 のように開けるファイルディスクリプタの数を変更すれば、特に何もオプションつけなくてもOKですが。

Teng::Plusing::SearchBySQLAbstractMore 0.07リリース

Teng::Plusing::SearchBySQLAbstractMore

Teng内で用意されている_executeメソッドを使うようにしましたので、エラーメッセージが改善されています。
というか、普通のTengが出すエラーメッセージになりました。

# なんで、素のexecuteを使うようにしちゃってたのかが謎

IO::Pty::Easy が簡単・便利

ちょっと遊んでいたら見つけたのですが、これ簡単ですね。

use IO::Pty::Easy;
my $pty = IO::Pty::Easy->new;

$pty->spawn("mysql -u root");
while ($pty->is_active) {
  while (my $o = $pty->read(1)) {
    print $o;
  }
  if (my $sql = <>) {
      $pty->write($sql);
  } else {
      $pty->write("exit\n");
      last;
  }
}

こんな感じにするだけで、perl から mysql の shell が叩けるよ。
いや、別に誰も嬉しくないけどさ。


top も叩ける。

use IO::Pty::Easy;
my $pty = IO::Pty::Easy->new;

$pty->spawn("top");
while ($pty->is_active) {
    while (my $o = $pty->read(2)) {
	print $o;
    }
}

いや、別に誰も嬉しくないけどさ。


余談ですが、batch 処理でtopを使うなら、

% top -b

を使うと良いですよ。


Expect 使って書くような処理を書いてみる。

use IO::Pty::Easy;
my $pty = IO::Pty::Easy->new;

$pty->spawn("htpasswd -c test_passwd ktat");
while ($pty->is_active) {
    $|= 1;
    while (my $o = $pty->read(1)) {
	print $o;
	if ($o =~m{password:}) {
	    my $passwd;
	    {
		chomp($passwd = <>);
		$passwd or redo;
	    }
	    $pty->write($passwd . "\n");
	}
    }
}

使いどころはあるんじゃないかな。


vi でファイルを作って、perlで実行とか。

use IO::Pty::Easy;
my $pty = IO::Pty::Easy->new;

$pty->spawn("vi test_pty.pl");
my @operation = (
                 chr(0x1b) . 'V',
                 'G',
                 'x',
                 'i',
                 '#!/usr/bin/perl' . "\n",
                 'print "Hello, World!\n"' . "\n",
                 chr(0x1b),
                 ":wq\n",
                );

while (@operation) {
  $pty->write(shift @operation);
}

while ($pty->is_active) {
  while (my $o = $pty->read(1)) {
    # print $o;
  }
}

$pty->spawn('perl test_pty.pl');

while (my $o = $pty->read(1)) {
    print $o;
}

SQL の Query Builder についてのとりとめのない話

誰か、ココが嫌だよQuery Builder的なエントリを書いてくれないかな。

使いどころ

  • 動的に条件を組み立てるところ(whereに与えるカラムが変わったりする)

使わなかったら、複雑な条件を動的に組み立てる場合は、sprintfでSQLを組み立てるみたいなことになって、それは劣化Query Builderを自作しているのと変わらないんじゃないですかね。


生のSQLも別に嫌いじゃないし、普通に書いてるけど、コードにはあんまり書かないです。

Query Builder のパフォーマンス

パフォーマンスについては、クエリを数個作る程度のことが多少遅くても、ボトルネックにはならない。
1/7500秒で処理できていたのが、1/2500秒かかるようになったとしても、困る人はあんまりいない。

Query Builder は余計なことするからなー

しません。


DBIx::Class はリレーションを設定していると、while で回して、foreign key で数珠つなぎにやってたら、千回くらいクエリ投げちゃった♪
みたいなことはあり得るかも知れませんけど。SQL::Abstract自体は、SQLを組み立てるだけです。
それは、Query Builderの問題ではなくて、ORマッパーの問題です。

SQL::Abstract::Moreは普通にSQL書くのとあまり変わらない

join はちょっと微妙なところはありますが。また、where 部分とかはどっかで組み立てた結果をそこに当てることになること場合は、ぱっと見分かりませんけどね。where の順番とかはハッシュで渡すと変わってしまいますが、配列リファレンスで一つずつ条件を渡せば、順番通りになります。

my ($s, @binds) = $sql->select                  # select
  (
   '-columns' => [                              # x.id, y.id, count(*) as cnt
		  'x.id',
		  'y.id',
		  'count(*) as cnt',
		 ],
   '-from' => [                                 # from
	       '-join',
	       'table1|x',                      # table1 as x
	       'x.id=y.table1_id,x.col1=y.col1',# on x.id = y.table1_id and x.col1 = y.col1  <- ここだけ、順番が違う
	       'table2|y'                       # inner join table2 as y  
	      ],
   '-where' => {                                 # where
		'x.type' => '1',                 # x.type = 1 and date = '2011-12-05' and y.name = 'name'
		'date'   => '2011-12-05',
		'y.name' => 'name'
	       },
   '-group_by' => [                      # group by (x.id, y.id)
		   'x.id',
		   'y.id'
		  ],
   '-limit' => 3                         # limit 3
  );

運用者の立場

というわけでもないけど、DBIx::QueryLogで吐き出されるSQLを見て、index使われてないからこのカラムもwhereに入れてとか、それパーティショニング使われてないから、このカラムは必ず入れてみたいなのは、Query Builder に何を使おうが別に問題なくできる。
デバッグでも、基本的にQueryLogを見て、組み立て部分間違ってないかなぁというのをチェックするとかが多い。
本番にいきなり投入されて、なんか遅くなっちゃったんだけど…とかだったらご残念としか言いようがないけど。


まぁ、この辺は、Query Builderと関係ないレイヤーの話なんじゃないかと思ったりします。


※その話でいうと、SQL::Abstract::Moreで吐き出すSQLが整形されていないのがみにくいのは結構見づらいので、整形するコードでも書こうかなぁと思ったり、思わなかったりする。

Teng::Plugin::SearchBySQLAbstractMore リリース

ケースによりますが、最近のプロジェクトだと、searchメソッドじゃ足りないことが多かった(50%くらい)ので、SQL::Abstract::Moreをクエリービルダーに使えるようにしました。
Teng::Plugin::SearchBySQLAbstractMore

リリースしたらblog書こうと思ってたら、リリースしては改変を繰り返してしまい、0.06っていう…どんだけバージョン上げてんだかorz
気を取りなおして…。

Tengのクエリービルダーとして、SQL::Abstract::Moreを使えるようになります。
これの利点としては、

  • group by 可能
  • having とかも書ける
  • (x = 1 or y = 2) のような別カラムの or が書きやすい
  • 複雑な join も書ける
  • SQL::Abstractに慣れてる人は書きやすい & SQL::Abstractを素で使うよりは全然楽

という、感じです。
デメリットとしては、

  • クエリが整形されてないのでデバッグ時に見づらい
  • SQL::Makerより2倍以上遅い
                    Rate sql_abstract_more      sql_abstract         sql_maker
sql_abstract_more 2692/s                --              -21%              -64%
sql_abstract      3422/s               27%                --              -55%
sql_maker         7566/s              181%              121%                --

ベンチマークのコードは、https://gist.github.com/1566118
とはいえ、実際問題としてはこれがボトルネックになることはないでしょう。


Teng::Plugin::SearchBySQLAbstractMore には、以下の5つのプラグインがあります(最初3つだったんだけどな...)。

  • Teng::Plugin::SearchBySQLAbstractMore
  • Teng::Plugin::SearchBySQLAbstractMore::Pager
  • Teng::Plugin::SearchBySQLAbstractMore::Pager::MySQLFoundRows
  • Teng::Plugin::SearchBySQLAbstractMore::Pager::Count
  • Teng::Plugin::SearchBySQLAbstractMore::Pager::CountOrMySQLFoundRows

で、普通にload_pluginすれば、それぞれ、

  • search_by_sql_abstrat_more
  • search_by_sql_abstrat_more_with_pager
  • search_by_sql_abstrat_more_with_pager (CALC_FOUND_ROWS を使うやつ)
  • search_by_sql_abstrat_more_with_pager (count(*) を使うやつ -- group by や having を使ってると微妙かも知れない)
  • search_by_sql_abstrat_more_with_pager (count(*) or CALC_FOUND_ROWS(group by or havingがある時のみ)を使う)

が生えます。Teng の search と一応互換性があるはず。Tengのsearch関連のテストをいくつか通しています。


Tengと同様の使い方で引数を渡す場合は、Tengの引数 -> SQL::Abstract::Moreに渡せるように変換しているのですが、
SQL::Abstract::More と同様の引数も取ることが出来ます。
その場合は、第一引数を table名の代わりにハッシュリファレンスを渡します。

 $teng->search({
    -columns => [qw/col1 col2 col3/],
    -from => 'table',
    -where => {
        col1 => {'>' => "1"},
    },
 });

この渡し方であれば、SQL::Abstract::Moreと全く同じ使い方です。
SQL::Abstract::Moreの使い方については、SQLを組み立てるものという記事に若干書いてます。


search、search_with_pager というメソッド名で使いたい場合は、

 YourClass->load_plugin('Teng::Plugin::SearchBySQLAbstractMore', {
    alias => 'search_by_sql_abstract_more' => 'search',
 });
 YourClass->load_plugin('Teng::Plugin::SearchBySQLAbstractMore::Pager', {
    alias => 'search_by_sql_abstract_more_with_pager' => 'search_with_pager',
 });
 YourClass->install_sql_abstract_more(pager => 'Pager'); # or pager => 'Pager::MySQLFoundRows'

のようにすれば、良いかと思います。メンドクサイよっていう人は、

 use Teng::Plugin::SearchBySQLAbstractMore;
 YourClass->install_sql_abstract_more(alias => 'search', pager => 'Pager'); # or pager => 'Pager::MySQLFoundRows' / 'Pager::Count'

とすると、

  1. YourClass に search を生やす
  2. YourClass に search_with_pager を生やす

※aliasって、ドキュメントに書かれてないので、ずっと残る物なのかは知らないです。


Teng::search を置き換えることもできます。

 use Teng::Plugin::SearchBySQLAbstractMore;
 YourClass->install_sql_abstract_more(pager => 'Pager', replace => 1);

とすると、

  1. Teng の search メソッドを search_by_sql_abstract_more で置き換え
  2. search_with_pager を生やす

Teng::search を置き換えるのはあんまりよろしくない気がするので、気になる方は使わない方が良いと思います。
ていうか、いらない気がしますね。無くすかも知れません。

DBにOracleを使ってるんだよ、とか言う場合は、

YourClass->sql_abstract_more_new_option(sql_dialect => 'Oracle');

のように、渡すことが出来ます(Oracleないので確かめてないけど)。このnew_optionに渡した引数は、そのままSQL::Abstract::Moreのnew時に渡されます。
詳しくはそちらのドキュメントを読んで頂ければと思います。

Tengのsearchだと表現できないけど、SQLの条件を文字列で組み立てるのはちょっと…
っていう方は使ってみて頂ければと思います。


追記: DBIx::QueryLogを使う場合、以下のコードを書いておいたほうが良いです。

%DBIx::QueryLog::SKIP_PKG_MAP = (
      'Teng::Plugin::SearchBySQLAbstractMore' => 1,
      'Teng::Plugin::SearchBySQLAbstractMore::Pager' => 1, # ここは使うPagerプラグインを適当に
      %DBIx::QueryLog::SKIP_PKG_MAP
);

ここに書かれたpackageは、無視して、その先までたどってログに行数を出してくれます。

xaicron さんの以下の記事に書かれています。
http://blog.livedoor.jp/xaicron/archives/51554520.html