X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FResultSet.pm;h=233597e45c0943b635a34155a17d193d565e2b10;hb=cdacba7e0c9b7bc947ae47238be4c6a25f9c7b13;hp=118496ecbbfdad4629e8ebe9cdbf45a0ae84fbc8;hpb=69a3916705dfcae6227bc744fdbb810d78ba2c45;p=dbsrgits%2FDBIx-Class-Historic.git diff --git a/lib/DBIx/Class/ResultSet.pm b/lib/DBIx/Class/ResultSet.pm index 118496e..233597e 100644 --- a/lib/DBIx/Class/ResultSet.pm +++ b/lib/DBIx/Class/ResultSet.pm @@ -19,38 +19,122 @@ __PACKAGE__->mk_group_accessors('simple' => qw/_result_class _source_handle/); =head1 NAME -DBIx::Class::ResultSet - Responsible for fetching and creating resultset. +DBIx::Class::ResultSet - Represents a query used for fetching a set of results. =head1 SYNOPSIS - my $rs = $schema->resultset('User')->search({ registered => 1 }); - my @rows = $schema->resultset('CD')->search({ year => 2005 })->all(); + my $users_rs = $schema->resultset('User'); + my $registered_users_rs = $schema->resultset('User')->search({ registered => 1 }); + my @cds_in_2005 = $schema->resultset('CD')->search({ year => 2005 })->all(); =head1 DESCRIPTION -The resultset is also known as an iterator. It is responsible for handling -queries that may return an arbitrary number of rows, e.g. via L -or a C relationship. +A ResultSet is an object which stores a set of conditions representing +a query. It is the backbone of DBIx::Class (i.e. the really +important/useful bit). -In the examples below, the following table classes are used: +No SQL is executed on the database when a ResultSet is created, it +just stores all the conditions needed to create the query. - package MyApp::Schema::Artist; - use base qw/DBIx::Class/; - __PACKAGE__->load_components(qw/Core/); - __PACKAGE__->table('artist'); - __PACKAGE__->add_columns(qw/artistid name/); - __PACKAGE__->set_primary_key('artistid'); - __PACKAGE__->has_many(cds => 'MyApp::Schema::CD'); - 1; +A basic ResultSet representing the data of an entire table is returned +by calling C on a L and passing in a +L name. - package MyApp::Schema::CD; - use base qw/DBIx::Class/; - __PACKAGE__->load_components(qw/Core/); - __PACKAGE__->table('cd'); - __PACKAGE__->add_columns(qw/cdid artist title year/); - __PACKAGE__->set_primary_key('cdid'); - __PACKAGE__->belongs_to(artist => 'MyApp::Schema::Artist'); - 1; + my $users_rs = $schema->resultset('User'); + +A new ResultSet is returned from calling L on an existing +ResultSet. The new one will contain all the conditions of the +original, plus any new conditions added in the C call. + +A ResultSet is also an iterator. L is used to return all the +Ls the ResultSet represents. + +The query that the ResultSet represents is B executed against +the database when these methods are called: + +=over + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=back + +=head1 EXAMPLES + +=head2 Chaining resultsets + +Let's say you've got a query that needs to be run to return some data +to the user. But, you have an authorization system in place that +prevents certain users from seeing certain information. So, you want +to construct the basic query in one method, but add constraints to it in +another. + + sub get_data { + my $self = shift; + my $request = $self->get_request; # Get a request object somehow. + my $schema = $self->get_schema; # Get the DBIC schema object somehow. + + my $cd_rs = $schema->resultset('CD')->search({ + title => $request->param('title'), + year => $request->param('year'), + }); + + $self->apply_security_policy( $cd_rs ); + + return $cd_rs->all(); + } + + sub apply_security_policy { + my $self = shift; + my ($rs) = @_; + + return $rs->search({ + subversive => 0, + }); + } + +=head2 Multiple queries + +Since a resultset just defines a query, you can do all sorts of +things with it with the same object. + + # Don't hit the DB yet. + my $cd_rs = $schema->resultset('CD')->search({ + title => 'something', + year => 2009, + }); + + # Each of these hits the DB individually. + my $count = $cd_rs->count; + my $most_recent = $cd_rs->get_column('date_released')->max(); + my @records = $cd_rs->all; + +And it's not just limited to SELECT statements. + + $cd_rs->delete(); + +This is even cooler: + + $cd_rs->create({ artist => 'Fred' }); + +Which is the same as: + + $schema->resultset('CD')->create({ + title => 'something', + year => 2009, + artist => 'Fred' + }); + +See: L, L, L, L, L. =head1 OVERLOADING @@ -607,6 +691,10 @@ of the resultset. sub single { my ($self, $where) = @_; + if(@_ > 2) { + $self->throw_exception('single() only takes search conditions, no attributes. You want ->search( $cond, $attrs )->single()'); + } + my $attrs = { %{$self->_resolved_attrs} }; if ($where) { if (defined $attrs->{where}) { @@ -1109,7 +1197,11 @@ is returned in list context. =cut sub all { - my ($self) = @_; + my $self = shift; + if(@_) { + $self->throw_exception("all() doesn't take any arguments, you probably wanted ->search(...)->all()"); + } + return @{ $self->get_cache } if $self->get_cache; my @obj; @@ -1261,6 +1353,11 @@ sub update { $self->throw_exception("Values for update must be a hash") unless ref $values eq 'HASH'; + carp( 'WARNING! Currently $rs->update() does not generate proper SQL' + . ' on joined resultsets, and may affect rows well outside of the' + . ' contents of $rs. Use at your own risk' ) + if ( $self->{attrs}{seen_join} ); + my $cond = $self->_cond_for_update_delete; return $self->result_source->storage->update( @@ -1603,7 +1700,8 @@ sub new_result { defined $self->{cond} && $self->{cond} eq $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION ) { - %new = %{$self->{attrs}{related_objects}}; + %new = %{ $self->{attrs}{related_objects} || {} }; # nothing might have been inserted yet + $new{-from_resultset} = [ keys %new ] if keys %new; } else { $self->throw_exception( "Can't abstract implicit construct, condition not a hash" @@ -1709,6 +1807,24 @@ sub _remove_alias { return \%unaliased; } +=head2 as_query + +=over 4 + +=item Arguments: none + +=item Return Value: \[ $sql, @bind ] + +=back + +Returns the SQL query and bind vars associated with the invocant. + +This is generally used as the RHS for a subquery. + +=cut + +sub as_query { return shift->cursor->as_query(@_) } + =head2 find_or_new =over 4 @@ -2167,97 +2283,127 @@ sub _resolved_attrs { my $self = shift; return $self->{_attrs} if $self->{_attrs}; - my $attrs = { %{$self->{attrs}||{}} }; + my $attrs = { %{ $self->{attrs} || {} } }; my $source = $self->result_source; - my $alias = $attrs->{alias}; + my $alias = $attrs->{alias}; $attrs->{columns} ||= delete $attrs->{cols} if exists $attrs->{cols}; - if ($attrs->{columns}) { - delete $attrs->{as}; - } elsif (!$attrs->{select}) { - $attrs->{columns} = [ $source->columns ]; + my @colbits; + + # build columns (as long as select isn't set) into a set of as/select hashes + unless ( $attrs->{select} ) { + @colbits = map { + ( ref($_) eq 'HASH' ) ? $_ + : { + ( + /^\Q${alias}.\E(.+)$/ ? $1 + : $_ + ) => ( /\./ ? $_ : "${alias}.$_" ) + } + } ( ref($attrs->{columns}) eq 'ARRAY' ) ? @{ delete $attrs->{columns}} : (delete $attrs->{columns} || $source->columns ); } - - $attrs->{select} = - ($attrs->{select} - ? (ref $attrs->{select} eq 'ARRAY' - ? [ @{$attrs->{select}} ] - : [ $attrs->{select} ]) - : [ map { m/\./ ? $_ : "${alias}.$_" } @{delete $attrs->{columns}} ] - ); - $attrs->{as} = - ($attrs->{as} - ? (ref $attrs->{as} eq 'ARRAY' - ? [ @{$attrs->{as}} ] - : [ $attrs->{as} ]) - : [ map { m/^\Q${alias}.\E(.+)$/ ? $1 : $_ } @{$attrs->{select}} ] + # add the additional columns on + foreach ( 'include_columns', '+columns' ) { + push @colbits, map { + ( ref($_) eq 'HASH' ) + ? $_ + : { ( split( /\./, $_ ) )[-1] => ( /\./ ? $_ : "${alias}.$_" ) } + } ( ref($attrs->{$_}) eq 'ARRAY' ) ? @{ delete $attrs->{$_} } : delete $attrs->{$_} if ( $attrs->{$_} ); + } + + # start with initial select items + if ( $attrs->{select} ) { + $attrs->{select} = + ( ref $attrs->{select} eq 'ARRAY' ) + ? [ @{ $attrs->{select} } ] + : [ $attrs->{select} ]; + $attrs->{as} = ( + $attrs->{as} + ? ( + ref $attrs->{as} eq 'ARRAY' + ? [ @{ $attrs->{as} } ] + : [ $attrs->{as} ] + ) + : [ map { m/^\Q${alias}.\E(.+)$/ ? $1 : $_ } @{ $attrs->{select} } ] ); - - my $adds; - if ($adds = delete $attrs->{include_columns}) { - $adds = [$adds] unless ref $adds eq 'ARRAY'; - push(@{$attrs->{select}}, @$adds); - push(@{$attrs->{as}}, map { m/([^.]+)$/; $1 } @$adds); } - if ($adds = delete $attrs->{'+select'}) { + else { + + # otherwise we intialise select & as to empty + $attrs->{select} = []; + $attrs->{as} = []; + } + + # now add colbits to select/as + push( @{ $attrs->{select} }, map { values( %{$_} ) } @colbits ); + push( @{ $attrs->{as} }, map { keys( %{$_} ) } @colbits ); + + my $adds; + if ( $adds = delete $attrs->{'+select'} ) { $adds = [$adds] unless ref $adds eq 'ARRAY'; - push(@{$attrs->{select}}, - map { /\./ || ref $_ ? $_ : "${alias}.$_" } @$adds); + push( + @{ $attrs->{select} }, + map { /\./ || ref $_ ? $_ : "${alias}.$_" } @$adds + ); } - if (my $adds = delete $attrs->{'+as'}) { + if ( $adds = delete $attrs->{'+as'} ) { $adds = [$adds] unless ref $adds eq 'ARRAY'; - push(@{$attrs->{as}}, @$adds); + push( @{ $attrs->{as} }, @$adds ); } - $attrs->{from} ||= [ { 'me' => $source->from } ]; + $attrs->{from} ||= [ { $self->{attrs}{alias} => $source->from } ]; - if (exists $attrs->{join} || exists $attrs->{prefetch}) { + if ( exists $attrs->{join} || exists $attrs->{prefetch} ) { my $join = delete $attrs->{join} || {}; - if (defined $attrs->{prefetch}) { - $join = $self->_merge_attr( - $join, $attrs->{prefetch} - ); - + if ( defined $attrs->{prefetch} ) { + $join = $self->_merge_attr( $join, $attrs->{prefetch} ); + } - $attrs->{from} = # have to copy here to avoid corrupting the original + $attrs->{from} = # have to copy here to avoid corrupting the original [ - @{$attrs->{from}}, - $source->resolve_join($join, $alias, { %{$attrs->{seen_join}||{}} }) + @{ $attrs->{from} }, + $source->resolve_join( + $join, $alias, { %{ $attrs->{seen_join} || {} } } + ) ]; } - $attrs->{group_by} ||= $attrs->{select} if delete $attrs->{distinct}; - if ($attrs->{order_by}) { - $attrs->{order_by} = (ref($attrs->{order_by}) eq 'ARRAY' - ? [ @{$attrs->{order_by}} ] - : [ $attrs->{order_by} ]); - } else { - $attrs->{order_by} = []; + $attrs->{group_by} ||= $attrs->{select} + if delete $attrs->{distinct}; + if ( $attrs->{order_by} ) { + $attrs->{order_by} = ( + ref( $attrs->{order_by} ) eq 'ARRAY' + ? [ @{ $attrs->{order_by} } ] + : [ $attrs->{order_by} ] + ); + } + else { + $attrs->{order_by} = []; } my $collapse = $attrs->{collapse} || {}; - if (my $prefetch = delete $attrs->{prefetch}) { - $prefetch = $self->_merge_attr({}, $prefetch); + if ( my $prefetch = delete $attrs->{prefetch} ) { + $prefetch = $self->_merge_attr( {}, $prefetch ); my @pre_order; - my $seen = $attrs->{seen_join} || {}; - foreach my $p (ref $prefetch eq 'ARRAY' ? @$prefetch : ($prefetch)) { + my $seen = { %{ $attrs->{seen_join} || {} } }; + foreach my $p ( ref $prefetch eq 'ARRAY' ? @$prefetch : ($prefetch) ) { + # bring joins back to level of current class - my @prefetch = $source->resolve_prefetch( - $p, $alias, $seen, \@pre_order, $collapse - ); - push(@{$attrs->{select}}, map { $_->[0] } @prefetch); - push(@{$attrs->{as}}, map { $_->[1] } @prefetch); + my @prefetch = + $source->resolve_prefetch( $p, $alias, $seen, \@pre_order, $collapse ); + push( @{ $attrs->{select} }, map { $_->[0] } @prefetch ); + push( @{ $attrs->{as} }, map { $_->[1] } @prefetch ); } - push(@{$attrs->{order_by}}, @pre_order); + push( @{ $attrs->{order_by} }, @pre_order ); } $attrs->{collapse} = $collapse; - if ($attrs->{page}) { + if ( $attrs->{page} ) { $attrs->{offset} ||= 0; - $attrs->{offset} += ($attrs->{rows} * ($attrs->{page} - 1)); + $attrs->{offset} += ( $attrs->{rows} * ( $attrs->{page} - 1 ) ); } return $self->{_attrs} = $attrs; @@ -2398,8 +2544,12 @@ sub throw_exception { =head1 ATTRIBUTES -The resultset takes various attributes that modify its behavior. Here's an -overview of them: +Attributes are used to refine a ResultSet in various ways when +searching for data. They can be passed to any method which takes an +C<\%attrs> argument. See L, L, L, +L. + +These are in no particular order: =head2 order_by @@ -2430,12 +2580,15 @@ recommended syntax. =back -Shortcut to request a particular set of columns to be retrieved. Adds -C onto the start of any column without a C<.> in it and sets C as normal. (You may also -use the C attribute, as in earlier versions of DBIC.) +Shortcut to request a particular set of columns to be retrieved. Each +column spec may be a string (a table column name), or a hash (in which +case the key is the C value, and the value is used as the C from that, then auto-populates C from +C but adds columns to the selection. =over 4 -Indicates additional column names for those added via L. +Indicates additional column names for those added via L. See L. =back