日向夏特殊応援部隊

俺様向けメモ

Iterator::GruopedRange 0.02 Released

さて、ちょっとしたことからタイトルのようなモジュールを書いて見ました。Iterator::GroupedRange モジュールは簡単に説明すると、リストまたは別の(複数行ずつ返す)イテレータから指定行数分まとめて列挙するというモジュールです。

0.01 で SYNOPSIS を思いっきり間違えてたので、正しいものをこちらに。*1

use Iterator::GroupedRange;

my @ds = (
  [ 1 .. 6 ],
  [ 7 .. 11 ],
  [ 11 .. 25 ],
);

my $i1 = Iterator::GroupedRange->new( sub { shift @ds; }, 10 );
$i1->next; # [ 1 .. 10 ]
$i1->next; # [ 11 .. 20 ]
$i1->next; # [ 21 .. 25 ]

my $i2 = Iterator::GroupedRange->new( [ 1 .. 25 ], 10 );
$i2->next; # [ 1 .. 10 ]
$i2->next; # [ 11 .. 20 ]
$i2->next; # [ 21 .. 25 ]

例えばこんな風に使います。

my $ids = $dbh_friend->selectcol_arrayref('SELECT friend_user_id FROM friends WHERE user_id = ?', +{ Columns => [1] }, 10028);
my $iterator = Iterator::GroupedRange->new( $ids, 1000 );
my @rs;
while ( $iterator->has_next ) {
  my $ranged_ids = $iterator->next;
  push(@rs, $dbh_owneres->selectcol_arrayref(
    sprintf('SELECT owner_user_id FROM user_application WHERE application_id = ? AND owner_user_id IN (%s)', substr('?,' x @$ranged_ids, 0, -1)),
    +{ Columns => [1], },
    @$ranged_ids,
  ));
}

ちなみに下記のように、やろうと思えばこれらの入れ子なんかも出来ます。

sub installed_users_iterator {
  my ($self, $uids) = @_;
  my $uids_iterator = Iterator::GroupedRange->new( $uids, 1000 );
  my $dbh = $self->dbh;
  return Iterator::GroupedRange->new(sub {
    return [] unless ( $uids_iterator->has_next );
    my $ranged_uids = $uids_iterator->next;
    $dbh->selectcol_arrayref( "SELECT ...", +{ Columns => [1] }, @$ranged_uids );
  }, 1000);
}

この例だと selectcol_arrayref() の実行結果が1000レコードに満たない配列リファレンスが返って来たとしても、高々1000件ずつ取得出来る Iterator を返すと言う感じです。

つまりこれ何のために作ったかと言うと、分割可能なクエリを物理的に異なるDBなどに対して順番にぶつけていくような処理に使えるかなと思って書いた次第です。その際に出来る限りクエリの発行回数を減らす為に書いてみた訳です。

と言うわけでドキュメントが無くてだいぶ酷い状態ですけど、よろしければご利用下さいませ。

*1:そして今気づいたけど method に関する POD が無かったことに気づいた><