2006-03-28

Class::DBI::Sweet::Pie - Class::DBI::Sweetに集約関数を追加する

 これでClass::DBIで集約関数が使いたくなっても困らないぞ、と。

 あとはドキュメントを整備したりテストコードを書けばいいかな……。

 YAPCが終わる頃までにはCPANに登録したいと思うんだけど*1、正しいモジュールの作り方をすっかり忘れてしまってますよ。h2xsとか使うんだっけ?

SYNOPSYS

  package MyData::CD;
  use base qw/Class::DBI::Sweet/;
  __PACKAGE__->has_a( artist => 'MyData::Artist' );
  use Class::DBI::Sweet::Pie;
  __PACKAGE__->mk_aggregate_function('sum');
  __PACKAGE__->mk_aggregate_function( max => 'maximum');

  package MyData::Artist;
  use base qw/Class::DBI::Sweet/;
  __PACKAGE__->has_many( cds => 'MyData::CD' );
  use Class::DBI::Sweet::Pie;
  __PACKAGE__->mk_aggregate_function('min');

  package main;

  # 一番高価なCDの価格
  $max_price = MyData::CD->maximum( 'price' );

  # fooさんのCDの合計金額
  $total_price = MyData::CD->sum( 'price', 'artist.name' => 'foo' );

  # fooさんのCDで一番安価なものの価格
  ($artist) = MyData::Artist->search( name => 'foo' );
  $min_price = $artist->min('cds.price');


ソースコード

package Class::DBI::Sweet::Pie;
use strict;
use vars qw/$VERSION @EXPORT @EXPORT_OK/;
$VERSION = '0.02';

use Exporter::Lite;
@EXPORT = qw/mk_aggregate_function/;

sub mk_aggregate_function {
    my $class = shift;
    my ($aggregate_func, $alias) = @_;
    $alias ||= $aggregate_func;

    $class->set_sql( "Join_Retrieve_$aggregate_func" => <<__SQL__ );
  SELECT $aggregate_func( %s )
  FROM   %s
  WHERE  %s
__SQL__

    no strict 'refs';
    *{"$class\::$alias"} = sub {
	my $self = shift;
	my $class = ref($self) || $self;
	my $aggregate_column = shift;
	my ($criteria, $attributes) = $class->_search_args(@_);

	if ($aggregate_column !~ /^(\w+)\./) {
	    $aggregate_column = "me.$aggregate_column";
	}
	elsif (ref $self) {
	    my $table = $1;
	    $attributes->{prefetch} = [ $table ];
	    foreach my $pcol ($self->primary_column) {
	        $criteria->{ $pcol } = $self->$pcol;
	    }
	}

	# make sure we take copy of $attribues since it can be reused
	my $agfunc_attr = { %{$attributes} };

	# no need for LIMIT/OFFSET and ORDER BY in AGGREGATE_FUNC()
	delete @{$agfunc_attr}{qw( rows offset order_by )};

	my ($sql_parts, $classes, $columns, $values) = $class->_search( $criteria, $agfunc_attr );

	my $sql_method = 'sql_' . ($attributes->{sql_method} || 'Join_Retrieve') . "_$aggregate_func";
	my $sth = $class->$sql_method( $aggregate_column, @{%$sql_parts}{qw/ from where /} );

	$class->_bind_param( $sth, $columns );
	return $sth->select_val(@$values);
    }
}

1;
__END__
=head1 NAME

Class::DBI::Sweet::Pie - create Aggregate Function

=head1 SYNOPSYS

  package MyData::CD;
  use base qw/Class::DBI::Sweet/;
  __PACKAGE__->has_a( artist => 'MyData::Artist' );
  use Class::DBI::Sweet::Pie;
  __PACKAGE__->mk_aggregate_function('sum');
  __PACKAGE__->mk_aggregate_function( max => 'maximum');

  package MyData::Artist;
  use base qw/Class::DBI::Sweet/;
  __PACKAGE__->has_many( cds => 'MyData::CD' );
  use Class::DBI::Sweet::Pie;
  __PACKAGE__->mk_aggregate_function('min');

  package main;

  # 一番高価なCDの価格
  $max_price = MyData::CD->maximum( 'price' );

  # fooさんのCDの合計金額
  $total_price = MyData::CD->sum( 'price', 'artist.name' => 'foo' );

  # fooさんのCDで一番安価なものの価格
  $artist = MyData::Artist->search( name => 'foo' );
  $min_price = $artist->min('cds.price');

=head1 DESCRIPTION

 Class::DBI::Sweetを使ったテーブルで集約関数を簡単に使えるようになります。

=cut

Class::DBI::Plugin::AggregateFunctionでCOUNT(*)する

 以下で動くはず。たぶん。

 類似のモジュールはClass::DBI::Plugin::CountSearchとかClass::DBI::Search::Countとかいっぱいあるので、あんまり意味はありませんが。

 COUNT(DISTINCT column)とかするときは使えるかもしれない。

package MyData::CD;
use base qw/Class::DBI/;
use Class::DBI::Plugin::AggregateFunction;
__PACKAGE__->mk_aggregate_function( count => 'counting' );

package main;
$count = MyData::CD->counting('*', artist => $artist);
$artist_count MyData::CD->counting('distinct artist');

*1Class::DBI::Plugin::AggregateFunctionも一緒に。