package Class::DBI::Search::Basic; =head1 NAME Class::DBI::Search::Basic - Simple Class::DBI search =head1 SYNOPSIS my $searcher = Class::DBI::Search::Basic->new( $cdbi_class, @search_args ); my @results = $searcher->run_search; # Over in your Class::DBI subclass: __PACKAGE__->add_searcher( search => "Class::DBI::Search::Basic", isearch => "Class::DBI::Search::Plugin::CaseInsensitive", ); =head1 DESCRIPTION This is the start of a pluggable Search infrastructure for Class::DBI. At the minute Class::DBI::Search::Basic doubles up as both the default search within Class::DBI as well as the search base class. We will probably need to tease this apart more later and create an abstract base class for search plugins. =head1 METHODS =head2 new my $searcher = Class::DBI::Search::Basic->new( $cdbi_class, @search_args ); A Searcher is created with the class to which the results will belong, and the arguments passed to the search call by the user. =head2 opt if (my $order = $self->opt('order_by')) { ... } The arguments passed to search may contain an options hash. This will return the value of a given option. =head2 run_search my @results = $searcher->run_search; my $iterator = $searcher->run_search; Actually run the search. =head1 SUBCLASSING =head2 sql / bind / fragment The actual mechanics of generating the SQL and executing it split up into a variety of methods for you to override. run_search() is implemented as: return $cdbi->sth_to_objects($self->sql, $self->bind); Where sql() is $cdbi->sql_Retrieve($self->fragment); There are also a variety of private methods underneath this that could be overriden in a pinch, but if you need to do this I'd rather you let me know so that I can make them public, or at least so that I don't remove them from under your feet. =cut use strict; use warnings; use base 'Class::Accessor::Fast'; __PACKAGE__->mk_accessors(qw/class args opts type/); sub new { my ($me, $proto, @args) = @_; my ($args, $opts) = $me->_unpack_args(@args); bless { class => ref $proto || $proto, args => $args, opts => $opts, type => "=", } => $me; } sub opt { my ($self, $option) = @_; $self->{opts}->{$option}; } sub _unpack_args { my ($self, @args) = @_; @args = %{ $args[0] } if ref $args[0] eq "HASH"; my $opts = @args % 2 ? pop @args : {}; return (\@args, $opts); } sub _search_for { my $self = shift; my @args = @{ $self->{args} }; my $class = $self->{class}; my %search_for; while (my ($col, $val) = splice @args, 0, 2) { my $column = $class->find_column($col) || (List::Util::first { $_->accessor eq $col } $class->columns) || $class->_croak("$col is not a column of $class"); $search_for{$column} = $class->_deflated_column($column, $val); } return \%search_for; } sub _qual_bind { my $self = shift; $self->{_qual_bind} ||= do { my $search_for = $self->_search_for; my $type = $self->type; my (@qual, @bind); for my $column (sort keys %$search_for) { # sort for prepare_cached if (defined(my $value = $search_for->{$column})) { push @qual, "$column $type ?"; push @bind, $value; } else { # perhaps _carp if $type ne "=" push @qual, "$column IS NULL"; } } [ \@qual, \@bind ]; }; } sub _qual { my $self = shift; $self->{_qual} ||= $self->_qual_bind->[0]; } sub bind { my $self = shift; $self->{_bind} ||= $self->_qual_bind->[1]; } sub fragment { my $self = shift; my $frag = join " AND ", @{ $self->_qual }; if (my $order = $self->opt('order_by')) { $frag .= " ORDER BY $order"; } return $frag; } sub sql { my $self = shift; return $self->class->sql_Retrieve($self->fragment); } sub run_search { my $self = shift; my $cdbi = $self->class; return $cdbi->sth_to_objects($self->sql, $self->bind); } 1;