More documentation improvements in the spirit of fb13a49f
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / ResultSet.pm
index 2c0207e..ed5a7ae 100644 (file)
@@ -34,12 +34,12 @@ DBIx::Class::ResultSet - Represents a query used for fetching a set of results.
 
 =head1 SYNOPSIS
 
-  my $users_rs   = $schema->resultset('User');
+  my $users_rs = $schema->resultset('User');
   while( $user = $users_rs->next) {
     print $user->username;
   }
 
-  my $registered_users_rs   = $schema->resultset('User')->search({ registered => 1 });
+  my $registered_users_rs = $schema->resultset('User')->search({ registered => 1 });
   my @cds_in_2005 = $schema->resultset('CD')->search({ year => 2005 })->all();
 
 =head1 DESCRIPTION
@@ -191,9 +191,9 @@ See: L</search>, L</count>, L</get_column>, L</all>, L</create>.
 
 =over 4
 
-=item Arguments: $source, \%$attrs
+=item Arguments: L<$source|DBIx::Class::ResultSource>, L<\%attrs?|/ATTRIBUTES>
 
-=item Return Value: $rs
+=item Return Value: L<$resultset|/search>
 
 =back
 
@@ -202,16 +202,31 @@ L<DBIx::Class::ResultSourceProxy::Table>) and an attribute hash (see
 L</ATTRIBUTES> below).  Does not perform any queries -- these are
 executed as needed by the other methods.
 
-Generally you won't need to construct a resultset manually.  You'll
-automatically get one from e.g. a L</search> called in scalar context:
+Generally you never construct a resultset manually. Instead you get one
+from e.g. a
+C<< $schema->L<resultset|DBIx::Class::Schema/resultset>('$source_name') >>
+or C<< $another_resultset->L<search|/search>(...) >> (the later called in
+scalar context):
 
   my $rs = $schema->resultset('CD')->search({ title => '100th Window' });
 
-IMPORTANT: If called on an object, proxies to new_result instead so
+=over
+
+=item WARNING
+
+If called on an object, proxies to L</new_result> instead, so
 
   my $cd = $schema->resultset('CD')->new({ title => 'Spoon' });
 
-will return a CD object, not a ResultSet.
+will return a CD object, not a ResultSet, and is equivalent to:
+
+  my $cd = $schema->resultset('CD')->new_result({ title => 'Spoon' });
+
+Please also keep in mind that many internals call L</new_result> directly,
+so overloading this method with the idea of intercepting new result object
+creation B<will not work>. See also warning pertaining to L</create>.
+
+=back
 
 =cut
 
@@ -254,9 +269,9 @@ sub new {
 
 =over 4
 
-=item Arguments: $cond, \%attrs?
+=item Arguments: L<$cond|DBIx::Class::SQLMaker> | undef, L<\%attrs?|/ATTRIBUTES>
 
-=item Return Value: $resultset (scalar context) ||  @row_objs (list context)
+=item Return Value: $resultset (scalar context) | L<@result_objs|DBIx::Class::Manual::ResultClass> (list context)
 
 =back
 
@@ -267,7 +282,8 @@ sub new {
                  # year = 2005 OR year = 2004
 
 In list context, C<< ->all() >> is called implicitly on the resultset, thus
-returning a list of row objects instead. To avoid that, use L</search_rs>.
+returning a list of L<result|DBIx::Class::Manual::ResultClass> objects instead.
+To avoid that, use L</search_rs>.
 
 If you need to pass in additional attributes but no additional condition,
 call it as C<search(undef, \%attrs)>.
@@ -289,7 +305,7 @@ For more help on using joins with search, see L<DBIx::Class::Manual::Joining>.
 
 Note that L</search> does not process/deflate any of the values passed in the
 L<SQL::Abstract>-compatible search condition structure. This is unlike other
-condition-bound methods L</new>, L</create> and L</find>. The user must ensure
+condition-bound methods L</new_result>, L</create> and L</find>. The user must ensure
 manually that any value passed to this method will stringify to something the
 RDBMS knows how to deal with. A notable example is the handling of L<DateTime>
 objects, for more info see:
@@ -324,9 +340,9 @@ sub search {
 
 =over 4
 
-=item Arguments: $cond, \%attrs?
+=item Arguments: L<$cond|DBIx::Class::SQLMaker>, L<\%attrs?|/ATTRIBUTES>
 
-=item Return Value: $resultset
+=item Return Value: L<$resultset|/search>
 
 =back
 
@@ -617,11 +633,20 @@ sub _stack_cond {
 
 =head2 search_literal
 
+B<CAVEAT>: C<search_literal> is provided for Class::DBI compatibility and
+should only be used in that context. C<search_literal> is a convenience
+method. It is equivalent to calling C<< $schema->search(\[]) >>, but if you
+want to ensure columns are bound correctly, use L</search>.
+
+See L<DBIx::Class::Manual::Cookbook/Searching> and
+L<DBIx::Class::Manual::FAQ/Searching> for searching techniques that do not
+require C<search_literal>.
+
 =over 4
 
-=item Arguments: $sql_fragment, @bind_values
+=item Arguments: $sql_fragment, @standalone_bind_values
 
-=item Return Value: $resultset (scalar context) || @row_objs (list context)
+=item Return Value: L<$resultset|/search> (scalar context) | L<@result_objs|DBIx::Class::Manual::ResultClass> (list context)
 
 =back
 
@@ -631,21 +656,11 @@ sub _stack_cond {
 Pass a literal chunk of SQL to be added to the conditional part of the
 resultset query.
 
-CAVEAT: C<search_literal> is provided for Class::DBI compatibility and should
-only be used in that context. C<search_literal> is a convenience method.
-It is equivalent to calling $schema->search(\[]), but if you want to ensure
-columns are bound correctly, use C<search>.
-
 Example of how to use C<search> instead of C<search_literal>
 
   my @cds = $cd_rs->search_literal('cdid = ? AND (artist = ? OR artist = ?)', (2, 1, 2));
   my @cds = $cd_rs->search(\[ 'cdid = ? AND (artist = ? OR artist = ?)', [ 'cdid', 2 ], [ 'artist', 1 ], [ 'artist', 2 ] ]);
 
-
-See L<DBIx::Class::Manual::Cookbook/Searching> and
-L<DBIx::Class::Manual::FAQ/Searching> for searching techniques that do not
-require C<search_literal>.
-
 =cut
 
 sub search_literal {
@@ -654,16 +669,16 @@ sub search_literal {
   if ( @bind && ref($bind[-1]) eq 'HASH' ) {
     $attr = pop @bind;
   }
-  return $self->search(\[ $sql, map [ __DUMMY__ => $_ ], @bind ], ($attr || () ));
+  return $self->search(\[ $sql, map [ {} => $_ ], @bind ], ($attr || () ));
 }
 
 =head2 find
 
 =over 4
 
-=item Arguments: \%columns_values | @pk_values, \%attrs?
+=item Arguments: \%columns_values | @pk_values, { key => $unique_constraint, L<%attrs|/ATTRIBUTES> }?
 
-=item Return Value: $row_object | undef
+=item Return Value: L<$result|DBIx::Class::Manual::ResultClass> | undef
 
 =back
 
@@ -695,7 +710,7 @@ Note that this fallback behavior may be deprecated in further versions. If
 you need to search with arbitrary conditions - use L</search>. If the query
 resulting from this fallback produces more than one row, a warning to the
 effect is issued, though only the first row is constructed and returned as
-C<$row_object>.
+C<$result_object>.
 
 In addition to C<key>, L</find> recognizes and applies standard
 L<resultset attributes|/ATTRIBUTES> in the same way as L</search> does.
@@ -829,7 +844,7 @@ sub find {
 
   # Run the query, passing the result_class since it should propagate for find
   my $rs = $self->search ($final_cond, {result_class => $self->result_class, %$attrs});
-  if ($rs->_resolved_attrs->{collapse}) {
+  if (keys %{$rs->_resolved_attrs->{collapse}}) {
     my $row = $rs->next;
     carp "Query returned more than one row" if $rs->next;
     return $row;
@@ -909,7 +924,7 @@ sub _build_unique_cond {
       and
     !$ENV{DBIC_NULLABLE_KEY_NOWARN}
       and
-    my @undefs = grep { ! defined $final_cond->{$_} } (keys %$final_cond)
+    my @undefs = sort grep { ! defined $final_cond->{$_} } (keys %$final_cond)
   ) {
     carp_unique ( sprintf (
       "NULL/undef values supplied for requested unique constraint '%s' (NULL "
@@ -927,9 +942,9 @@ sub _build_unique_cond {
 
 =over 4
 
-=item Arguments: $rel, $cond?, \%attrs?
+=item Arguments: $rel_name, $cond?, L<\%attrs?|/ATTRIBUTES>
 
-=item Return Value: $new_resultset (scalar context) || @row_objs (list context)
+=item Return Value: L<$resultset|/search> (scalar context) | L<@result_objs|DBIx::Class::Manual::ResultClass> (list context)
 
 =back
 
@@ -941,7 +956,7 @@ Searches the specified relationship, optionally specifying a condition and
 attributes for matching records. See L</ATTRIBUTES> for more information.
 
 In list context, C<< ->all() >> is called implicitly on the resultset, thus
-returning a list of row objects instead. To avoid that, use L</search_related_rs>.
+returning a list of result objects instead. To avoid that, use L</search_related_rs>.
 
 See also L</search_related_rs>.
 
@@ -968,7 +983,7 @@ sub search_related_rs {
 
 =item Arguments: none
 
-=item Return Value: $cursor
+=item Return Value: L<$cursor|DBIx::Class::Cursor>
 
 =back
 
@@ -991,9 +1006,9 @@ sub cursor {
 
 =over 4
 
-=item Arguments: $cond?
+=item Arguments: L<$cond?|DBIx::Class::SQLMaker>
 
-=item Return Value: $row_object | undef
+=item Return Value: L<$result|DBIx::Class::Manual::ResultClass> | undef
 
 =back
 
@@ -1038,9 +1053,11 @@ sub single {
 
   my $attrs = $self->_resolved_attrs_copy;
 
-  $self->throw_exception(
-    'single() can not be used on resultsets prefetching has_many. Use find( \%cond ) or next() instead'
-  ) if $attrs->{collapse};
+  if (keys %{$attrs->{collapse}}) {
+    $self->throw_exception(
+      'single() can not be used on resultsets prefetching has_many. Use find( \%cond ) or next() instead'
+    );
+  }
 
   if ($where) {
     if (defined $attrs->{where}) {
@@ -1054,13 +1071,12 @@ sub single {
     }
   }
 
-  my $data = [ $self->result_source->storage->select_single(
+  my @data = $self->result_source->storage->select_single(
     $attrs->{from}, $attrs->{select},
     $attrs->{where}, $attrs
-  )];
-  return undef unless @$data;
-  $self->{stashed_rows} = [ $data ];
-  $self->_construct_objects->[0];
+  );
+
+  return (@data ? ($self->_construct_object(@data))[0] : undef);
 }
 
 
@@ -1100,9 +1116,9 @@ sub _collapse_query {
 
 =over 4
 
-=item Arguments: $cond?
+=item Arguments: L<$cond?|DBIx::Class::SQLMaker>
 
-=item Return Value: $resultsetcolumn
+=item Return Value: L<$resultsetcolumn|DBIx::Class::ResultSetColumn>
 
 =back
 
@@ -1122,9 +1138,9 @@ sub get_column {
 
 =over 4
 
-=item Arguments: $cond, \%attrs?
+=item Arguments: L<$cond|DBIx::Class::SQLMaker>, L<\%attrs?|/ATTRIBUTES>
 
-=item Return Value: $resultset (scalar context) || @row_objs (list context)
+=item Return Value: L<$resultset|/search> (scalar context) | L<@result_objs|DBIx::Class::Manual::ResultClass> (list context)
 
 =back
 
@@ -1167,7 +1183,7 @@ sub search_like {
 
 =item Arguments: $first, $last
 
-=item Return Value: $resultset (scalar context) || @row_objs (list context)
+=item Return Value: L<$resultset|/search> (scalar context) | L<@result_objs|DBIx::Class::Manual::ResultClass> (list context)
 
 =back
 
@@ -1196,7 +1212,7 @@ sub slice {
 
 =item Arguments: none
 
-=item Return Value: $result | undef
+=item Return Value: L<$result|DBIx::Class::Manual::ResultClass> | undef
 
 =back
 
@@ -1217,154 +1233,170 @@ first record from the resultset.
 
 sub next {
   my ($self) = @_;
-
   if (my $cache = $self->get_cache) {
     $self->{all_cache_position} ||= 0;
     return $cache->[$self->{all_cache_position}++];
   }
-
   if ($self->{attrs}{cache}) {
     delete $self->{pager};
     $self->{all_cache_position} = 1;
     return ($self->all)[0];
   }
+  if ($self->{stashed_objects}) {
+    my $obj = shift(@{$self->{stashed_objects}});
+    delete $self->{stashed_objects} unless @{$self->{stashed_objects}};
+    return $obj;
+  }
+  my @row = (
+    exists $self->{stashed_row}
+      ? @{delete $self->{stashed_row}}
+      : $self->cursor->next
+  );
+  return undef unless (@row);
+  my ($row, @more) = $self->_construct_object(@row);
+  $self->{stashed_objects} = \@more if @more;
+  return $row;
+}
 
-  return shift(@{$self->{stashed_objects}}) if @{ $self->{stashed_objects}||[] };
-
-  $self->{stashed_objects} = $self->_construct_objects
-    or return undef;
+sub _construct_object {
+  my ($self, @row) = @_;
 
-  return shift @{$self->{stashed_objects}};
+  my $info = $self->_collapse_result($self->{_attrs}{as}, \@row)
+    or return ();
+  my @new = $self->result_class->inflate_result($self->result_source, @$info);
+  @new = $self->{_attrs}{record_filter}->(@new)
+    if exists $self->{_attrs}{record_filter};
+  return @new;
 }
 
-# takes a single DBI-row of data and coinstructs as many objects
-# as the resultset attributes call for.
-# This can be a bit of an action at a distance - it takes as an argument
-# the *current* cursor-row (already taken off the $sth), but if
-# collapsing is requested it will keep advancing the cursor either
-# until the current row-object is assembled (the collapser was able to
-# order the result sensibly) OR until the cursor is exhausted (an
-# unordered collapsing resultset effectively triggers ->all)
-sub _construct_objects {
-  my ($self, $fetch_all) = @_;
+sub _collapse_result {
+  my ($self, $as_proto, $row) = @_;
 
-  my $rsrc = $self->result_source;
-  my $attrs = $self->_resolved_attrs;
-  my $cursor = $self->cursor;
-
-  # this will be used as both initial raw-row collector AND as a RV of
-  # _construct_objects. Not regrowing the array twice matters a lot...
-  # a suprising amount actually
-  my $rows = (delete $self->{stashed_rows}) || [];
-  if ($fetch_all) {
-    # FIXME - we can do better, cursor->next/all (well diff. methods) should return a ref
-    $rows = [ @$rows, $cursor->all ];
-  }
-  elsif (!$attrs->{collapse}) {
-    push @$rows, do { my @r = $cursor->next; @r ? \@r : () }
-      unless @$rows;
-  }
-  else {
-    $attrs->{_ordered_for_collapse} ||= (!$attrs->{order_by}) ? undef : do {
-      my $st = $rsrc->schema->storage;
-      my @ord_cols = map
-        { $_->[0] }
-        ( $st->_extract_order_criteria($attrs->{order_by}) )
-      ;
+  my @copy = @$row;
 
-      my $colinfos = $st->_resolve_column_info($attrs->{from}, \@ord_cols);
+  # 'foo'         => [ undef, 'foo' ]
+  # 'foo.bar'     => [ 'foo', 'bar' ]
+  # 'foo.bar.baz' => [ 'foo.bar', 'baz' ]
 
-      for (0 .. $#ord_cols) {
-        if (
-          ! $colinfos->{$ord_cols[$_]}
-            or
-          $colinfos->{$ord_cols[$_]}{-result_source} != $rsrc
-        ) {
-          splice @ord_cols, $_;
-          last;
-        }
-      }
+  my @construct_as = map { [ (/^(?:(.*)\.)?([^.]+)$/) ] } @$as_proto;
 
-      # since all we check here are the start of the order_by belonging to the
-      # top level $rsrc, the order stability check will fail unless the whole
-      # thing is ordered as we need it
-      (@ord_cols and $rsrc->_identifying_column_set({ map
-        { $colinfos->{$_}{-colname} => $colinfos->{$_} }
-        @ord_cols
-      })) ? 1 : 0;
-    };
+  my %collapse = %{$self->{_attrs}{collapse}||{}};
 
-    if ($attrs->{_ordered_for_collapse}) {
-      push @$rows, do { my @r = $cursor->next; @r ? \@r : () };
-    }
-    # instead of looping over ->next, use ->all in stealth mode
-    elsif (! $cursor->{done}) {
-      push @$rows, $cursor->all;
-      $cursor->{done} = 1;
-      $fetch_all = 1;
+  my @pri_index;
+
+  # if we're doing collapsing (has_many prefetch) we need to grab records
+  # until the PK changes, so fill @pri_index. if not, we leave it empty so
+  # we know we don't have to bother.
+
+  # the reason for not using the collapse stuff directly is because if you
+  # had for e.g. two artists in a row with no cds, the collapse info for
+  # both would be NULL (undef) so you'd lose the second artist
+
+  # store just the index so we can check the array positions from the row
+  # without having to contruct the full hash
+
+  if (keys %collapse) {
+    my %pri = map { ($_ => 1) } $self->result_source->_pri_cols;
+    foreach my $i (0 .. $#construct_as) {
+      next if defined($construct_as[$i][0]); # only self table
+      if (delete $pri{$construct_as[$i][1]}) {
+        push(@pri_index, $i);
+      }
+      last unless keys %pri; # short circuit (Johnny Five Is Alive!)
     }
   }
 
-  return undef unless @$rows;
+  # no need to do an if, it'll be empty if @pri_index is empty anyway
 
-  my $res_class = $self->result_class;
-  my $inflator = $res_class->can ('inflate_result')
-    or $self->throw_exception("Inflator $res_class does not provide an inflate_result() method");
+  my %pri_vals = map { ($_ => $copy[$_]) } @pri_index;
 
-  my $infmap = $attrs->{as};
+  my @const_rows;
 
-  if (!$attrs->{collapse} and $attrs->{_single_object_inflation}) {
-    # construct a much simpler array->hash folder for the one-table cases right here
+  do { # no need to check anything at the front, we always want the first row
 
-    # FIXME SUBOPTIMAL this is a very very very hot spot
-    # while rather optimal we can *still* do much better, by
-    # building a smarter [Row|HRI]::inflate_result(), and
-    # switch to feeding it data via a much leaner interface
-    #
-    # crude unscientific benchmarking indicated the shortcut eval is not worth it for
-    # this particular resultset size
-    if (@$rows < 60) {
-      my @as_idx = 0..$#$infmap;
-      for my $r (@$rows) {
-        $r = $inflator->($res_class, $rsrc, { map { $infmap->[$_] => $r->[$_] } @as_idx } );
-      }
-    }
-    else {
-      eval sprintf (
-        '$_ = $inflator->($res_class, $rsrc, { %s }) for @$rows',
-        join (', ', map { "\$infmap->[$_] => \$_->[$_]" } 0..$#$infmap )
-      );
+    my %const;
+
+    foreach my $this_as (@construct_as) {
+      $const{$this_as->[0]||''}{$this_as->[1]} = shift(@copy);
     }
-  }
-  else {
-    ($self->{_row_parser} ||= eval sprintf 'sub { %s }', $rsrc->_mk_row_parser({
-      inflate_map => $infmap,
-      selection => $attrs->{select},
-      collapse => $attrs->{collapse},
-    }) or die $@)->($rows, $fetch_all ? () : (
-      sub { my @r = $cursor->next or return; \@r },
-      ($self->{stashed_rows} = []),
-    ));  # modify $rows in-place, shrinking/extending as necessary
 
-    $_ = $inflator->($res_class, $rsrc, @$_) for @$rows;
+    push(@const_rows, \%const);
 
-  }
+  } until ( # no pri_index => no collapse => drop straight out
+      !@pri_index
+    or
+      do { # get another row, stash it, drop out if different PK
+
+        @copy = $self->cursor->next;
+        $self->{stashed_row} = \@copy;
+
+        # last thing in do block, counts as true if anything doesn't match
+
+        # check xor defined first for NULL vs. NOT NULL then if one is
+        # defined the other must be so check string equality
+
+        grep {
+          (defined $pri_vals{$_} ^ defined $copy[$_])
+          || (defined $pri_vals{$_} && ($pri_vals{$_} ne $copy[$_]))
+        } @pri_index;
+      }
+  );
+
+  my $alias = $self->{attrs}{alias};
+  my $info = [];
+
+  my %collapse_pos;
+
+  my @const_keys;
 
-  # CDBI compat stuff
-  if ($attrs->{record_filter}) {
-    $_ = $attrs->{record_filter}->($_) for @$rows;
+  foreach my $const (@const_rows) {
+    scalar @const_keys or do {
+      @const_keys = sort { length($a) <=> length($b) } keys %$const;
+    };
+    foreach my $key (@const_keys) {
+      if (length $key) {
+        my $target = $info;
+        my @parts = split(/\./, $key);
+        my $cur = '';
+        my $data = $const->{$key};
+        foreach my $p (@parts) {
+          $target = $target->[1]->{$p} ||= [];
+          $cur .= ".${p}";
+          if ($cur eq ".${key}" && (my @ckey = @{$collapse{$cur}||[]})) {
+            # collapsing at this point and on final part
+            my $pos = $collapse_pos{$cur};
+            CK: foreach my $ck (@ckey) {
+              if (!defined $pos->{$ck} || $pos->{$ck} ne $data->{$ck}) {
+                $collapse_pos{$cur} = $data;
+                delete @collapse_pos{ # clear all positioning for sub-entries
+                  grep { m/^\Q${cur}.\E/ } keys %collapse_pos
+                };
+                push(@$target, []);
+                last CK;
+              }
+            }
+          }
+          if (exists $collapse{$cur}) {
+            $target = $target->[-1];
+          }
+        }
+        $target->[0] = $data;
+      } else {
+        $info->[0] = $const->{$key};
+      }
+    }
   }
 
-  return $rows;
+  return $info;
 }
 
 =head2 result_source
 
 =over 4
 
-=item Arguments: $result_source?
+=item Arguments: L<$result_source?|DBIx::Class::ResultSource>
 
-=item Return Value: $result_source
+=item Return Value: L<$result_source|DBIx::Class::ResultSource>
 
 =back
 
@@ -1381,7 +1413,7 @@ is derived.
 
 =back
 
-An accessor for the class to use when creating row objects. Defaults to
+An accessor for the class to use when creating result objects. Defaults to
 C<< result_source->result_class >> - which in most cases is the name of the
 L<"table"|DBIx::Class::Manual::Glossary/"ResultSource"> class.
 
@@ -1411,7 +1443,7 @@ sub result_class {
 
 =over 4
 
-=item Arguments: $cond, \%attrs??
+=item Arguments: L<$cond|DBIx::Class::SQLMaker>, L<\%attrs?|/ATTRIBUTES>
 
 =item Return Value: $count
 
@@ -1455,9 +1487,9 @@ sub count {
 
 =over 4
 
-=item Arguments: $cond, \%attrs??
+=item Arguments: L<$cond|DBIx::Class::SQLMaker>, L<\%attrs?|/ATTRIBUTES>
 
-=item Return Value: $count_rs
+=item Return Value: L<$count_rs|DBIx::Class::ResultSetColumn>
 
 =back
 
@@ -1526,7 +1558,7 @@ sub _count_subq_rs {
 
   # if we multi-prefetch we group_by something unique, as this is what we would
   # get out of the rs via ->next/->all. We *DO WANT* to clobber old group_by regardless
-  if ( $attrs->{collapse}  ) {
+  if ( keys %{$attrs->{collapse}}  ) {
     $sub_attrs->{group_by} = [ map { "$attrs->{alias}.$_" } @{
       $rsrc->_identifying_column_set || $self->throw_exception(
         'Unable to construct a unique group_by criteria properly collapsing the '
@@ -1611,9 +1643,12 @@ sub _bool {
 
 =head2 count_literal
 
+B<CAVEAT>: C<count_literal> is provided for Class::DBI compatibility and
+should only be used in that context. See L</search_literal> for further info.
+
 =over 4
 
-=item Arguments: $sql_fragment, @bind_values
+=item Arguments: $sql_fragment, @standalone_bind_values
 
 =item Return Value: $count
 
@@ -1632,7 +1667,7 @@ sub count_literal { shift->search_literal(@_)->count; }
 
 =item Arguments: none
 
-=item Return Value: @objects
+=item Return Value: L<@result_objs|DBIx::Class::Manual::ResultClass>
 
 =back
 
@@ -1643,23 +1678,33 @@ Returns all elements in the resultset.
 sub all {
   my $self = shift;
   if(@_) {
-    $self->throw_exception("all() doesn't take any arguments, you probably wanted ->search(...)->all()");
+      $self->throw_exception("all() doesn't take any arguments, you probably wanted ->search(...)->all()");
   }
 
-  delete $self->{stashed_rows};
-  delete $self->{stashed_objects};
-
-  if (my $c = $self->get_cache) {
-    return @$c;
+  return @{ $self->get_cache } if $self->get_cache;
+
+  my @obj;
+
+  if (keys %{$self->_resolved_attrs->{collapse}}) {
+    # Using $self->cursor->all is really just an optimisation.
+    # 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
+    $self->cursor->reset;
+    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;
   }
 
-  $self->cursor->reset;
-
-  my $objs = $self->_construct_objects('fetch_all') || [];
+  $self->set_cache(\@obj) if $self->{attrs}{cache};
 
-  $self->set_cache($objs) if $self->{attrs}{cache};
-
-  return @$objs;
+  return @obj;
 }
 
 =head2 reset
@@ -1680,10 +1725,7 @@ another query.
 
 sub reset {
   my ($self) = @_;
-  delete $self->{_attrs};
-  delete $self->{stashed_rows};
-  delete $self->{stashed_objects};
-
+  delete $self->{_attrs} if exists $self->{_attrs};
   $self->{all_cache_position} = 0;
   $self->cursor->reset;
   return $self;
@@ -1695,12 +1737,12 @@ sub reset {
 
 =item Arguments: none
 
-=item Return Value: $object | undef
+=item Return Value: L<$result|DBIx::Class::Manual::ResultClass> | undef
 
 =back
 
-Resets the resultset and returns an object for the first result (or C<undef>
-if the resultset is empty).
+L<Resets|/reset> the resultset (causing a fresh query to storage) and returns
+an object for the first result (or C<undef> if the resultset is empty).
 
 =cut
 
@@ -1724,54 +1766,49 @@ sub _rs_update_delete {
 
   my $attrs = { %{$self->_resolved_attrs} };
 
-  # "needs" is a strong word here - if the subquery is part of an IN clause - no point of
-  # even adding the group_by. It will really be used only when composing a poor-man's
-  # multicolumn-IN equivalent OR set
-  my $needs_group_by_subq = defined $attrs->{group_by};
+  my $existing_group_by = delete $attrs->{group_by};
+  my $needs_subq = defined $existing_group_by;
+
+  # simplify the joinmap and maybe decide if a subquery is necessary
+  my $relation_classifications = {};
 
-  # simplify the joinmap and maybe decide if a grouping (and thus subquery) is necessary
-  my $relation_classifications;
   if (ref($attrs->{from}) eq 'ARRAY') {
-    $attrs->{from} = $storage->_prune_unused_joins ($attrs->{from}, $attrs->{select}, $cond, $attrs);
+    # if we already know we need a subq, no point of classifying relations
+    if (!$needs_subq and @{$attrs->{from}} > 1) {
+      $attrs->{from} = $storage->_prune_unused_joins ($attrs->{from}, $attrs->{select}, $cond, $attrs);
 
-    $relation_classifications = $storage->_resolve_aliastypes_from_select_args (
-      [ @{$attrs->{from}}[1 .. $#{$attrs->{from}}] ],
-      $attrs->{select},
-      $cond,
-      $attrs
-    ) unless $needs_group_by_subq;  # we already know we need a group, no point of resolving them
+      $relation_classifications = $storage->_resolve_aliastypes_from_select_args (
+        [ @{$attrs->{from}}[1 .. $#{$attrs->{from}}] ],
+        $attrs->{select},
+        $cond,
+        $attrs
+      );
+    }
   }
   else {
-    $needs_group_by_subq ||= 1; # if {from} is unparseable assume the worst
+    $needs_subq ||= 1; # if {from} is unparseable assume the worst
   }
 
-  $needs_group_by_subq ||= exists $relation_classifications->{multiplying};
-
-  # if no subquery - life is easy-ish
-  unless (
-    $needs_group_by_subq
-      or
-    keys %$relation_classifications # if any joins at all - need to wrap a subq
-      or
-    $self->_has_resolved_attr(qw/rows offset/) # limits call for a subq
+  # do we need anything like a subquery?
+  if (
+    ! $needs_subq
+      and
+    ! keys %{ $relation_classifications->{restricting} || {} }
+      and
+    ! $self->_has_resolved_attr(qw/rows offset/) # limits call for a subq
   ) {
     # Most databases do not allow aliasing of tables in UPDATE/DELETE. Thus
     # a condition containing 'me' or other table prefixes will not work
-    # at all. What this code tries to do (badly) is to generate a condition
-    # with the qualifiers removed, by exploiting the quote mechanism of sqla
-    #
-    # this is atrocious and should be replaced by normal sqla introspection
-    # one sunny day
-    my ($sql, @bind) = do {
+    # at all. Tell SQLMaker to dequalify idents via a gross hack.
+    my $cond = do {
       my $sqla = $rsrc->storage->sql_maker;
       local $sqla->{_dequalify_idents} = 1;
-      $sqla->_recurse_where($self->{cond});
-    } if $self->{cond};
-
+      \[ $sqla->_recurse_where($self->{cond}) ];
+    };
     return $rsrc->storage->$op(
       $rsrc,
       $op eq 'update' ? $values : (),
-      $self->{cond} ? \[$sql, @bind] : (),
+      $cond,
     );
   }
 
@@ -1783,10 +1820,9 @@ sub _rs_update_delete {
       $rsrc->source_name,
     )
   );
-  my $existing_group_by = delete $attrs->{group_by};
 
   # make a new $rs selecting only the PKs (that's all we really need for the subq)
-  delete $attrs->{$_} for qw/collapse select _prefetch_selector_range as/;
+  delete $attrs->{$_} for qw/collapse _collapse_order_by select _prefetch_selector_range as/;
   $attrs->{columns} = [ map { "$attrs->{alias}.$_" } @$idcols ];
   $attrs->{group_by} = \ '';  # FIXME - this is an evil hack, it causes the optimiser to kick in and throw away the LEFT joins
   my $subrs = (ref $self)->new($rsrc, $attrs);
@@ -1814,13 +1850,15 @@ sub _rs_update_delete {
     );
   }
   else {
+
     # if all else fails - get all primary keys and operate over a ORed set
     # wrap in a transaction for consistency
     # this is where the group_by starts to matter
-    my $subq_group_by;
-    if ($needs_group_by_subq) {
-      $subq_group_by = $attrs->{columns};
-
+    if (
+      $existing_group_by
+        or
+      keys %{ $relation_classifications->{multiplying} || {} }
+    ) {
       # make sure if there is a supplied group_by it matches the columns compiled above
       # perfectly. Anything else can not be sanely executed on most databases so croak
       # right then and there
@@ -1833,7 +1871,7 @@ sub _rs_update_delete {
         if (
           join ("\x00", sort @current_group_by)
             ne
-          join ("\x00", sort @$subq_group_by )
+          join ("\x00", sort @{$attrs->{columns}} )
         ) {
           $self->throw_exception (
             "You have just attempted a $op operation on a resultset which does group_by"
@@ -1844,12 +1882,14 @@ sub _rs_update_delete {
           );
         }
       }
+
+      $subrs = $subrs->search({}, { group_by => $attrs->{columns} });
     }
 
     my $guard = $storage->txn_scope_guard;
 
     my @op_condition;
-    for my $row ($subrs->search({}, { group_by => $subq_group_by })->cursor->all) {
+    for my $row ($subrs->cursor->all) {
       push @op_condition, { map
         { $idcols->[$_] => $row->[$_] }
         (0 .. $#$idcols)
@@ -1874,13 +1914,13 @@ sub _rs_update_delete {
 
 =item Arguments: \%values
 
-=item Return Value: $storage_rv
+=item Return Value: $underlying_storage_rv
 
 =back
 
 Sets the specified columns in the resultset to the supplied values in a
 single query. Note that this will not run any accessor/set_column/update
-triggers, nor will it update any row object instances derived from this
+triggers, nor will it update any result object instances derived from this
 resultset (this includes the contents of the L<resultset cache|/set_cache>
 if any). See L</update_all> if you need to execute any on-update
 triggers or cascades defined either by you or a
@@ -1942,13 +1982,13 @@ sub update_all {
 
 =item Arguments: none
 
-=item Return Value: $storage_rv
+=item Return Value: $underlying_storage_rv
 
 =back
 
 Deletes the rows matching this resultset in a single query. Note that this
 will not run any delete triggers, nor will it alter the
-L<in_storage|DBIx::Class::Row/in_storage> status of any row object instances
+L<in_storage|DBIx::Class::Row/in_storage> status of any result object instances
 derived from this resultset (this includes the contents of the
 L<resultset cache|/set_cache> if any). See L</delete_all> if you need to
 execute any on-delete triggers or cascades defined either by you or a
@@ -2204,11 +2244,11 @@ sub _normalize_populate_args {
 
 =item Arguments: none
 
-=item Return Value: $pager
+=item Return Value: L<$pager|Data::Page>
 
 =back
 
-Return Value a L<Data::Page> object for the current resultset. Only makes
+Returns a L<Data::Page> object for the current resultset. Only makes
 sense for queries with a C<page> attribute.
 
 To get the full count of entries for a paged resultset, call
@@ -2251,7 +2291,7 @@ sub pager {
 
 =item Arguments: $page_number
 
-=item Return Value: $rs
+=item Return Value: L<$resultset|/search>
 
 =back
 
@@ -2270,16 +2310,16 @@ sub page {
 
 =over 4
 
-=item Arguments: \%vals
+=item Arguments: \%col_data
 
-=item Return Value: $rowobject
+=item Return Value: L<$result|DBIx::Class::Manual::ResultClass>
 
 =back
 
-Creates a new row object in the resultset's result class and returns
+Creates a new result object in the resultset's result class and returns
 it. The row is not inserted into the database at this point, call
 L<DBIx::Class::Row/insert> to do that. Calling L<DBIx::Class::Row/in_storage>
-will tell you whether the row object has been inserted or not.
+will tell you whether the result object has been inserted or not.
 
 Passes the hashref of input on to L<DBIx::Class::Row/new>.
 
@@ -2287,7 +2327,11 @@ Passes the hashref of input on to L<DBIx::Class::Row/new>.
 
 sub new_result {
   my ($self, $values) = @_;
-  $self->throw_exception( "new_result needs a hash" )
+
+  $self->throw_exception( "new_result takes only one argument - a hashref of values" )
+    if @_ > 2;
+
+  $self->throw_exception( "new_result expects a hashref" )
     unless (ref $values eq 'HASH');
 
   my ($merged_cond, $cols_from_relations) = $self->_merge_with_rscond($values);
@@ -2473,7 +2517,7 @@ sub _remove_alias {
 
 =item Arguments: none
 
-=item Return Value: \[ $sql, @bind ]
+=item Return Value: \[ $sql, L<@bind_values|/DBIC BIND VALUES> ]
 
 =back
 
@@ -2504,9 +2548,9 @@ sub as_query {
 
 =over 4
 
-=item Arguments: \%vals, \%attrs?
+=item Arguments: \%col_data, { key => $unique_constraint, L<%attrs|/ATTRIBUTES> }?
 
-=item Return Value: $rowobject
+=item Return Value: L<$result|DBIx::Class::Manual::ResultClass>
 
 =back
 
@@ -2551,9 +2595,9 @@ sub find_or_new {
 
 =over 4
 
-=item Arguments: \%vals
+=item Arguments: \%col_data
 
-=item Return Value: a L<DBIx::Class::Row> $object
+=item Return Value: L<$result|DBIx::Class::Manual::ResultClass>
 
 =back
 
@@ -2577,12 +2621,11 @@ This can be applied recursively, and will work correctly for a structure
 with an arbitrary depth and width, as long as the relationships actually
 exists and the correct column data has been supplied.
 
-
 Instead of hashrefs of plain related data (key/value pairs), you may
 also pass new or inserted objects. New objects (not inserted yet, see
-L</new>), will be inserted into their appropriate tables.
+L</new_result>), will be inserted into their appropriate tables.
 
-Effectively a shortcut for C<< ->new_result(\%vals)->insert >>.
+Effectively a shortcut for C<< ->new_result(\%col_data)->insert >>.
 
 Example of creating a new row.
 
@@ -2620,9 +2663,10 @@ C<belongs_to> resultset. Note Hashref.
 When subclassing ResultSet never attempt to override this method. Since
 it is a simple shortcut for C<< $self->new_result($attrs)->insert >>, a
 lot of the internals simply never call it, so your override will be
-bypassed more often than not. Override either L<new|DBIx::Class::Row/new>
-or L<insert|DBIx::Class::Row/insert> depending on how early in the
-L</create> process you need to intervene.
+bypassed more often than not. Override either L<DBIx::Class::Row/new>
+or L<DBIx::Class::Row/insert> depending on how early in the
+L</create> process you need to intervene. See also warning pertaining to
+L</new>.
 
 =back
 
@@ -2639,9 +2683,9 @@ sub create {
 
 =over 4
 
-=item Arguments: \%vals, \%attrs?
+=item Arguments: \%col_data, { key => $unique_constraint, L<%attrs|/ATTRIBUTES> }?
 
-=item Return Value: $rowobject
+=item Return Value: L<$result|DBIx::Class::Manual::ResultClass>
 
 =back
 
@@ -2700,7 +2744,7 @@ database!
     year   => 2005,
   });
 
-  if( $cd->in_storage ) {
+  if( !$cd->in_storage ) {
       # do some stuff
       $cd->insert;
   }
@@ -2721,16 +2765,16 @@ sub find_or_create {
 
 =over 4
 
-=item Arguments: \%col_values, { key => $unique_constraint }?
+=item Arguments: \%col_data, { key => $unique_constraint, L<%attrs|/ATTRIBUTES> }?
 
-=item Return Value: $row_object
+=item Return Value: L<$result|DBIx::Class::Manual::ResultClass>
 
 =back
 
   $resultset->update_or_create({ col => $val, ... });
 
 Like L</find_or_create>, but if a row is found it is immediately updated via
-C<< $found_row->update (\%col_values) >>.
+C<< $found_row->update (\%col_data) >>.
 
 
 Takes an optional C<key> attribute to search on a specific unique constraint.
@@ -2771,20 +2815,6 @@ L</update_or_new> and L<DBIx::Class::Row/in_storage> instead. Don't forget
 to call L<DBIx::Class::Row/insert> to save the newly created row to the
 database!
 
-  my $cd = $schema->resultset('CD')->update_or_new(
-    {
-      artist => 'Massive Attack',
-      title  => 'Mezzanine',
-      year   => 1998,
-    },
-    { key => 'cd_artist_title' }
-  );
-
-  if( $cd->in_storage ) {
-      # do some stuff
-      $cd->insert;
-  }
-
 =cut
 
 sub update_or_create {
@@ -2805,16 +2835,16 @@ sub update_or_create {
 
 =over 4
 
-=item Arguments: \%col_values, { key => $unique_constraint }?
+=item Arguments: \%col_data, { key => $unique_constraint, L<%attrs|/ATTRIBUTES> }?
 
-=item Return Value: $rowobject
+=item Return Value: L<$result|DBIx::Class::Manual::ResultClass>
 
 =back
 
   $resultset->update_or_new({ col => $val, ... });
 
 Like L</find_or_new> but if a row is found it is immediately updated via
-C<< $found_row->update (\%col_values) >>.
+C<< $found_row->update (\%col_data) >>.
 
 For example:
 
@@ -2870,7 +2900,7 @@ sub update_or_new {
 
 =item Arguments: none
 
-=item Return Value: \@cache_objects | undef
+=item Return Value: L<\@result_objs|DBIx::Class::Manual::ResultClass> | undef
 
 =back
 
@@ -2889,15 +2919,15 @@ sub get_cache {
 
 =over 4
 
-=item Arguments: \@cache_objects
+=item Arguments: L<\@result_objs|DBIx::Class::Manual::ResultClass>
 
-=item Return Value: \@cache_objects
+=item Return Value: L<\@result_objs|DBIx::Class::Manual::ResultClass>
 
 =back
 
 Sets the contents of the cache for the resultset. Expects an arrayref
 of objects of the same class as those produced by the resultset. Note that
-if the cache is set the resultset will return the cached objects rather
+if the cache is set, the resultset will return the cached objects rather
 than re-querying the database even if the cache attr is not set.
 
 The contents of the cache can also be populated by using the
@@ -2968,9 +2998,9 @@ sub is_ordered {
 
 =over 4
 
-=item Arguments: $relationship_name
+=item Arguments: $rel_name
 
-=item Return Value: $resultset
+=item Return Value: L<$resultset|/search>
 
 =back
 
@@ -3014,7 +3044,7 @@ sub related_resultset {
 
     if (my $cache = $self->get_cache) {
       if ($cache->[0] && $cache->[0]->related_resultset($rel)->get_cache) {
-        $new_cache = [ map { @{$_->related_resultset($rel)->get_cache||[]} }
+        $new_cache = [ map { @{$_->related_resultset($rel)->get_cache} }
                         @$cache ];
       }
     }
@@ -3093,7 +3123,7 @@ sub current_source_alias {
 
 =item Arguments: none
 
-=item Return Value: $resultset
+=item Return Value: L<$resultset|/search>
 
 =back
 
@@ -3295,7 +3325,7 @@ sub _resolved_attrs {
   if (my $cols = delete $attrs->{columns}) {
     for my $c (ref $cols eq 'ARRAY' ? @$cols : $cols) {
       if (ref $c eq 'HASH') {
-        for my $as (keys %$c) {
+        for my $as (sort keys %$c) {
           push @sel, $c->{$as};
           push @as, $as;
         }
@@ -3317,10 +3347,14 @@ sub _resolved_attrs {
     if $attrs->{select};
 
   # assume all unqualified selectors to apply to the current alias (legacy stuff)
-  $_ = (ref $_ or $_ =~ /\./) ? $_ : "$alias.$_" for @sel;
+  for (@sel) {
+    $_ = (ref $_ or $_ =~ /\./) ? $_ : "$alias.$_";
+  }
 
-  # disqualify all $alias.col as-bits (inflate-map mandated)
-  $_ = ($_ =~ /^\Q$alias.\E(.+)$/) ? $1 : $_ for @as;
+  # disqualify all $alias.col as-bits (collapser mandated)
+  for (@as) {
+    $_ = ($_ =~ /^\Q$alias.\E(.+)$/) ? $1 : $_;
+  }
 
   # de-duplicate the result (remove *identical* select/as pairs)
   # and also die on duplicate {as} pointing to different {select}s
@@ -3407,17 +3441,15 @@ sub _resolved_attrs {
     }
   }
 
-  # generate selections based on the prefetch helper
-  my $prefetch;
-  $prefetch = $self->_merge_joinpref_attr( {}, delete $attrs->{prefetch} )
-    if defined $attrs->{prefetch};
-
-  if ($prefetch) {
+  $attrs->{collapse} ||= {};
+  if ($attrs->{prefetch}) {
 
     $self->throw_exception("Unable to prefetch, resultset contains an unnamed selector $attrs->{_dark_selector}{string}")
       if $attrs->{_dark_selector};
 
-    $attrs->{collapse} = 1;
+    my $prefetch = $self->_merge_joinpref_attr( {}, delete $attrs->{prefetch} );
+
+    my $prefetch_ordering = [];
 
     # this is a separate structure (we don't look in {from} directly)
     # as the resolver needs to shift things off the lists to work
@@ -3440,7 +3472,8 @@ sub _resolved_attrs {
       }
     }
 
-    my @prefetch = $source->_resolve_prefetch( $prefetch, $alias, $join_map );
+    my @prefetch =
+      $source->_resolve_prefetch( $prefetch, $alias, $join_map, $prefetch_ordering, $attrs->{collapse} );
 
     # we need to somehow mark which columns came from prefetch
     if (@prefetch) {
@@ -3450,39 +3483,9 @@ sub _resolved_attrs {
 
     push @{ $attrs->{select} }, (map { $_->[0] } @prefetch);
     push @{ $attrs->{as} }, (map { $_->[1] } @prefetch);
-  }
-
-  $attrs->{_single_object_inflation} = ! List::Util::first { $_ =~ /\./ } @{$attrs->{as}};
-
-  # run through the resulting joinstructure (starting from our current slot)
-  # and unset collapse if proven unnesessary
-  if ($attrs->{collapse} && ref $attrs->{from} eq 'ARRAY') {
-
-    if (@{$attrs->{from}} > 1) {
-
-      # find where our table-spec starts and consider only things after us
-      my @fromlist = @{$attrs->{from}};
-      while (@fromlist) {
-        my $t = shift @fromlist;
-        $t = $t->[0] if ref $t eq 'ARRAY';  #me vs join from-spec mismatch
-        last if ($t->{-alias} && $t->{-alias} eq $alias);
-      }
-
-      for (@fromlist) {
-        $attrs->{collapse} = ! $_->[0]{-is_single}
-          and last;
-      }
-    }
-    else {
-      # no joins - no collapse
-      $attrs->{collapse} = 0;
-    }
-  }
 
-  if (! $attrs->{order_by} and $attrs->{collapse}) {
-    # default order for collapsing unless the user asked for something
-    $attrs->{order_by} = [ map { "$alias.$_" } $source->primary_columns ];
-    $attrs->{_ordered_for_collapse} = 1;
+    push( @{$attrs->{order_by}}, @$prefetch_ordering );
+    $attrs->{_collapse_order_by} = \@$prefetch_ordering;
   }
 
   # if both page and offset are specified, produce a combined offset
@@ -3707,9 +3710,6 @@ sub STORABLE_freeze {
   # A cursor in progress can't be serialized (and would make little sense anyway)
   delete $to_serialize->{cursor};
 
-  # the parser can be regenerated
-  delete $to_serialize->{_row_parser};
-
   # nor is it sensical to store a not-yet-fired-count pager
   if ($to_serialize->{pager} and ref $to_serialize->{pager}{total_entries} eq 'CODE') {
     delete $to_serialize->{pager};
@@ -3754,6 +3754,10 @@ searching for data. They can be passed to any method which takes an
 C<\%attrs> argument. See L</search>, L</search_rs>, L</find>,
 L</count>.
 
+Default attributes can be set on the result class using
+L<DBIx::Class::ResultSource/resultset_attributes>.  (Please read
+the CAVEATS on that feature before using it!)
+
 These are in no particular order:
 
 =head2 order_by
@@ -4008,6 +4012,12 @@ to Earth' and a cd with title 'Popular'.
 If you want to fetch related objects from other tables as well, see C<prefetch>
 below.
 
+ NOTE: An internal join-chain pruner will discard certain joins while
+ constructing the actual SQL query, as long as the joins in question do not
+ affect the retrieved result. This for example includes 1:1 left joins
+ that are not part of the restriction specification (WHERE/HAVING) nor are
+ a part of the query selection.
+
 For more help on using joins with search, see L<DBIx::Class::Manual::Joining>.
 
 =head2 prefetch
@@ -4113,12 +4123,6 @@ relationship on a given level. e.g.:
    }
  );
 
-In fact, C<DBIx::Class> will emit the following warning:
-
- Prefetching multiple has_many rels tracks and cd_to_producer at top
- level will explode the number of row objects retrievable via ->next
- or ->all. Use at your own risk.
-
 The collapser currently can't identify duplicate tuples for multiple
 L<has_many|DBIx::Class::Relationship/has_many> relationships and as a
 result the second L<has_many|DBIx::Class::Relationship/has_many>
@@ -4196,6 +4200,37 @@ behavior may or may not survive the 0.09 transition.
 
 =back
 
+=head2 alias
+
+=over 4
+
+=item Value: $source_alias
+
+=back
+
+Sets the source alias for the query.  Normally, this defaults to C<me>, but
+nested search queries (sub-SELECTs) might need specific aliases set to
+reference inner queries.  For example:
+
+   my $q = $rs
+      ->related_resultset('CDs')
+      ->related_resultset('Tracks')
+      ->search({
+         'track.id' => { -ident => 'none_search.id' },
+      })
+      ->as_query;
+
+   my $ids = $self->search({
+      -not_exists => $q,
+   }, {
+      alias    => 'none_search',
+      group_by => 'none_search.id',
+   })->get_column('id')->as_query;
+
+   $self->search({ id => { -in => $ids } })
+
+This attribute is directly tied to L</current_source_alias>.
+
 =head2 page
 
 =over 4
@@ -4302,7 +4337,7 @@ attribute, this setting is ignored and an appropriate warning is issued.
 Adds to the WHERE clause.
 
   # only return rows WHERE deleted IS NULL for all searches
-  __PACKAGE__->resultset_attributes({ where => { deleted => undef } }); )
+  __PACKAGE__->resultset_attributes({ where => { deleted => undef } });
 
 Can be overridden by passing C<< { where => undef } >> as an attribute
 to a resultset.
@@ -4333,12 +4368,69 @@ L<DBIx::Class::Manual::Cookbook>.
 
 =over 4
 
-=item Value: ( 'update' | 'shared' )
+=item Value: ( 'update' | 'shared' | \$scalar )
 
 =back
 
 Set to 'update' for a SELECT ... FOR UPDATE or 'shared' for a SELECT
-... FOR SHARED.
+... FOR SHARED. If \$scalar is passed, this is taken directly and embedded in the
+query.
+
+=head1 DBIC BIND VALUES
+
+Because DBIC may need more information to bind values than just the column name
+and value itself, it uses a special format for both passing and receiving bind
+values.  Each bind value should be composed of an arrayref of
+C<< [ \%args => $val ] >>.  The format of C<< \%args >> is currently:
+
+=over 4
+
+=item dbd_attrs
+
+If present (in any form), this is what is being passed directly to bind_param.
+Note that different DBD's expect different bind args.  (e.g. DBD::SQLite takes
+a single numerical type, while DBD::Pg takes a hashref if bind options.)
+
+If this is specified, all other bind options described below are ignored.
+
+=item sqlt_datatype
+
+If present, this is used to infer the actual bind attribute by passing to
+C<< $resolved_storage->bind_attribute_by_data_type() >>.  Defaults to the
+"data_type" from the L<add_columns column info|DBIx::Class::ResultSource/add_columns>.
+
+Note that the data type is somewhat freeform (hence the sqlt_ prefix);
+currently drivers are expected to "Do the Right Thing" when given a common
+datatype name.  (Not ideal, but that's what we got at this point.)
+
+=item sqlt_size
+
+Currently used to correctly allocate buffers for bind_param_inout().
+Defaults to "size" from the L<add_columns column info|DBIx::Class::ResultSource/add_columns>,
+or to a sensible value based on the "data_type".
+
+=item dbic_colname
+
+Used to fill in missing sqlt_datatype and sqlt_size attributes (if they are
+explicitly specified they are never overriden).  Also used by some weird DBDs,
+where the column name should be available at bind_param time (e.g. Oracle).
+
+=back
+
+For backwards compatibility and convenience, the following shortcuts are
+supported:
+
+  [ $name => $val ] === [ { dbic_colname => $name }, $val ]
+  [ \$dt  => $val ] === [ { sqlt_datatype => $dt }, $val ]
+  [ undef,   $val ] === [ {}, $val ]
+
+=head1 AUTHOR AND CONTRIBUTORS
+
+See L<AUTHOR|DBIx::Class/AUTHOR> and L<CONTRIBUTORS|DBIx::Class/CONTRIBUTORS> in DBIx::Class
+
+=head1 LICENSE
+
+You may distribute this code under the same terms as Perl itself.
 
 =cut