changed Foo/Bar in docs to more meaningful names
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / ResultSet.pm
index 3ce9489..b5bad6a 100644 (file)
@@ -8,6 +8,7 @@ use overload
         fallback => 1;
 use Data::Page;
 use Storable;
+use Scalar::Util qw/weaken/;
 
 use base qw/DBIx::Class/;
 __PACKAGE__->load_components(qw/AccessorGroup/);
@@ -20,7 +21,7 @@ DBIx::Class::ResultSet - Responsible for fetching and creating resultset.
 =head1 SYNOPSIS
 
   my $rs   = $schema->resultset('User')->search(registered => 1);
-  my @rows = $schema->resultset('Foo')->search(bar => 'baz');
+  my @rows = $schema->resultset('CD')->search(year => 2005);
 
 =head1 DESCRIPTION
 
@@ -71,8 +72,9 @@ sub new {
   return $class->new_result(@_) if ref $class;
   
   my ($source, $attrs) = @_;
-  #use Data::Dumper; warn Dumper($attrs);
+  weaken $source;
   $attrs = Storable::dclone($attrs || {}); # { %{ $attrs || {} } };
+  #use Data::Dumper; warn Dumper($attrs);
   my $alias = ($attrs->{alias} ||= 'me');
   
   $attrs->{columns} ||= delete $attrs->{cols} if $attrs->{cols};
@@ -149,14 +151,16 @@ sub new {
 
 =head2 search
 
-  my @obj    = $rs->search({ foo => 3 }); # "... WHERE foo = 3"
-  my $new_rs = $rs->search({ foo => 3 });
+  my @cds    = $rs->search({ year => 2001 }); # "... WHERE year = 2001"
+  my $new_rs = $rs->search({ year => 2005 });
 
 If you need to pass in additional attributes but no additional condition,
 call it as C<search(undef, \%attrs);>.
 
-  # "SELECT foo, bar FROM $class_table"
-  my @all = $class->search(undef, { columns => [qw/foo bar/] });
+  # "SELECT name, artistid FROM $artist_table"
+  my @all_artists = $schema->resultset('Artist')->search(undef, {
+    columns => [qw/name artistid/],
+  });
 
 =cut
 
@@ -250,7 +254,7 @@ sub find {
   my @cols = $self->result_source->primary_columns;
   if (exists $attrs->{key}) {
     my %uniq = $self->result_source->unique_constraints;
-    $self->throw_exception( "Unknown key $attrs->{key} on $self->name" )
+    $self->throw_exception( "Unknown key $attrs->{key} on '" . $self->result_source->name . "'" )
       unless exists $uniq{$attrs->{key}};
     @cols = @{ $uniq{$attrs->{key}} };
   }
@@ -503,43 +507,49 @@ clause.
 sub count {
   my $self = shift;
   return $self->search(@_)->count if @_ and defined $_[0];
-  unless (defined $self->{count}) {
-    return scalar @{ $self->get_cache } if @{ $self->get_cache };
-    my $select = { count => '*' };
-    my $attrs = { %{ $self->{attrs} } };
-    if (my $group_by = delete $attrs->{group_by}) {
-      delete $attrs->{having};
-      my @distinct = (ref $group_by ?  @$group_by : ($group_by));
-      # todo: try CONCAT for multi-column pk
-      my @pk = $self->result_source->primary_columns;
-      if (@pk == 1) {
-        foreach my $column (@distinct) {
-          if ($column =~ qr/^(?:\Q$attrs->{alias}.\E)?$pk[0]$/) {
-            @distinct = ($column);
-            last;
-          }
-        } 
-      }
+  return scalar @{ $self->get_cache } if @{ $self->get_cache };
 
-      $select = { count => { distinct => \@distinct } };
-      #use Data::Dumper; die Dumper $select;
-    }
+  my $count = $self->_count;
+  return 0 unless $count;
 
-    $attrs->{select} = $select;
-    $attrs->{as} = [qw/count/];
-    # offset, order by and page are not needed to count. record_filter is cdbi
-    delete $attrs->{$_} for qw/rows offset order_by page pager record_filter/;
-        
-    ($self->{count}) = (ref $self)->new($self->result_source, $attrs)->cursor->next;
-  }
-  return 0 unless $self->{count};
-  my $count = $self->{count};
   $count -= $self->{attrs}{offset} if $self->{attrs}{offset};
   $count = $self->{attrs}{rows} if
     $self->{attrs}{rows} and $self->{attrs}{rows} < $count;
   return $count;
 }
 
+sub _count { # Separated out so pager can get the full count
+  my $self = shift;
+  my $select = { count => '*' };
+  my $attrs = { %{ $self->{attrs} } };
+  if (my $group_by = delete $attrs->{group_by}) {
+    delete $attrs->{having};
+    my @distinct = (ref $group_by ?  @$group_by : ($group_by));
+    # todo: try CONCAT for multi-column pk
+    my @pk = $self->result_source->primary_columns;
+    if (@pk == 1) {
+      foreach my $column (@distinct) {
+        if ($column =~ qr/^(?:\Q$attrs->{alias}.\E)?$pk[0]$/) {
+          @distinct = ($column);
+          last;
+        }
+      } 
+    }
+
+    $select = { count => { distinct => \@distinct } };
+    #use Data::Dumper; die Dumper $select;
+  }
+
+  $attrs->{select} = $select;
+  $attrs->{as} = [qw/count/];
+
+  # offset, order by and page are not needed to count. record_filter is cdbi
+  delete $attrs->{$_} for qw/rows offset order_by page pager record_filter/;
+        
+  my ($count) = (ref $self)->new($self->result_source, $attrs)->cursor->next;
+  return $count;
+}
+
 =head2 count_literal
 
 Calls L</search_literal> with the passed arguments, then L</count>.
@@ -566,10 +576,13 @@ sub all {
       # If we're collapsing has_many prefetches it probably makes
       # very little difference, and this is cleaner than hacking
       # _construct_object to survive the approach
-    my @row;
     $self->cursor->reset;
-    while (@row = $self->cursor->next) {
+    my @row = $self->cursor->next;
+    while (@row) {
       push(@obj, $self->_construct_object(@row));
+      @row = (exists $self->{stashed_row}
+               ? @{delete $self->{stashed_row}}
+               : $self->cursor->next);
     }
   } else {
     @obj = map { $self->_construct_object(@$_) } $self->cursor->all;
@@ -644,26 +657,41 @@ Deletes the contents of the resultset from its result source.
 sub delete {
   my ($self) = @_;
   my $del = {};
-  $self->throw_exception("Can't delete on resultset with condition unless hash or array")
-    unless (ref($self->{cond}) eq 'HASH' || ref($self->{cond}) eq 'ARRAY');
-  if (ref $self->{cond} eq 'ARRAY') {
+
+  if (!ref($self->{cond})) {
+
+    # No-op. No condition, we're deleting everything
+
+  } elsif (ref $self->{cond} eq 'ARRAY') {
+
     $del = [ map { my %hash;
       foreach my $key (keys %{$_}) {
         $key =~ /([^.]+)$/;
         $hash{$1} = $_->{$key};
       }; \%hash; } @{$self->{cond}} ];
-  } elsif ((keys %{$self->{cond}})[0] eq '-and') {
-    $del->{-and} = [ map { my %hash;
-      foreach my $key (keys %{$_}) {
+
+  } elsif (ref $self->{cond} eq 'HASH') {
+
+    if ((keys %{$self->{cond}})[0] eq '-and') {
+
+      $del->{-and} = [ map { my %hash;
+        foreach my $key (keys %{$_}) {
+          $key =~ /([^.]+)$/;
+          $hash{$1} = $_->{$key};
+        }; \%hash; } @{$self->{cond}{-and}} ];
+
+    } else {
+
+      foreach my $key (keys %{$self->{cond}}) {
         $key =~ /([^.]+)$/;
-        $hash{$1} = $_->{$key};
-      }; \%hash; } @{$self->{cond}{-and}} ];
-  } else {
-    foreach my $key (keys %{$self->{cond}}) {
-      $key =~ /([^.]+)$/;
-      $del->{$1} = $self->{cond}{$key};
+        $del->{$1} = $self->{cond}{$key};
+      }
     }
+  } else {
+    $self->throw_exception(
+      "Can't delete on resultset with condition unless hash or array");
   }
+
   $self->result_source->storage->delete($self->result_source->from, $del);
   return 1;
 }
@@ -693,9 +721,8 @@ sub pager {
   my $attrs = $self->{attrs};
   $self->throw_exception("Can't create pager for non-paged rs") unless $self->{page};
   $attrs->{rows} ||= 10;
-  $self->count;
   return $self->{pager} ||= Data::Page->new(
-    $self->{count}, $attrs->{rows}, $self->{page});
+    $self->_count, $attrs->{rows}, $self->{page});
 }
 
 =head2 page
@@ -899,7 +926,7 @@ sub clear_cache {
 
 Returns a related resultset for the supplied relationship name.
 
-  $rs = $rs->related_resultset('foo');
+  $artist_rs = $schema->resultset('CD')->related_resultset('Artist');
 
 =cut
 
@@ -948,8 +975,9 @@ overview of them:
 
 =head2 order_by
 
-Which column(s) to order the results by. This is currently passed through
-directly to SQL, so you can give e.g. C<foo DESC> for a descending order.
+Which column(s) to order the results by. This is currently passed
+through directly to SQL, so you can give e.g. C<year DESC> for a
+descending order on the column `year'.
 
 =head2 columns
 
@@ -966,9 +994,13 @@ use the C<cols> attribute, as in earlier versions of DBIC.)
 
 Shortcut to include additional columns in the returned results - for example
 
-  { include_columns => ['foo.name'], join => ['foo'] }
+  $schema->resultset('CD')->search(undef, {
+    include_columns => ['artist.name'],
+    join => ['artist']
+  });
 
-would add a 'name' column to the information passed to object inflation
+would return all CDs and include a 'name' column to the information
+passed to object inflation
 
 =head2 select
 
@@ -978,20 +1010,17 @@ Indicates which columns should be selected from the storage. You can use
 column names, or in the case of RDBMS back ends, function or stored procedure
 names:
 
-  $rs = $schema->resultset('Foo')->search(
-    undef,
-    {
-      select => [
-        'column_name',
-        { count => 'column_to_count' },
-        { sum => 'column_to_sum' }
-      ]
-    }
-  );
+  $rs = $schema->resultset('Employee')->search(undef, {
+    select => [
+      'name',
+      { count => 'employeeid' },
+      { sum => 'salary' }
+    ]
+  });
 
 When you use function/stored procedure names and do not supply an C<as>
 attribute, the column names returned are storage-dependent. E.g. MySQL would
-return a column named C<count(column_to_count)> in the above example.
+return a column named C<count(employeeid)> in the above example.
 
 =head2 as
 
@@ -1001,29 +1030,26 @@ Indicates column names for object inflation. This is used in conjunction with
 C<select>, usually when C<select> contains one or more function or stored
 procedure names:
 
-  $rs = $schema->resultset('Foo')->search(
-    undef,
-    {
-      select => [
-        'column1',
-        { count => 'column2' }
-      ],
-      as => [qw/ column1 column2_count /]
-    }
-  );
+  $rs = $schema->resultset('Employee')->search(undef, {
+    select => [
+      'name',
+      { count => 'employeeid' }
+    ],
+    as => ['Employee Name', 'employee_count'],
+  });
 
-  my $foo = $rs->first(); # get the first Foo
+  my $employee = $rs->first(); # get the first Employee
 
 If the object against which the search is performed already has an accessor
 matching a column name specified in C<as>, the value can be retrieved using
 the accessor as normal:
 
-  my $column1 = $foo->column1();
+  my $name = $employee->name();
 
 If on the other hand an accessor does not exist in the object, you need to
 use C<get_column> instead:
 
-  my $column2_count = $foo->get_column('column2_count');
+  my $employee_count = $employee->get_column('employee_count');
 
 You can create your own accessors if required - see
 L<DBIx::Class::Manual::Cookbook> for details.
@@ -1062,13 +1088,15 @@ For example:
 If the same join is supplied twice, it will be aliased to <rel>_2 (and
 similarly for a third time). For e.g.
 
-  my $rs = $schema->resultset('Artist')->search(
-    { 'cds.title'   => 'Foo',
-      'cds_2.title' => 'Bar' },
-    { join => [ qw/cds cds/ ] });
+  my $rs = $schema->resultset('Artist')->search({
+    'cds.title'   => 'Down to Earth',
+    'cds_2.title' => 'Popular',
+  }, {
+    join => [ qw/cds cds/ ],
+  });
 
-will return a set of all artists that have both a cd with title Foo and a cd
-with title Bar.
+will return a set of all artists that have both a cd with title 'Down
+to Earth' and a cd with title 'Popular'.
 
 If you want to fetch related objects from other tables as well, see C<prefetch>
 below.