Merge 'trunk' into 'prefetch'
Peter Rabbitson [Mon, 1 Mar 2010 00:39:55 +0000 (00:39 +0000)]
r8759@Thesaurus (orig r8746):  ribasushi | 2010-02-19 00:30:37 +0100
Fix bogus test
r8760@Thesaurus (orig r8747):  ribasushi | 2010-02-19 00:34:22 +0100
Retire useless abstraction (all rdbms need this anyway)
r8761@Thesaurus (orig r8748):  ribasushi | 2010-02-19 00:35:01 +0100
Fix count of group_by over aliased function
r8765@Thesaurus (orig r8752):  ribasushi | 2010-02-19 10:11:20 +0100
 r8497@Thesaurus (orig r8484):  ribasushi | 2010-01-31 10:06:29 +0100
 Branch to unify mandatory PK handling
 r8498@Thesaurus (orig r8485):  ribasushi | 2010-01-31 10:20:36 +0100
 This is not really used for anything (same code in DBI)
 r8499@Thesaurus (orig r8486):  ribasushi | 2010-01-31 10:25:55 +0100
 Helper primary_columns wrapper to throw if a PK is not defined
 r8500@Thesaurus (orig r8487):  ribasushi | 2010-01-31 11:07:25 +0100
 Stupid errors
 r8501@Thesaurus (orig r8488):  ribasushi | 2010-01-31 12:18:57 +0100
 Saner handling of nonexistent/partial conditions
 r8762@Thesaurus (orig r8749):  ribasushi | 2010-02-19 10:07:40 +0100
 trap unresolvable conditions due to incomplete relationship specification
 r8764@Thesaurus (orig r8751):  ribasushi | 2010-02-19 10:11:09 +0100
 Changes

r8767@Thesaurus (orig r8754):  ribasushi | 2010-02-19 11:14:30 +0100
Fix for RT54697
r8769@Thesaurus (orig r8756):  caelum | 2010-02-19 12:21:53 +0100
bump Test::Pod dep
r8770@Thesaurus (orig r8757):  caelum | 2010-02-19 12:23:07 +0100
bump Test::Pod dep in Optional::Dependencies too
r8773@Thesaurus (orig r8760):  rabbit | 2010-02-19 16:41:24 +0100
Fix stupid sqlt parser regression
r8774@Thesaurus (orig r8761):  rabbit | 2010-02-19 16:42:40 +0100
Port remaining tests to the Opt::Dep reposiory
r8775@Thesaurus (orig r8762):  rabbit | 2010-02-19 16:43:36 +0100
Some test cleanups
r8780@Thesaurus (orig r8767):  rabbit | 2010-02-20 20:59:20 +0100
Test::Deep actually isn't required
r8786@Thesaurus (orig r8773):  rabbit | 2010-02-20 22:21:41 +0100
These are core for perl 5.8
r8787@Thesaurus (orig r8774):  rabbit | 2010-02-21 10:52:40 +0100
Shuffle tests a bit
r8788@Thesaurus (orig r8775):  rabbit | 2010-02-21 12:09:25 +0100
Bogus require
r8789@Thesaurus (orig r8776):  rabbit | 2010-02-21 12:09:48 +0100
Bogus unnecessary dep
r8800@Thesaurus (orig r8787):  rabbit | 2010-02-21 13:39:21 +0100
 r8748@Thesaurus (orig r8735):  goraxe | 2010-02-17 23:17:15 +0100
 branch for dbicadmin pod fixes

 r8778@Thesaurus (orig r8765):  goraxe | 2010-02-20 20:35:00 +0100
 add G:L:D sub classes to generate pod
 r8779@Thesaurus (orig r8766):  goraxe | 2010-02-20 20:56:16 +0100
 dbicadmin: use subclassed G:L:D to generate some pod
 r8782@Thesaurus (orig r8769):  goraxe | 2010-02-20 21:48:29 +0100
 adjust Makefile.pl to generate dbicadmin.pod
 r8783@Thesaurus (orig r8770):  goraxe | 2010-02-20 21:50:55 +0100
 add svn-ignore for dbicadmin.pod
 r8784@Thesaurus (orig r8771):  goraxe | 2010-02-20 22:01:41 +0100
 change Options to Arguments
 r8785@Thesaurus (orig r8772):  goraxe | 2010-02-20 22:10:29 +0100
 add DBIx::Class::Admin::{Descriptive,Usage} to podcover ignore list
 r8790@Thesaurus (orig r8777):  rabbit | 2010-02-21 12:35:38 +0100
 Cleanup the makefile regen a bit
 r8792@Thesaurus (orig r8779):  rabbit | 2010-02-21 12:53:01 +0100
 Bah humbug
 r8793@Thesaurus (orig r8780):  rabbit | 2010-02-21 12:55:18 +0100
 And another one
 r8797@Thesaurus (orig r8784):  rabbit | 2010-02-21 13:32:03 +0100
 The minimal pod seems to confuse the manpage generator, commenting out for now
 r8798@Thesaurus (orig r8785):  rabbit | 2010-02-21 13:38:03 +0100
 Add license/author to dbicadmin autogen POD
 r8799@Thesaurus (orig r8786):  rabbit | 2010-02-21 13:38:58 +0100
 Reorder makefile author actions to make output more readable

r8803@Thesaurus (orig r8790):  ribasushi | 2010-02-21 14:24:15 +0100
Fix exception text
r8804@Thesaurus (orig r8791):  ribasushi | 2010-02-21 15:14:58 +0100
Extra testdep
r8808@Thesaurus (orig r8795):  caelum | 2010-02-22 20:16:07 +0100
with_deferred_fk_checks for Oracle
r8809@Thesaurus (orig r8796):  rabbit | 2010-02-22 21:26:20 +0100
Add a hidden option to dbicadmin to self-inject autogenerated POD
r8810@Thesaurus (orig r8797):  caelum | 2010-02-22 21:48:43 +0100
improve with_deferred_fk_checks for Oracle, add tests
r8812@Thesaurus (orig r8799):  rbuels | 2010-02-22 23:09:40 +0100
added package name to DBD::Pg warning in Pg storage driver to make it explicit where the warning is coming from
r8815@Thesaurus (orig r8802):  rabbit | 2010-02-23 11:21:10 +0100
Looks like the distdir wrapping is finally taken care of
r8818@Thesaurus (orig r8805):  rabbit | 2010-02-23 14:05:14 +0100
remove POD
r8819@Thesaurus (orig r8806):  rabbit | 2010-02-23 14:05:32 +0100
More index exclusions
r8821@Thesaurus (orig r8808):  goraxe | 2010-02-23 15:00:38 +0100
remove short options from dbicadmin
r8822@Thesaurus (orig r8809):  rabbit | 2010-02-23 15:15:00 +0100
Whitespace
r8826@Thesaurus (orig r8813):  rabbit | 2010-02-24 09:28:43 +0100
 r8585@Thesaurus (orig r8572):  faxm0dem | 2010-02-06 23:01:04 +0100
 sqlt::producer::oracle is now able to handle quotes correctly. Now we need to take advantage of that as currently the oracle producer capitalises everything
 r8586@Thesaurus (orig r8573):  faxm0dem | 2010-02-06 23:03:31 +0100
 the way I thought. ribasushi suggested to override deploy(ment_statements)
 r8607@Thesaurus (orig r8594):  faxm0dem | 2010-02-09 21:53:48 +0100
 should work now
 r8714@Thesaurus (orig r8701):  faxm0dem | 2010-02-14 09:49:44 +0100
 oracle_version
 r8747@Thesaurus (orig r8734):  faxm0dem | 2010-02-17 18:54:45 +0100
 still need to uc source_name if quotes off
 r8817@Thesaurus (orig r8804):  rabbit | 2010-02-23 12:03:23 +0100
 Cleanup code (hopefully no functional changes)
 r8820@Thesaurus (orig r8807):  rabbit | 2010-02-23 14:14:19 +0100
 Proper error message
 r8823@Thesaurus (orig r8810):  faxm0dem | 2010-02-23 15:46:11 +0100
 Schema Object Naming Rules :
 [...]
 However, database names, global database names, and database link names are always case insensitive and are stored as uppercase.

 # source: http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/sql_elements008.htm

 r8824@Thesaurus (orig r8811):  rabbit | 2010-02-23 16:09:36 +0100
 Changes and dep-bump

r8828@Thesaurus (orig r8815):  rabbit | 2010-02-24 09:32:53 +0100
Changelogging
r8829@Thesaurus (orig r8816):  rabbit | 2010-02-24 09:37:14 +0100
Protect dbicadmin from self-injection when not in make
r8830@Thesaurus (orig r8817):  rabbit | 2010-02-24 10:00:43 +0100
Release 0.08120
r8832@Thesaurus (orig r8819):  rabbit | 2010-02-24 10:02:36 +0100
Bump trunk version
r8833@Thesaurus (orig r8820):  goraxe | 2010-02-24 14:21:23 +0100
 do not include hidden opts in generated pod
r8834@Thesaurus (orig r8821):  rabbit | 2010-02-24 15:50:34 +0100
small tool to query cpan deps
r8835@Thesaurus (orig r8822):  rabbit | 2010-02-26 00:22:51 +0100
Typo
r8849@Thesaurus (orig r8836):  rabbit | 2010-03-01 01:32:03 +0100
Cleanup logic in RSC
r8850@Thesaurus (orig r8837):  rabbit | 2010-03-01 01:36:24 +0100
Fix incorrect placement of condition resolution failure trap
r8851@Thesaurus (orig r8838):  rabbit | 2010-03-01 01:37:53 +0100
Changes

1  2 
lib/DBIx/Class/ResultSet.pm
lib/DBIx/Class/ResultSetColumn.pm
lib/DBIx/Class/ResultSource.pm
lib/DBIx/Class/Storage/DBI.pm
t/inflate/hri.t

@@@ -141,7 -141,7 +141,7 @@@ See: L</search>, L</count>, L</get_colu
  =head1 OVERLOADING
  
  If a resultset is used in a numeric context it returns the L</count>.
- However, if it is used in a booleand context it is always true.  So if
+ However, if it is used in a boolean context it is always true.  So if
  you want to check if a resultset has any results use C<if $rs != 0>.
  C<if $rs> will always be true.
  
@@@ -524,7 -524,7 +524,7 @@@ sub find 
      # in ::Relationship::Base::search_related (the row method), and furthermore
      # the relationship is of the 'single' type. This means that the condition
      # provided by the relationship (already attached to $self) is sufficient,
-     # as there can be only one row in the databse that would satisfy the
+     # as there can be only one row in the database that would satisfy the
      # relationship
    }
    else {
    }
  
    # Run the query
 -  my $rs = $self->search ($query, $attrs);
 -  if (keys %{$rs->_resolved_attrs->{collapse}}) {
 +  my $rs = $self->search ($query, {result_class => $self->result_class, %$attrs});
 +  if ($rs->_resolved_attrs->{collapse}) {
      my $row = $rs->next;
      carp "Query returned more than one row" if $rs->next;
      return $row;
@@@ -639,7 -639,7 +639,7 @@@ sub search_related 
  =head2 search_related_rs
  
  This method works exactly the same as search_related, except that
- it guarantees a restultset, even in list context.
+ it guarantees a resultset, even in list context.
  
  =cut
  
@@@ -697,7 -697,7 +697,7 @@@ L<DBIx::Class::ResultSet> returned
  
  =item B<Note>
  
- As of 0.08100, this method enforces the assumption that the preceeding
+ As of 0.08100, this method enforces the assumption that the preceding
  query returns only one row. If more than one row is returned, you will receive
  a warning:
  
@@@ -723,7 -723,7 +723,7 @@@ sub single 
  
    my $attrs = $self->_resolved_attrs_copy;
  
 -  if (keys %{$attrs->{collapse}}) {
 +  if ($attrs->{collapse}) {
      $self->throw_exception(
        'single() can not be used on resultsets prefetching has_many. Use find( \%cond ) or next() instead'
      );
@@@ -976,103 -976,127 +976,103 @@@ sub _construct_object 
    return @new;
  }
  
 -sub _collapse_result {
 -  my ($self, $as_proto, $row) = @_;
 -
 -  my @copy = @$row;
 -
 -  # 'foo'         => [ undef, 'foo' ]
 -  # 'foo.bar'     => [ 'foo', 'bar' ]
 -  # 'foo.bar.baz' => [ 'foo.bar', 'baz' ]
 -
 -  my @construct_as = map { [ (/^(?:(.*)\.)?([^.]+)$/) ] } @$as_proto;
 -
 -  my %collapse = %{$self->{_attrs}{collapse}||{}};
 -
 -  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.
 +# two arguments: $as_proto is an arrayref of column names,
 +# $row_ref is an arrayref of the data. If none of the row data
 +# is defined we return undef (that's copied from the old
 +# _collapse_result). Next we decide whether we need to collapse
 +# the resultset (i.e. we prefetch something) or not. $collapse
 +# indicates that. The do-while loop will run once if we do not need
 +# to collapse the result and will run as long as _merge_result returns
 +# a true value. It will return undef if the current added row does not
 +# match the previous row. A bit of stashing and cursor magic is
 +# required so that the cursor is not mixed up.
  
 -  # 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
 +# "$rows" is a bit misleading. In the end, there should only be one
 +# element in this arrayref. 
  
 -  # 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->primary_columns;
 -    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!)
 +sub _collapse_result {
 +    my ( $self, $as_proto, $row_ref ) = @_;
 +    my $has_def;
 +    for (@$row_ref) {
 +        if ( defined $_ ) {
 +            $has_def++;
 +            last;
 +        }
      }
 -  }
 -
 -  # no need to do an if, it'll be empty if @pri_index is empty anyway
 -
 -  my %pri_vals = map { ($_ => $copy[$_]) } @pri_index;
 -
 -  my @const_rows;
 +    return undef unless $has_def;
 +
 +    my $collapse = $self->_resolved_attrs->{collapse};
 +    my $rows     = [];
 +    my @row      = @$row_ref;
 +    do {
 +        my $i = 0;
 +        my $row = { map { $_ => $row[ $i++ ] } @$as_proto };
 +        $row = $self->result_source->_parse_row($row, $collapse);
 +        unless ( scalar @$rows ) {
 +            push( @$rows, $row );
 +        }
 +        $collapse = undef unless ( $self->_merge_result( $rows, $row ) );
 +      } while (
 +        $collapse
 +        && do { @row = $self->cursor->next; $self->{stashed_row} = \@row if @row; }
 +      );
  
 -  do { # no need to check anything at the front, we always want the first row
 +    return $rows->[0];
  
 -    my %const;
 +}
  
 -    foreach my $this_as (@construct_as) {
 -      $const{$this_as->[0]||''}{$this_as->[1]} = shift(@copy);
 +# _merge_result accepts an arrayref of rows objects (again, an arrayref of two elements)
 +# and a row object which should be merged into the first object.
 +# First we try to find out whether $row is already in $rows. If this is the case
 +# we try to merge them by iteration through their relationship data. We call
 +# _merge_result again on them, so they get merged.
 +
 +# If we don't find the $row in $rows, we append it to $rows and return undef.
 +# _merge_result returns 1 otherwise (i.e. $row has been found in $rows).
 +
 +sub _merge_result {
 +    my ( $self, $rows, $row ) = @_;
 +    my ( $columns, $rels ) = @$row;
 +    my $found = undef;
 +    foreach my $seen (@$rows) {
 +        my $match = 1;
 +        foreach my $column ( keys %$columns ) {
 +            if (   defined $seen->[0]->{$column} ^ defined $columns->{$column}
 +                or defined $columns->{$column}
 +                && $seen->[0]->{$column} ne $columns->{$column} )
 +            {
 +
 +                $match = 0;
 +                last;
 +            }
 +        }
 +        if ($match) {
 +            $found = $seen;
 +            last;
 +        }
      }
 +    if ($found) {
 +        foreach my $rel ( keys %$rels ) {
 +            my $old_rows = $found->[1]->{$rel};
 +            $self->_merge_result(
 +                ref $found->[1]->{$rel}->[0] eq 'HASH' ? [ $found->[1]->{$rel} ]
 +                : $found->[1]->{$rel},
 +                ref $rels->{$rel}->[0] eq 'HASH' ? [ $rels->{$rel}->[0], $rels->{$rel}->[1] ]
 +                : $rels->{$rel}->[0]
 +            );
  
 -    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;
 -
 -  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};
 -      }
 +
 +    }
 +    else {
 +        push( @$rows, $row );
 +        return undef;
      }
 -  }
  
 -  return $info;
 +    return 1;
  }
  
 +
  =head2 result_source
  
  =over 4
@@@ -1112,6 -1136,7 +1112,7 @@@ sub result_class 
    if ($result_class) {
      $self->ensure_class_loaded($result_class);
      $self->_result_class($result_class);
+     $self->{attrs}{result_class} = $result_class if ref $self;
    }
    $self->_result_class;
  }
@@@ -1236,11 -1261,11 +1237,11 @@@ sub _count_subq_rs 
  
    # if we multi-prefetch we group_by primary keys only 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}) {
-     $sub_attrs->{group_by} = [ map { "$attrs->{alias}.$_" } ($rsrc->primary_columns) ]
 -  if ( keys %{$attrs->{collapse}}  ) {
++  if ( $attrs->{collapse}  ) {
+     $sub_attrs->{group_by} = [ map { "$attrs->{alias}.$_" } ($rsrc->_pri_cols) ]
    }
  
-   $sub_attrs->{select} = $rsrc->storage->_subq_count_select ($rsrc, $sub_attrs);
+   $sub_attrs->{select} = $rsrc->storage->_subq_count_select ($rsrc, $attrs);
  
    # this is so that the query can be simplified e.g.
    # * ordering can be thrown away in things like Top limit
@@@ -1306,7 -1331,7 +1307,7 @@@ sub all 
  
    my @obj;
  
 -  if (keys %{$self->_resolved_attrs->{collapse}}) {
 +  if ($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
@@@ -1396,7 -1421,7 +1397,7 @@@ sub _rs_update_delete 
      my $attrs = $self->_resolved_attrs_copy;
  
      delete $attrs->{$_} for qw/collapse select as/;
-     $attrs->{columns} = [ map { "$attrs->{alias}.$_" } ($self->result_source->primary_columns) ];
+     $attrs->{columns} = [ map { "$attrs->{alias}.$_" } ($self->result_source->_pri_cols) ];
  
      if ($needs_group_by_subq) {
        # make sure no group_by was supplied, or if there is one - make sure it matches
@@@ -1573,7 -1598,7 +1574,7 @@@ Example:  Assuming an Artist Class tha
        ],
       },
       { artistid => 5, name => 'Angsty-Whiny Girl', cds => [
-         { title => 'My parents sold me to a record company' ,year => 2005 },
+         { title => 'My parents sold me to a record company', year => 2005 },
          { title => 'Why Am I So Ugly?', year => 2006 },
          { title => 'I Got Surgery and am now Popular', year => 2007 }
        ],
@@@ -1601,7 -1626,7 +1602,7 @@@ example
      [qw/artistid name/],
      [100, 'A Formally Unknown Singer'],
      [101, 'A singer that jumped the shark two albums ago'],
-     [102, 'An actually cool singer.'],
+     [102, 'An actually cool singer'],
    ]);
  
  Please note an important effect on your data when choosing between void and
@@@ -2108,7 -2133,7 +2109,7 @@@ To create related objects, pass a hashr
  B<keyed on the relationship name>. If the relationship is of type C<multi>
  (L<DBIx::Class::Relationship/has_many>) - pass an arrayref of hashrefs.
  The process will correctly identify columns holding foreign keys, and will
- transparrently populate them from the keys of the corresponding relation.
+ transparently populate them from the keys of the corresponding relation.
  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.
@@@ -2841,6 -2866,7 +2842,6 @@@ sub _resolved_attrs 
      }
    }
    else {
 -
      # otherwise we intialise select & as to empty
      $attrs->{select} = [];
      $attrs->{as}     = [];
      }
    }
  
 -  $attrs->{collapse} ||= {};
    if ( my $prefetch = delete $attrs->{prefetch} ) {
 -    $prefetch = $self->_merge_attr( {}, $prefetch );
 +    $attrs->{collapse} = 1;
  
      my $prefetch_ordering = [];
  
        }
      }
  
 -    my @prefetch =
 -      $source->_resolve_prefetch( $prefetch, $alias, $join_map, $prefetch_ordering, $attrs->{collapse} );
 +    my @prefetch = $source->_resolve_prefetch( $prefetch, $alias, $join_map, $prefetch_ordering );
  
      # we need to somehow mark which columns came from prefetch
      $attrs->{_prefetch_select} = [ map { $_->[0] } @prefetch ];
      $attrs->{_collapse_order_by} = \@$prefetch_ordering;
    }
  
 +  # 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);
 +      }
 +
 +      if (@fromlist) {
 +        $attrs->{collapse} = scalar grep { ! $_->[0]{-is_single} } (@fromlist);
 +      }
 +    }
 +    else {
 +      # no joins - no collapse
 +      $attrs->{collapse} = 0;
 +    }
 +  }
 +
    # if both page and offset are specified, produce a combined offset
    # even though it doesn't make much sense, this is what pre 081xx has
    # been doing
@@@ -3326,7 -3330,7 +3327,7 @@@ attempting to use the accessor in an C<
  will fail miserably.
  
  To get around this limitation, you can supply literal SQL to your
- C<select> attibute that contains the C<AS alias> text, eg:
+ C<select> attribute that contains the C<AS alias> text, e.g.
  
    select => [\'myfield AS alias']
  
@@@ -3437,7 -3441,7 +3438,7 @@@ for a C<join> attribute in the above se
  C<prefetch> can be used with the following relationship types: C<belongs_to>,
  C<has_one> (or if you're using C<add_relationship>, any relationship declared
  with an accessor type of 'single' or 'filter'). A more complex example that
- prefetches an artists cds, the tracks on those cds, and the tags associted
+ prefetches an artists cds, the tracks on those cds, and the tags associated
  with that artist is given below (assuming many-to-many from artists to tags):
  
   my $rs = $schema->resultset('Artist')->search(
@@@ -3516,7 -3520,7 +3517,7 @@@ C<total_entries> on it
  
  =back
  
- Specifes the maximum number of rows for direct retrieval or the number of
+ Specifies the maximum number of rows for direct retrieval or the number of
  rows per page if the page attribute or method is used.
  
  =head2 offset
@@@ -44,7 -44,8 +44,8 @@@ sub new 
  
    $rs->throw_exception('column must be supplied') unless $column;
  
-   my $orig_attrs = $rs->_resolved_attrs_copy;
+   my $orig_attrs = $rs->_resolved_attrs;
+   my $alias = $rs->current_source_alias;
  
    # If $column can be found in the 'as' list of the parent resultset, use the
    # corresponding element of its 'select' list (to keep any custom column
    # analyze the order_by, and see if it is done over a function/nonexistentcolumn
    # if this is the case we will need to wrap a subquery since the result of RSC
    # *must* be a single column select
-   my %collist = map { $_ => 1 } ($rs->result_source->columns, $column);
+   my %collist = map 
+     { $_ => 1, ($_ =~ /\./) ? () : ( "$alias.$_" => 1 ) }
+     ($rs->result_source->columns, $column)
+   ;
    if (
      scalar grep
        { ! $collist{$_} }
        ( $rs->result_source->schema->storage->_parse_order_by ($orig_attrs->{order_by} ) ) 
    ) {
      # nuke the prefetch before collapsing to sql
      my $subq_rs = $rs->search;
      $subq_rs->{attrs}{join} = $subq_rs->_merge_attr( $subq_rs->{attrs}{join}, delete $subq_rs->{attrs}{prefetch} );
-     $new_parent_rs = $rs->result_source->resultset->search ( {}, {
-       alias => $alias,
-       from => [{
-         $alias => $subq_rs->as_query,
-         -alias => $alias,
-         -source_handle => $rs->result_source->handle,
-       }]
-     });
+     $new_parent_rs = $subq_rs->as_subselect_rs;
    }
  
    $new_parent_rs ||= $rs->search_rs;
@@@ -91,7 -86,7 +86,7 @@@
  
    # {collapse} would mean a has_many join was injected, which in turn means
    # we need to group *IF WE CAN* (only if the column in question is unique)
-   if (!$new_attrs->{group_by} && $orig_attrs->{collapse}) {
 -  if (!$orig_attrs->{group_by} && keys %{$orig_attrs->{collapse}}) {
++  if (!$orig_attrs->{group_by} && $orig_attrs->{collapse}) {
  
      # scan for a constraint that would contain our column only - that'd be proof
      # enough it is unique
@@@ -503,6 -503,16 +503,16 @@@ sub primary_columns 
    return @{shift->_primaries||[]};
  }
  
+ sub _pri_cols {
+   my $self = shift;
+   my @pcols = $self->primary_columns
+     or $self->throw_exception (sprintf(
+       'Operation requires a primary key to be declared on %s via set_primary_key',
+       ref $self,
+     ));
+   return @pcols;
+ }
  =head2 add_unique_constraint
  
  =over 4
@@@ -1378,7 -1388,7 +1388,7 @@@ sub _resolve_condition 
  # in the supplied relationships.
  
  sub _resolve_prefetch {
 -  my ($self, $pre, $alias, $alias_map, $order, $collapse, $pref_path) = @_;
 +  my ($self, $pre, $alias, $alias_map, $order, $pref_path) = @_;
    $pref_path ||= [];
  
    if (not defined $pre) {
    }
    elsif( ref $pre eq 'ARRAY' ) {
      return
 -      map { $self->_resolve_prefetch( $_, $alias, $alias_map, $order, $collapse, [ @$pref_path ] ) }
 +      map { $self->_resolve_prefetch( $_, $alias, $alias_map, $order, [ @$pref_path ] ) }
          @$pre;
    }
    elsif( ref $pre eq 'HASH' ) {
      my @ret =
      map {
 -      $self->_resolve_prefetch($_, $alias, $alias_map, $order, $collapse, [ @$pref_path ] ),
 +      $self->_resolve_prefetch($_, $alias, $alias_map, $order, [ @$pref_path ] ),
        $self->related_source($_)->_resolve_prefetch(
 -               $pre->{$_}, "${alias}.$_", $alias_map, $order, $collapse, [ @$pref_path, $_] )
 +               $pre->{$_}, "${alias}.$_", $alias_map, $order, [ @$pref_path, $_] )
      } keys %$pre;
      return @ret;
    }
          "Can't prefetch has_many ${pre} (join cond too complex)")
          unless ref($rel_info->{cond}) eq 'HASH';
        my $dots = @{[$as_prefix =~ m/\./g]} + 1; # +1 to match the ".${as_prefix}"
 -      if (my ($fail) = grep { @{[$_ =~ m/\./g]} == $dots }
 -                         keys %{$collapse}) {
 -        my ($last) = ($fail =~ /([^\.]+)$/);
 -        carp (
 -          "Prefetching multiple has_many rels ${last} and ${pre} "
 -          .(length($as_prefix)
 -            ? "at the same level (${as_prefix}) "
 -            : "at top level "
 -          )
 -          . 'will explode the number of row objects retrievable via ->next or ->all. '
 -          . 'Use at your own risk.'
 -        );
 -      }
 +
        #my @col = map { (/^self\.(.+)$/ ? ("${as_prefix}.$1") : ()); }
        #              values %{$rel_info->{cond}};
 -      $collapse->{".${as_prefix}${pre}"} = [ $rel_source->primary_columns ];
 -        # action at a distance. prepending the '.' allows simpler code
 -        # in ResultSet->_collapse_result
        my @key = map { (/^foreign\.(.+)$/ ? ($1) : ()); }
                      keys %{$rel_info->{cond}};
        my @ord = (ref($rel_info->{attrs}{order_by}) eq 'ARRAY'
                     ? @{$rel_info->{attrs}{order_by}}
 -   
 +
                  : (defined $rel_info->{attrs}{order_by}
                         ? ($rel_info->{attrs}{order_by})
                         : ()));
    }
  }
  
 +# Takes a hashref of $sth->fetchrow values keyed to the corresponding
 +# {as} dbic aliases, and splits it into a native columns hashref
 +# (as in $row->get_columns), followed by any non-native (prefetched)
 +# columns, presented in a nested structure resembling an HRI dump.
 +# The structure is constructed taking into account relationship metadata
 +# (single vs multi).
 +# The resulting arrayref resembles the arguments to ::Row::inflate_result
 +# For an example look at t/prefetch/_util.t
 +#
 +# The will collapse flag is for backwards compatibility only - if it is
 +# set, all relationship row-parts are returned as hashes, even if some
 +# of these relationships are has_many's
 +#
 +sub _parse_row {
 +    my ( $self, $row, $will_collapse ) = @_;
 +
 +    my ($me, $pref);
 +
 +    foreach my $column ( keys %$row ) {
 +        if ( $column =~ /^ ([^\.]+) \. (.*) $/x ) {
 +            $pref->{$1}{$2} = $row->{$column};
 +        }
 +        else {
 +            $me->{$column} = $row->{$column};
 +        }
 +    }
 +
 +    foreach my $rel ( keys %{$pref||{}} ) {
 +        my $rel_info = $self->relationship_info($rel);
 +
 +        $pref->{$rel} =
 +          $self->related_source($rel)->_parse_row( $pref->{$rel}, $will_collapse );
 +
 +        $pref->{$rel} = [ $pref->{$rel} ]
 +          if ( $will_collapse && $rel_info->{attrs}{accessor} eq 'multi' );
 +    }
 +
 +    return [ $me||{}, $pref||() ];
 +}
 +
  =head2 related_source
  
  =over 4
@@@ -1590,7 -1575,7 +1600,7 @@@ Creates a new ResultSource object.  No
    __PACKAGE__->column_info_from_storage(1);
  
  Enables the on-demand automatic loading of the above column
- metadata from storage as neccesary.  This is *deprecated*, and
+ metadata from storage as necessary.  This is *deprecated*, and
  should not be used.  It will be removed before 1.0.
  
  
@@@ -40,6 -40,7 +40,7 @@@ __PACKAGE__->sql_maker_class('DBIx::Cla
  # Each of these methods need _determine_driver called before itself
  # in order to function reliably. This is a purely DRY optimization
  my @rdbms_specific_methods = qw/
+   deployment_statements
    sqlt_type
    build_datetime_parser
    datetime_parser_type
@@@ -190,7 -191,7 +191,7 @@@ for most DBDs. See L</DBIx::Class and A
  In addition to the standard L<DBI|DBI/ATTRIBUTES_COMMON_TO_ALL_HANDLES>
  L<connection|DBI/Database_Handle_Attributes> attributes, DBIx::Class recognizes
  the following connection options. These options can be mixed in with your other
- L<DBI> connection attributes, or placed in a seperate hashref
+ L<DBI> connection attributes, or placed in a separate hashref
  (C<\%extra_attributes>) as shown above.
  
  Every time C<connect_info> is invoked, any previous settings for
@@@ -342,7 -343,7 +343,7 @@@ SQL Server you should use C<< quote_cha
  =item name_sep
  
  This only needs to be used in conjunction with C<quote_char>, and is used to
- specify the charecter that seperates elements (schemas, tables, columns) from
+ specify the character that separates elements (schemas, tables, columns) from
  each other. In most cases this is simply a C<.>.
  
  The consequences of not supplying this value is that L<SQL::Abstract>
@@@ -778,8 -779,8 +779,8 @@@ sub with_deferred_fk_checks 
  
  =back
  
- Verifies that the the current database handle is active and ready to execute
- an SQL statement (i.e. the connection did not get stale, server is still
+ Verifies that the current database handle is active and ready to execute
+ an SQL statement (e.g. the connection did not get stale, server is still
  answering, etc.) This method is used internally by L</dbh>.
  
  =cut
@@@ -1601,15 -1602,7 +1602,7 @@@ sub _subq_update_delete 
    my $rsrc = $rs->result_source;
  
    # quick check if we got a sane rs on our hands
-   my @pcols = $rsrc->primary_columns;
-   unless (@pcols) {
-     $self->throw_exception (
-       sprintf (
-         "You must declare primary key(s) on source '%s' (via set_primary_key) in order to update or delete complex resultsets",
-         $rsrc->source_name || $rsrc->from
-       )
-     );
-   }
+   my @pcols = $rsrc->_pri_cols;
  
    my $sel = $rs->_resolved_attrs->{select};
    $sel = [ $sel ] unless ref $sel eq 'ARRAY';
@@@ -1662,7 -1655,7 +1655,7 @@@ sub _per_row_update_delete 
    my ($rs, $op, $values) = @_;
  
    my $rsrc = $rs->result_source;
-   my @pcols = $rsrc->primary_columns;
+   my @pcols = $rsrc->_pri_cols;
  
    my $guard = $self->txn_scope_guard;
  
@@@ -1793,8 -1786,8 +1786,8 @@@ sub _select_args 
    # see if we need to tear the prefetch apart otherwise delegate the limiting to the
    # storage, unless software limit was requested
    if (
 -    #limited has_many
 -    ( $attrs->{rows} && keys %{$attrs->{collapse}} )
 +    # limited collapsing has_many
 +    ( $attrs->{rows} && $attrs->{collapse} )
         ||
      # limited prefetch with RNO subqueries
      (
@@@ -1896,7 -1889,33 +1889,33 @@@ sub _count_select 
  #
  sub _subq_count_select {
    my ($self, $source, $rs_attrs) = @_;
-   return $rs_attrs->{group_by} if $rs_attrs->{group_by};
+   if (my $groupby = $rs_attrs->{group_by}) {
+     my $avail_columns = $self->_resolve_column_info ($rs_attrs->{from});
+     my $sel_index;
+     for my $sel (@{$rs_attrs->{select}}) {
+       if (ref $sel eq 'HASH' and $sel->{-as}) {
+         $sel_index->{$sel->{-as}} = $sel;
+       }
+     }
+     my @selection;
+     for my $g_part (@$groupby) {
+       if (ref $g_part or $avail_columns->{$g_part}) {
+         push @selection, $g_part;
+       }
+       elsif ($sel_index->{$g_part}) {
+         push @selection, $sel_index->{$g_part};
+       }
+       else {
+         $self->throw_exception ("group_by criteria '$g_part' not contained within current resultset source(s)");
+       }
+     }
+     return \@selection;
+   }
  
    my @pcols = map { join '.', $rs_attrs->{alias}, $_ } ($source->primary_columns);
    return @pcols ? \@pcols : [ 1 ];
@@@ -2442,7 -2461,7 +2461,7 @@@ sub deploy 
      }
      $self->_query_end($line);
    };
-   my @statements = $self->deployment_statements($schema, $type, undef, $dir, { %{ $sqltargs || {} }, no_comments => 1 } );
+   my @statements = $schema->deployment_statements($type, undef, $dir, { %{ $sqltargs || {} }, no_comments => 1 } );
    if (@statements > 1) {
      foreach my $statement (@statements) {
        $deploy->( $statement );
@@@ -2530,8 -2549,8 +2549,8 @@@ queries
  This hook is to allow specific L<DBIx::Class::Storage> drivers to change the
  way these aliases are named.
  
- The default behavior is C<"$relname_$join_count" if $join_count > 1>, otherwise
- C<"$relname">.
+ The default behavior is C<< "$relname_$join_count" if $join_count > 1 >>,
+ otherwise C<"$relname">.
  
  =cut
  
diff --combined t/inflate/hri.t
@@@ -26,6 -26,9 +26,9 @@@ my $schema = DBICTest->init_schema()
  
      my $cd1 = $rs->find ({cdid => 1});
      is_deeply ( $cd1, $datahashref1, 'first/find return the same thing');
+     my $cd2 = $rs->search({ cdid => 1 })->single;
+     is_deeply ( $cd2, $datahashref1, 'first/search+single return the same thing');
  }
  
  sub check_cols_of {
@@@ -45,7 -48,7 +48,7 @@@
              my @dbic_reltable = $dbic_obj->$col;
              my @hashref_reltable = @{$datahashref->{$col}};
  
 -            is (scalar @dbic_reltable, scalar @hashref_reltable, 'number of related entries');
 +            is (scalar @hashref_reltable, scalar @dbic_reltable, 'number of related entries');
  
              # for my $index (0..scalar @hashref_reltable) {
              for my $index (0..scalar @dbic_reltable) {