Merge 'trunk' into 'prefetch'
Peter Rabbitson [Sun, 14 Feb 2010 10:46:07 +0000 (10:46 +0000)]
r8598@Thesaurus (orig r8585):  ribasushi | 2010-02-08 12:48:31 +0100
Release 0.08118
r8600@Thesaurus (orig r8587):  ribasushi | 2010-02-08 12:52:33 +0100
Bump trunk version
r8606@Thesaurus (orig r8593):  ribasushi | 2010-02-08 16:16:44 +0100
cheaper lookup
r8609@Thesaurus (orig r8596):  ribasushi | 2010-02-10 12:40:37 +0100
Consolidate last_insert_id handling with a fallback-attempt on DBI::last_insert_id
r8614@Thesaurus (orig r8601):  caelum | 2010-02-10 21:29:51 +0100
workaround for Moose bug affecting Replicated storage
r8615@Thesaurus (orig r8602):  caelum | 2010-02-10 21:40:07 +0100
revert Moose bug workaround, bump Moose dep for Replicated to 0.98
r8616@Thesaurus (orig r8603):  caelum | 2010-02-10 22:48:34 +0100
add a couple proxy methods to Replicated so it can run
r8628@Thesaurus (orig r8615):  caelum | 2010-02-11 11:35:01 +0100
 r21090@hlagh (orig r7836):  caelum | 2009-11-02 06:40:52 -0500
 new branch to fix unhandled methods in Storage::DBI::Replicated
 r21091@hlagh (orig r7837):  caelum | 2009-11-02 06:42:00 -0500
 add test to display unhandled methods
 r21092@hlagh (orig r7838):  caelum | 2009-11-02 06:55:34 -0500
 minor fix to last committed test
 r21093@hlagh (orig r7839):  caelum | 2009-11-02 09:26:00 -0500
 minor test code cleanup
 r23125@hlagh (orig r8607):  caelum | 2010-02-10 19:25:51 -0500
 add unimplemented Storage::DBI methods to ::DBI::Replicated
 r23130@hlagh (orig r8612):  ribasushi | 2010-02-11 05:12:48 -0500
 Podtesting exclusion

r8630@Thesaurus (orig r8617):  frew | 2010-02-11 11:45:54 +0100
Changes (from a while ago)
r8631@Thesaurus (orig r8618):  caelum | 2010-02-11 11:46:58 +0100
savepoints for SQLAnywhere
r8640@Thesaurus (orig r8627):  ribasushi | 2010-02-11 12:33:19 +0100
 r8424@Thesaurus (orig r8411):  ribasushi | 2010-01-22 11:19:40 +0100
 Chaining POC test

r8641@Thesaurus (orig r8628):  ribasushi | 2010-02-11 12:34:19 +0100
 r8426@Thesaurus (orig r8413):  ribasushi | 2010-01-22 11:35:15 +0100
 Moev failing regression test away from trunk

r8642@Thesaurus (orig r8629):  ribasushi | 2010-02-11 12:34:56 +0100

r8643@Thesaurus (orig r8630):  ribasushi | 2010-02-11 12:35:03 +0100
 r8507@Thesaurus (orig r8494):  frew | 2010-02-01 04:33:08 +0100
 small refactor to put select/as/+select/+as etc merging in it's own function

r8644@Thesaurus (orig r8631):  ribasushi | 2010-02-11 12:35:11 +0100
 r8514@Thesaurus (orig r8501):  frew | 2010-02-02 05:12:29 +0100
 revert actual changes from yesterday as per ribasushis advice

r8645@Thesaurus (orig r8632):  ribasushi | 2010-02-11 12:35:16 +0100
 r8522@Thesaurus (orig r8509):  frew | 2010-02-02 19:39:33 +0100
 delete +stuff if stuff exists

r8646@Thesaurus (orig r8633):  ribasushi | 2010-02-11 12:35:23 +0100
 r8534@Thesaurus (orig r8521):  frew | 2010-02-03 06:14:44 +0100
 change deletion/overriding to fix t/76

r8647@Thesaurus (orig r8634):  ribasushi | 2010-02-11 12:35:30 +0100
 r8535@Thesaurus (orig r8522):  frew | 2010-02-03 06:57:15 +0100
 some basic readability factorings (aka, fewer nested ternaries and long maps)

r8648@Thesaurus (orig r8635):  ribasushi | 2010-02-11 12:36:01 +0100
 r8558@Thesaurus (orig r8545):  frew | 2010-02-04 20:32:54 +0100
 fix incorrect test in t/76select.t and posit an incorrect solution

r8649@Thesaurus (orig r8636):  ribasushi | 2010-02-11 12:38:47 +0100

r8650@Thesaurus (orig r8637):  ribasushi | 2010-02-11 12:38:57 +0100
 r8578@Thesaurus (orig r8565):  ribasushi | 2010-02-05 19:11:09 +0100
 Should not be needed

r8651@Thesaurus (orig r8638):  ribasushi | 2010-02-11 12:39:03 +0100
 r8579@Thesaurus (orig r8566):  ribasushi | 2010-02-05 19:13:24 +0100
 SQLA now fixed

r8652@Thesaurus (orig r8639):  ribasushi | 2010-02-11 12:39:10 +0100
 r8624@Thesaurus (orig r8611):  ribasushi | 2010-02-11 10:31:08 +0100
 MOAR testing

r8653@Thesaurus (orig r8640):  ribasushi | 2010-02-11 12:39:17 +0100
 r8626@Thesaurus (orig r8613):  frew | 2010-02-11 11:16:30 +0100
 fix bad test

r8654@Thesaurus (orig r8641):  ribasushi | 2010-02-11 12:39:23 +0100
 r8627@Thesaurus (orig r8614):  frew | 2010-02-11 11:21:52 +0100
 fix t/76, break rsc tests

r8655@Thesaurus (orig r8642):  ribasushi | 2010-02-11 12:39:30 +0100
 r8632@Thesaurus (orig r8619):  frew | 2010-02-11 11:53:50 +0100
 fix incorrect test

r8656@Thesaurus (orig r8643):  ribasushi | 2010-02-11 12:39:35 +0100
 r8633@Thesaurus (orig r8620):  frew | 2010-02-11 11:54:49 +0100
 make t/76s and t/88 pass by deleting from the correct attr hash

r8657@Thesaurus (orig r8644):  ribasushi | 2010-02-11 12:39:40 +0100
 r8634@Thesaurus (orig r8621):  frew | 2010-02-11 11:55:41 +0100
 fix a test due to ordering issues

r8658@Thesaurus (orig r8645):  ribasushi | 2010-02-11 12:39:45 +0100
 r8635@Thesaurus (orig r8622):  frew | 2010-02-11 11:58:23 +0100
 this is why you run tests before you commit them.

r8659@Thesaurus (orig r8646):  ribasushi | 2010-02-11 12:39:51 +0100
 r8636@Thesaurus (orig r8623):  frew | 2010-02-11 12:00:59 +0100
 fix another ordering issue

r8660@Thesaurus (orig r8647):  ribasushi | 2010-02-11 12:39:57 +0100
 r8637@Thesaurus (orig r8624):  frew | 2010-02-11 12:11:31 +0100
 fix for search/select_chains

r8661@Thesaurus (orig r8648):  ribasushi | 2010-02-11 12:40:03 +0100

r8662@Thesaurus (orig r8649):  caelum | 2010-02-11 12:40:07 +0100
test nanosecond precision for SQLAnywhere
r8663@Thesaurus (orig r8650):  ribasushi | 2010-02-11 12:40:09 +0100
 r8639@Thesaurus (orig r8626):  ribasushi | 2010-02-11 12:33:03 +0100
 Changes and small ommission

r8666@Thesaurus (orig r8653):  ribasushi | 2010-02-11 18:16:45 +0100
Changes
r8674@Thesaurus (orig r8661):  ribasushi | 2010-02-12 09:12:45 +0100
Fix moose dep
r8680@Thesaurus (orig r8667):  dew | 2010-02-12 18:05:11 +0100
Add is_ordered to DBIC::ResultSet
r8688@Thesaurus (orig r8675):  ribasushi | 2010-02-13 09:36:29 +0100
 r8667@Thesaurus (orig r8654):  ribasushi | 2010-02-11 18:17:35 +0100
 Try a dep-handling idea
 r8675@Thesaurus (orig r8662):  ribasushi | 2010-02-12 12:46:11 +0100
 Move optional deps out of the Makefile
 r8676@Thesaurus (orig r8663):  ribasushi | 2010-02-12 13:40:53 +0100
 Support methods to verify group dependencies
 r8677@Thesaurus (orig r8664):  ribasushi | 2010-02-12 13:45:18 +0100
 Move sqlt dephandling to Optional::Deps
 r8679@Thesaurus (orig r8666):  ribasushi | 2010-02-12 14:03:17 +0100
 Move replicated to Opt::Deps
 r8684@Thesaurus (orig r8671):  ribasushi | 2010-02-13 02:47:52 +0100
 Auto-POD for Optional Deps
 r8685@Thesaurus (orig r8672):  ribasushi | 2010-02-13 02:53:20 +0100
 Privatize the full list method
 r8686@Thesaurus (orig r8673):  ribasushi | 2010-02-13 02:59:51 +0100
 Scary warning
 r8687@Thesaurus (orig r8674):  ribasushi | 2010-02-13 09:35:01 +0100
 Changes

r8691@Thesaurus (orig r8678):  ribasushi | 2010-02-13 10:07:15 +0100
Autogen comment for Dependencies.pod
r8692@Thesaurus (orig r8679):  ribasushi | 2010-02-13 10:11:24 +0100
Ask for newer M::I
r8698@Thesaurus (orig r8685):  ribasushi | 2010-02-13 11:11:10 +0100
Add author/license to pod
r8699@Thesaurus (orig r8686):  arcanez | 2010-02-13 13:43:22 +0100
fix typo per nuba on irc
r8705@Thesaurus (orig r8692):  ribasushi | 2010-02-13 15:15:33 +0100
 r8001@Thesaurus (orig r7989):  goraxe | 2009-11-30 01:14:47 +0100
 Branch for dbicadmin script refactor

 r8003@Thesaurus (orig r7991):  goraxe | 2009-11-30 01:26:39 +0100
 add DBIx::Class::Admin
 r8024@Thesaurus (orig r8012):  goraxe | 2009-12-02 22:49:27 +0100
 get deployment tests to pass
 r8025@Thesaurus (orig r8013):  goraxe | 2009-12-02 22:50:42 +0100
 get deployment tests to pass
 r8026@Thesaurus (orig r8014):  goraxe | 2009-12-02 23:52:40 +0100
 all ddl tests now pass
 r8083@Thesaurus (orig r8071):  goraxe | 2009-12-12 17:01:11 +0100
 add quite attribute to DBIx::Class admin
 r8086@Thesaurus (orig r8074):  goraxe | 2009-12-12 17:36:58 +0100
 add tests for data manipulation ported from 89dbicadmin.t
 r8088@Thesaurus (orig r8076):  goraxe | 2009-12-12 17:38:07 +0100
 add sleep 1 to t/admin/02ddl.t so insert into upgrade table does not happen too quickly
 r8089@Thesaurus (orig r8077):  goraxe | 2009-12-12 17:40:33 +0100
 update DBIx::Class::Admin data manip functions to pass the test
 r8095@Thesaurus (orig r8083):  goraxe | 2009-12-12 19:36:22 +0100
 change passing of preversion to be a parameter
 r8096@Thesaurus (orig r8084):  goraxe | 2009-12-12 19:38:26 +0100
 add some pod to DBIx::Class::Admin
 r8103@Thesaurus (orig r8091):  goraxe | 2009-12-12 22:08:55 +0100
 some changes to make DBIx::Class::Admin more compatible with dbicadmin interface
 r8104@Thesaurus (orig r8092):  goraxe | 2009-12-12 22:09:39 +0100
 commit refactored dbicadmin script and very minor changes to its existing test suite
 r8107@Thesaurus (orig r8095):  goraxe | 2009-12-12 22:34:35 +0100
 add compatability for --op for dbicadmin, revert test suite
 r8127@Thesaurus (orig r8115):  goraxe | 2009-12-15 22:14:20 +0100
 dep check to end of module
 r8128@Thesaurus (orig r8116):  goraxe | 2009-12-15 23:15:25 +0100
 add namespace::autoclean to DBIx::Class::Admin
 r8129@Thesaurus (orig r8117):  goraxe | 2009-12-15 23:16:00 +0100
 update test suite to skip if cannot load DBIx::Class::Admin
 r8130@Thesaurus (orig r8118):  goraxe | 2009-12-15 23:18:35 +0100
 add deps check for 89dbicadmin.t
 r8131@Thesaurus (orig r8119):  goraxe | 2009-12-15 23:19:01 +0100
 include deps for dbicadmin DBIx::Class::Admin to Makefile.PL
 r8149@Thesaurus (orig r8137):  goraxe | 2009-12-17 23:21:50 +0100
 use DBICTest::_database over creating a schema object to steal conn info
 r8338@Thesaurus (orig r8326):  goraxe | 2010-01-15 19:00:17 +0100
 change white space to not be tabs
 r8339@Thesaurus (orig r8327):  goraxe | 2010-01-15 19:10:42 +0100
 remove Module::Load from test suite
 r8358@Thesaurus (orig r8346):  ribasushi | 2010-01-17 17:52:10 +0100
 Real detabify
 r8359@Thesaurus (orig r8347):  ribasushi | 2010-01-17 18:01:53 +0100
 Fix POD (spacing matters)
 r8360@Thesaurus (orig r8348):  ribasushi | 2010-01-17 21:57:53 +0100
 More detabification
 r8361@Thesaurus (orig r8349):  ribasushi | 2010-01-17 22:33:12 +0100
 Test cleanup
 r8362@Thesaurus (orig r8350):  ribasushi | 2010-01-17 22:41:11 +0100
 More tets cleanup
 r8363@Thesaurus (orig r8351):  ribasushi | 2010-01-17 22:43:57 +0100
 And more cleanup
 r8364@Thesaurus (orig r8352):  ribasushi | 2010-01-17 22:51:21 +0100
 Disallow mucking with INC
 r8365@Thesaurus (orig r8353):  ribasushi | 2010-01-17 23:23:15 +0100
 More cleanup
 r8366@Thesaurus (orig r8354):  ribasushi | 2010-01-17 23:27:49 +0100
 Add lib path to ENV so that $^X can see it
 r8367@Thesaurus (orig r8355):  ribasushi | 2010-01-17 23:33:10 +0100
 Move script-test
 r8368@Thesaurus (orig r8356):  goraxe | 2010-01-17 23:35:03 +0100
 change warns/dies -> carp/throw_exception
 r8369@Thesaurus (orig r8357):  goraxe | 2010-01-17 23:53:54 +0100
 add goraxe to contributors
 r8370@Thesaurus (orig r8358):  goraxe | 2010-01-17 23:54:15 +0100
 remove comment headers
 r8404@Thesaurus (orig r8391):  caelum | 2010-01-20 20:54:29 +0100
 minor fixups
 r8405@Thesaurus (orig r8392):  goraxe | 2010-01-20 21:13:24 +0100
 add private types to coerce
 r8406@Thesaurus (orig r8393):  goraxe | 2010-01-20 21:17:19 +0100
 remove un-needed coerce from schema_class of type Str
 r8411@Thesaurus (orig r8398):  caelum | 2010-01-21 23:36:25 +0100
 minor documentation updates
 r8436@Thesaurus (orig r8423):  caelum | 2010-01-25 02:56:30 +0100
 this code never runs anyway
 r8440@Thesaurus (orig r8427):  caelum | 2010-01-26 14:05:53 +0100
 prefer JSON::DWIW for barekey support
 r8693@Thesaurus (orig r8680):  ribasushi | 2010-02-13 10:27:18 +0100
 dbicadmin dependencies
 r8694@Thesaurus (orig r8681):  ribasushi | 2010-02-13 10:28:04 +0100
 Some cleaup, make use of Text::CSV
 r8695@Thesaurus (orig r8682):  ribasushi | 2010-02-13 10:34:19 +0100
 We use Try::Tiny in a single spot, not grounds for inlusion in deps
 r8696@Thesaurus (orig r8683):  ribasushi | 2010-02-13 10:37:30 +0100
 POD section
 r8697@Thesaurus (orig r8684):  ribasushi | 2010-02-13 11:05:17 +0100
 Switch tests to Optional::Deps
 r8700@Thesaurus (orig r8687):  ribasushi | 2010-02-13 14:32:50 +0100
 Switch Admin/dbicadmin to Opt::Deps
 r8702@Thesaurus (orig r8689):  ribasushi | 2010-02-13 14:39:24 +0100
 JSON dep is needed for Admin.pm itself
 r8703@Thesaurus (orig r8690):  ribasushi | 2010-02-13 15:06:28 +0100
 Test fixes
 r8704@Thesaurus (orig r8691):  ribasushi | 2010-02-13 15:13:31 +0100
 Changes

r8707@Thesaurus (orig r8694):  ribasushi | 2010-02-13 16:37:57 +0100
Test for optional deps manager
r8710@Thesaurus (orig r8697):  caelum | 2010-02-14 05:22:03 +0100
add doc on maximum cursors for SQLAnywhere
r8711@Thesaurus (orig r8698):  ribasushi | 2010-02-14 09:23:09 +0100
Cleanup dependencies / Admin inheritance
r8712@Thesaurus (orig r8699):  ribasushi | 2010-02-14 09:28:29 +0100
Some formatting
r8715@Thesaurus (orig r8702):  ribasushi | 2010-02-14 10:46:51 +0100
This is Moose, so use CMOP

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

@@@ -291,10 -291,15 +291,15 @@@ sub search_rs 
      $rows = $self->get_cache;
    }
  
+   # reset the selector list
+   if (List::Util::first { exists $attrs->{$_} } qw{columns select as}) {
+      delete @{$our_attrs}{qw{select as columns +select +as +columns include_columns}};
+   }
    my $new_attrs = { %{$our_attrs}, %{$attrs} };
  
    # merge new attrs into inherited
-   foreach my $key (qw/join prefetch +select +as bind/) {
+   foreach my $key (qw/join prefetch +select +as +columns include_columns bind/) {
      next unless exists $attrs->{$key};
      $new_attrs->{$key} = $self->_merge_attr($our_attrs->{$key}, $attrs->{$key});
    }
    }
  
    my $rs = (ref $self)->new($self->result_source, $new_attrs);
-   if ($rows) {
-     $rs->set_cache($rows);
-   }
+   $rs->set_cache($rows) if ($rows);
    return $rs;
  }
  
@@@ -519,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 databse that would satisfy the
      # relationship
    }
    else {
    }
  
    # Run the query
-   my $rs = $self->search ($query, $attrs);
+   my $rs = $self->search ($query, {result_class => $self->result_class, %$attrs});
    if (keys %{$rs->_resolved_attrs->{collapse}}) {
      my $row = $rs->next;
      carp "Query returned more than one row" if $rs->next;
@@@ -971,104 -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}||{}};
 +# 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.
 +
 +# "$rows" is a bit misleading. In the end, there should only be one
 +# element in this arrayref. 
  
 -  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->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 = keys %{ $self->{_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
@@@ -1204,18 -1232,13 +1208,13 @@@ sub _count_rs 
  
    my $tmp_attrs = { %$attrs };
  
-   # take off any limits, record_filter is cdbi, and no point of ordering a count 
+   # take off any limits, record_filter is cdbi, and no point of ordering a count
    delete $tmp_attrs->{$_} for (qw/select as rows offset order_by record_filter/);
  
    # overwrite the selector (supplied by the storage)
    $tmp_attrs->{select} = $rsrc->storage->_count_select ($rsrc, $tmp_attrs);
    $tmp_attrs->{as} = 'count';
  
-   # read the comment on top of the actual function to see what this does
-   $tmp_attrs->{from} = $self->_switch_to_inner_join_if_needed (
-     $tmp_attrs->{from}, $tmp_attrs->{alias}
-   );
    my $tmp_rs = $rsrc->resultset_class->new($rsrc, $tmp_attrs)->get_column ('count');
  
    return $tmp_rs;
@@@ -1235,20 -1258,16 +1234,16 @@@ sub _count_subq_rs 
    # extra selectors do not go in the subquery and there is no point of ordering it
    delete $sub_attrs->{$_} for qw/collapse select _prefetch_select as order_by/;
  
-   # if we 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 ( keys %{$attrs->{collapse}} ) {
+   # 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 ( keys %{$attrs->{collapse}}  ) {
      $sub_attrs->{group_by} = [ map { "$attrs->{alias}.$_" } ($rsrc->primary_columns) ]
    }
  
    $sub_attrs->{select} = $rsrc->storage->_subq_count_select ($rsrc, $sub_attrs);
  
-   # read the comment on top of the actual function to see what this does
-   $sub_attrs->{from} = $self->_switch_to_inner_join_if_needed (
-     $sub_attrs->{from}, $sub_attrs->{alias}
-   );
-   # this is so that ordering can be thrown away in things like Top limit
+   # this is so that the query can be simplified e.g.
+   # * ordering can be thrown away in things like Top limit
    $sub_attrs->{-for_count_only} = 1;
  
    my $sub_rs = $rsrc->resultset_class->new ($rsrc, $sub_attrs);
    return $self->_count_rs ($attrs);
  }
  
- # The DBIC relationship chaining implementation is pretty simple - every
- # new related_relationship is pushed onto the {from} stack, and the {select}
- # window simply slides further in. This means that when we count somewhere
- # in the middle, we got to make sure that everything in the join chain is an
- # actual inner join, otherwise the count will come back with unpredictable
- # results (a resultset may be generated with _some_ rows regardless of if
- # the relation which the $rs currently selects has rows or not). E.g.
- # $artist_rs->cds->count - normally generates:
- # SELECT COUNT( * ) FROM artist me LEFT JOIN cd cds ON cds.artist = me.artistid
- # which actually returns the number of artists * (number of cds || 1)
- #
- # So what we do here is crawl {from}, determine if the current alias is at
- # the top of the stack, and if not - make sure the chain is inner-joined down
- # to the root.
- #
- sub _switch_to_inner_join_if_needed {
-   my ($self, $from, $alias) = @_;
-   # subqueries and other oddness is naturally not supported
-   return $from if (
-     ref $from ne 'ARRAY'
-       ||
-     @$from <= 1
-       ||
-     ref $from->[0] ne 'HASH'
-       ||
-     ! $from->[0]{-alias}
-       ||
-     $from->[0]{-alias} eq $alias
-   );
-   my $switch_branch;
-   JOINSCAN:
-   for my $j (@{$from}[1 .. $#$from]) {
-     if ($j->[0]{-alias} eq $alias) {
-       $switch_branch = $j->[0]{-join_path};
-       last JOINSCAN;
-     }
-   }
-   # something else went wrong
-   return $from unless $switch_branch;
-   # So it looks like we will have to switch some stuff around.
-   # local() is useless here as we will be leaving the scope
-   # anyway, and deep cloning is just too fucking expensive
-   # So replace the inner hashref manually
-   my @new_from = ($from->[0]);
-   my $sw_idx = { map { $_ => 1 } @$switch_branch };
-   for my $j (@{$from}[1 .. $#$from]) {
-     my $jalias = $j->[0]{-alias};
-     if ($sw_idx->{$jalias}) {
-       my %attrs = %{$j->[0]};
-       delete $attrs{-join_type};
-       push @new_from, [
-         \%attrs,
-         @{$j}[ 1 .. $#$j ],
-       ];
-     }
-     else {
-       push @new_from, $j;
-     }
-   }
-   return \@new_from;
- }
  sub _bool {
    return 1;
  }
@@@ -1459,8 -1407,12 +1383,12 @@@ sub _rs_update_delete 
  
    my $rsrc = $self->result_source;
  
+   # if a condition exists we need to strip all table qualifiers
+   # if this is not possible we'll force a subquery below
+   my $cond = $rsrc->schema->storage->_strip_cond_qualifiers ($self->{cond});
    my $needs_group_by_subq = $self->_has_resolved_attr (qw/collapse group_by -join/);
-   my $needs_subq = $self->_has_resolved_attr (qw/row offset/);
+   my $needs_subq = $needs_group_by_subq || (not defined $cond) || $self->_has_resolved_attr(qw/row offset/);
  
    if ($needs_group_by_subq or $needs_subq) {
  
      return $rsrc->storage->$op(
        $rsrc,
        $op eq 'update' ? $values : (),
-       $self->_cond_for_update_delete,
+       $cond,
      );
    }
  }
  
- # _cond_for_update_delete
- #
- # update/delete require the condition to be modified to handle
- # the differing SQL syntax available.  This transforms the $self->{cond}
- # appropriately, returning the new condition.
- sub _cond_for_update_delete {
-   my ($self, $full_cond) = @_;
-   my $cond = {};
-   $full_cond ||= $self->{cond};
-   # No-op. No condition, we're updating/deleting everything
-   return $cond unless ref $full_cond;
-   if (ref $full_cond eq 'ARRAY') {
-     $cond = [
-       map {
-         my %hash;
-         foreach my $key (keys %{$_}) {
-           $key =~ /([^.]+)$/;
-           $hash{$1} = $_->{$key};
-         }
-         \%hash;
-       } @{$full_cond}
-     ];
-   }
-   elsif (ref $full_cond eq 'HASH') {
-     if ((keys %{$full_cond})[0] eq '-and') {
-       $cond->{-and} = [];
-       my @cond = @{$full_cond->{-and}};
-        for (my $i = 0; $i < @cond; $i++) {
-         my $entry = $cond[$i];
-         my $hash;
-         if (ref $entry eq 'HASH') {
-           $hash = $self->_cond_for_update_delete($entry);
-         }
-         else {
-           $entry =~ /([^.]+)$/;
-           $hash->{$1} = $cond[++$i];
-         }
-         push @{$cond->{-and}}, $hash;
-       }
-     }
-     else {
-       foreach my $key (keys %{$full_cond}) {
-         $key =~ /([^.]+)$/;
-         $cond->{$1} = $full_cond->{$key};
-       }
-     }
-   }
-   else {
-     $self->throw_exception("Can't update/delete on resultset with condition unless hash or array");
-   }
-   return $cond;
- }
  =head2 update
  
  =over 4
@@@ -1746,10 -1639,10 +1615,10 @@@ values
  =cut
  
  sub populate {
-   my $self = shift @_;
-   my $data = ref $_[0][0] eq 'HASH'
-     ? $_[0] : ref $_[0][0] eq 'ARRAY' ? $self->_normalize_populate_args($_[0]) :
-     $self->throw_exception('Populate expects an arrayref of hashes or arrayref of arrayrefs');
+   my $self = shift;
+   # cruft placed in standalone method
+   my $data = $self->_normalize_populate_args(@_);
  
    if(defined wantarray) {
      my @created;
      }
      return wantarray ? @created : \@created;
    } else {
-     my ($first, @rest) = @$data;
+     my $first = $data->[0];
+     # if a column is a registered relationship, and is a non-blessed hash/array, consider
+     # it relationship data
+     my (@rels, @columns);
+     for (keys %$first) {
+       my $ref = ref $first->{$_};
+       $self->result_source->has_relationship($_) && ($ref eq 'ARRAY' or $ref eq 'HASH')
+         ? push @rels, $_
+         : push @columns, $_
+       ;
+     }
  
-     my @names = grep {!ref $first->{$_}} keys %$first;
-     my @rels = grep { $self->result_source->has_relationship($_) } keys %$first;
      my @pks = $self->result_source->primary_columns;
  
      ## do the belongs_to relationships
          delete $data->[$index]->{$rel};
          $data->[$index] = {%{$data->[$index]}, %$related};
  
-         push @names, keys %$related if $index == 0;
+         push @columns, keys %$related if $index == 0;
        }
      }
  
-     ## do bulk insert on current row
-     my @values = map { [ @$_{@names} ] } @$data;
+     ## inherit the data locked in the conditions of the resultset
+     my ($rs_data) = $self->_merge_cond_with_data({});
+     delete @{$rs_data}{@columns};
+     my @inherit_cols = keys %$rs_data;
+     my @inherit_data = values %$rs_data;
  
+     ## do bulk insert on current row
      $self->result_source->storage->insert_bulk(
        $self->result_source,
-       \@names,
-       \@values,
+       [@columns, @inherit_cols],
+       [ map { [ @$_{@columns}, @inherit_data ] } @$data ],
      );
  
      ## do the has_many relationships
        foreach my $rel (@rels) {
          next unless $item->{$rel} && ref $item->{$rel} eq "ARRAY";
  
-         my $parent = $self->find(map {{$_=>$item->{$_}} } @pks)
+         my $parent = $self->find({map { $_ => $item->{$_} } @pks})
       || $self->throw_exception('Cannot find the relating object.');
  
          my $child = $parent->$rel;
    }
  }
  
- =head2 _normalize_populate_args ($args)
- Private method used by L</populate> to normalize its incoming arguments.  Factored
- out in case you want to subclass and accept new argument structures to the
- L</populate> method.
- =cut
  
+ # populate() argumnets went over several incarnations
+ # What we ultimately support is AoH
  sub _normalize_populate_args {
-   my ($self, $data) = @_;
-   my @names = @{shift(@$data)};
-   my @results_to_create;
-   foreach my $datum (@$data) {
-     my %result_to_create;
-     foreach my $index (0..$#names) {
-       $result_to_create{$names[$index]} = $$datum[$index];
+   my ($self, $arg) = @_;
+   if (ref $arg eq 'ARRAY') {
+     if (ref $arg->[0] eq 'HASH') {
+       return $arg;
+     }
+     elsif (ref $arg->[0] eq 'ARRAY') {
+       my @ret;
+       my @colnames = @{$arg->[0]};
+       foreach my $values (@{$arg}[1 .. $#$arg]) {
+         push @ret, { map { $colnames[$_] => $values->[$_] } (0 .. $#colnames) };
+       }
+       return \@ret;
      }
-     push @results_to_create, \%result_to_create;
    }
-   return \@results_to_create;
+   $self->throw_exception('Populate expects an arrayref of hashrefs or arrayref of arrayrefs');
  }
  
  =head2 pager
@@@ -1937,46 -1844,66 +1820,66 @@@ sub new_result 
    $self->throw_exception( "new_result needs a hash" )
      unless (ref $values eq 'HASH');
  
-   my %new;
+   my ($merged_cond, $cols_from_relations) = $self->_merge_cond_with_data($values);
+   my %new = (
+     %$merged_cond,
+     @$cols_from_relations
+       ? (-cols_from_relations => $cols_from_relations)
+       : (),
+     -source_handle => $self->_source_handle,
+     -result_source => $self->result_source, # DO NOT REMOVE THIS, REQUIRED
+   );
+   return $self->result_class->new(\%new);
+ }
+ # _merge_cond_with_data
+ #
+ # Takes a simple hash of K/V data and returns its copy merged with the
+ # condition already present on the resultset. Additionally returns an
+ # arrayref of value/condition names, which were inferred from related
+ # objects (this is needed for in-memory related objects)
+ sub _merge_cond_with_data {
+   my ($self, $data) = @_;
+   my (%new_data, @cols_from_relations);
    my $alias = $self->{attrs}{alias};
  
-   if (
-     defined $self->{cond}
-     && $self->{cond} eq $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION
-   ) {
-     %new = %{ $self->{attrs}{related_objects} || {} };  # nothing might have been inserted yet
-     $new{-from_resultset} = [ keys %new ] if keys %new;
-   } else {
+   if (! defined $self->{cond}) {
+     # just massage $data below
+   }
+   elsif ($self->{cond} eq $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION) {
+     %new_data = %{ $self->{attrs}{related_objects} || {} };  # nothing might have been inserted yet
+     @cols_from_relations = keys %new_data;
+   }
+   elsif (ref $self->{cond} ne 'HASH') {
      $self->throw_exception(
-       "Can't abstract implicit construct, condition not a hash"
-     ) if ($self->{cond} && !(ref $self->{cond} eq 'HASH'));
-     my $collapsed_cond = (
-       $self->{cond}
-         ? $self->_collapse_cond($self->{cond})
-         : {}
+       "Can't abstract implicit construct, resultset condition not a hash"
      );
+   }
+   else {
      # precendence must be given to passed values over values inherited from
      # the cond, so the order here is important.
-     my %implied =  %{$self->_remove_alias($collapsed_cond, $alias)};
-     while( my($col,$value) = each %implied ){
-       if(ref($value) eq 'HASH' && keys(%$value) && (keys %$value)[0] eq '='){
-         $new{$col} = $value->{'='};
+     my $collapsed_cond = $self->_collapse_cond($self->{cond});
+     my %implied = %{$self->_remove_alias($collapsed_cond, $alias)};
+     while ( my($col, $value) = each %implied ) {
+       if (ref($value) eq 'HASH' && keys(%$value) && (keys %$value)[0] eq '=') {
+         $new_data{$col} = $value->{'='};
          next;
        }
-       $new{$col} = $value if $self->_is_deterministic_value($value);
+       $new_data{$col} = $value if $self->_is_deterministic_value($value);
      }
    }
  
-   %new = (
-     %new,
-     %{ $self->_remove_alias($values, $alias) },
-     -source_handle => $self->_source_handle,
-     -result_source => $self->result_source, # DO NOT REMOVE THIS, REQUIRED
+   %new_data = (
+     %new_data,
+     %{ $self->_remove_alias($data, $alias) },
    );
  
-   return $self->result_class->new(\%new);
+   return (\%new_data, \@cols_from_relations);
  }
  
  # _is_deterministic_value
@@@ -2101,7 -2028,7 +2004,7 @@@ sub _remove_alias 
    return \%unaliased;
  }
  
- =head2 as_query (EXPERIMENTAL)
+ =head2 as_query
  
  =over 4
  
@@@ -2115,8 -2042,6 +2018,6 @@@ Returns the SQL query and bind vars ass
  
  This is generally used as the RHS for a subquery.
  
- B<NOTE>: This feature is still experimental.
  =cut
  
  sub as_query {
@@@ -2528,6 -2453,40 +2429,40 @@@ sub clear_cache 
    shift->set_cache(undef);
  }
  
+ =head2 is_paged
+ =over 4
+ =item Arguments: none
+ =item Return Value: true, if the resultset has been paginated
+ =back
+ =cut
+ sub is_paged {
+   my ($self) = @_;
+   return !!$self->{attrs}{page};
+ }
+ =head2 is_ordered
+ =over 4
+ =item Arguments: none
+ =item Return Value: true, if the resultset has been ordered with C<order_by>.
+ =back
+ =cut
+ sub is_ordered {
+   my ($self) = @_;
+   return scalar $self->result_source->storage->_parse_order_by($self->{attrs}{order_by});
+ }
  =head2 related_resultset
  
  =over 4
@@@ -2549,21 -2508,30 +2484,30 @@@ sub related_resultset 
  
    $self->{related_resultsets} ||= {};
    return $self->{related_resultsets}{$rel} ||= do {
-     my $rel_info = $self->result_source->relationship_info($rel);
+     my $rsrc = $self->result_source;
+     my $rel_info = $rsrc->relationship_info($rel);
  
      $self->throw_exception(
-       "search_related: result source '" . $self->result_source->source_name .
+       "search_related: result source '" . $rsrc->source_name .
          "' has no such relationship $rel")
        unless $rel_info;
  
-     my ($from,$seen) = $self->_chain_relationship($rel);
+     my $attrs = $self->_chain_relationship($rel);
+     my $join_count = $attrs->{seen_join}{$rel};
+     my $alias = $self->result_source->storage
+         ->relname_to_table_alias($rel, $join_count);
+     # since this is search_related, and we already slid the select window inwards
+     # (the select/as attrs were deleted in the beginning), we need to flip all
+     # left joins to inner, so we get the expected results
+     # read the comment on top of the actual function to see what this does
+     $attrs->{from} = $rsrc->schema->storage->_straight_join_to_node ($attrs->{from}, $alias);
  
-     my $join_count = $seen->{$rel};
-     my $alias = ($join_count > 1 ? join('_', $rel, $join_count) : $rel);
  
      #XXX - temp fix for result_class bug. There likely is a more elegant fix -groditi
-     my %attrs = %{$self->{attrs}||{}};
-     delete @attrs{qw(result_class alias)};
+     delete @{$attrs}{qw(result_class alias)};
  
      my $new_cache;
  
        }
      }
  
-     my $rel_source = $self->result_source->related_source($rel);
+     my $rel_source = $rsrc->related_source($rel);
  
      my $new = do {
  
        # to work sanely (e.g. RestrictWithObject wants to be able to add
        # extra query restrictions, and these may need to be $alias.)
  
-       my $attrs = $rel_source->resultset_attributes;
-       local $attrs->{alias} = $alias;
+       my $rel_attrs = $rel_source->resultset_attributes;
+       local $rel_attrs->{alias} = $alias;
  
        $rel_source->resultset
                   ->search_rs(
                       undef, {
-                        %attrs,
-                        join => undef,
-                        prefetch => undef,
-                        select => undef,
-                        as => undef,
-                        where => $self->{cond},
-                        seen_join => $seen,
-                        from => $from,
+                        %$attrs,
+                        where => $attrs->{where},
                     });
      };
      $new->set_cache($new_cache) if $new_cache;
@@@ -2648,6 -2610,68 +2586,68 @@@ sub current_source_alias 
    return ($self->{attrs} || {})->{alias} || 'me';
  }
  
+ =head2 as_subselect_rs
+ =over 4
+ =item Arguments: none
+ =item Return Value: $resultset
+ =back
+ Act as a barrier to SQL symbols.  The resultset provided will be made into a
+ "virtual view" by including it as a subquery within the from clause.  From this
+ point on, any joined tables are inaccessible to ->search on the resultset (as if
+ it were simply where-filtered without joins).  For example:
+  my $rs = $schema->resultset('Bar')->search({'x.name' => 'abc'},{ join => 'x' });
+  # 'x' now pollutes the query namespace
+  # So the following works as expected
+  my $ok_rs = $rs->search({'x.other' => 1});
+  # But this doesn't: instead of finding a 'Bar' related to two x rows (abc and
+  # def) we look for one row with contradictory terms and join in another table
+  # (aliased 'x_2') which we never use
+  my $broken_rs = $rs->search({'x.name' => 'def'});
+  my $rs2 = $rs->as_subselect_rs;
+  # doesn't work - 'x' is no longer accessible in $rs2, having been sealed away
+  my $not_joined_rs = $rs2->search({'x.other' => 1});
+  # works as expected: finds a 'table' row related to two x rows (abc and def)
+  my $correctly_joined_rs = $rs2->search({'x.name' => 'def'});
+ Another example of when one might use this would be to select a subset of
+ columns in a group by clause:
+  my $rs = $schema->resultset('Bar')->search(undef, {
+    group_by => [qw{ id foo_id baz_id }],
+  })->as_subselect_rs->search(undef, {
+    columns => [qw{ id foo_id }]
+  });
+ In the above example normally columns would have to be equal to the group by,
+ but because we isolated the group by into a subselect the above works.
+ =cut
+ sub as_subselect_rs {
+    my $self = shift;
+    return $self->result_source->resultset->search( undef, {
+       alias => $self->current_source_alias,
+       from => [{
+             $self->current_source_alias => $self->as_query,
+             -alias         => $self->current_source_alias,
+             -source_handle => $self->result_source->handle,
+          }]
+    });
+ }
  # This code is called by search_related, and makes sure there
  # is clear separation between the joins before, during, and
  # after the relationship. This information is needed later
  # with a relation_chain_depth less than the depth of the
  # current prefetch is not considered)
  #
- # The increments happen in 1/2s to make it easier to correlate the
- # join depth with the join path. An integer means a relationship
- # specified via a search_related, whereas a fraction means an added
- # join/prefetch via attributes
+ # The increments happen twice per join. An even number means a
+ # relationship specified via a search_related, whereas an odd
+ # number indicates a join/prefetch added via attributes
+ #
+ # Also this code will wrap the current resultset (the one we
+ # chain to) in a subselect IFF it contains limiting attributes
  sub _chain_relationship {
    my ($self, $rel) = @_;
    my $source = $self->result_source;
-   my $attrs = $self->{attrs};
+   my $attrs = { %{$self->{attrs}||{}} };
  
-   my $from = [ @{
-       $attrs->{from}
-         ||
-       [{
-         -source_handle => $source->handle,
-         -alias => $attrs->{alias},
-         $attrs->{alias} => $source->from,
-       }]
-   }];
+   # we need to take the prefetch the attrs into account before we
+   # ->_resolve_join as otherwise they get lost - captainL
+   my $join = $self->_merge_attr( $attrs->{join}, $attrs->{prefetch} );
  
-   my $seen = { %{$attrs->{seen_join} || {} } };
-   my $jpath = ($attrs->{seen_join} && keys %{$attrs->{seen_join}}) 
-     ? $from->[-1][0]{-join_path} 
-     : [];
+   delete @{$attrs}{qw/join prefetch collapse distinct select as columns +select +as +columns/};
  
+   my $seen = { %{ (delete $attrs->{seen_join}) || {} } };
  
-   # we need to take the prefetch the attrs into account before we
-   # ->_resolve_join as otherwise they get lost - captainL
-   my $merged = $self->_merge_attr( $attrs->{join}, $attrs->{prefetch} );
+   my $from;
+   my @force_subq_attrs = qw/offset rows group_by having/;
+   if (
+     ($attrs->{from} && ref $attrs->{from} ne 'ARRAY')
+       ||
+     $self->_has_resolved_attr (@force_subq_attrs)
+   ) {
+     # Nuke the prefetch (if any) before the new $rs attrs
+     # are resolved (prefetch is useless - we are wrapping
+     # a subquery anyway).
+     my $rs_copy = $self->search;
+     $rs_copy->{attrs}{join} = $self->_merge_attr (
+       $rs_copy->{attrs}{join},
+       delete $rs_copy->{attrs}{prefetch},
+     );
+     $from = [{
+       -source_handle => $source->handle,
+       -alias => $attrs->{alias},
+       $attrs->{alias} => $rs_copy->as_query,
+     }];
+     delete @{$attrs}{@force_subq_attrs, 'where'};
+     $seen->{-relation_chain_depth} = 0;
+   }
+   elsif ($attrs->{from}) {  #shallow copy suffices
+     $from = [ @{$attrs->{from}} ];
+   }
+   else {
+     $from = [{
+       -source_handle => $source->handle,
+       -alias => $attrs->{alias},
+       $attrs->{alias} => $source->from,
+     }];
+   }
+   my $jpath = ($seen->{-relation_chain_depth})
+     ? $from->[-1][0]{-join_path}
+     : [];
  
    my @requested_joins = $source->_resolve_join(
-     $merged,
+     $join,
      $attrs->{alias},
      $seen,
      $jpath,
  
    push @$from, @requested_joins;
  
-   $seen->{-relation_chain_depth} += 0.5;
+   $seen->{-relation_chain_depth}++;
  
    # if $self already had a join/prefetch specified on it, the requested
    # $rel might very well be already included. What we do in this case
    # the join in question so we could tell it *is* the search_related)
    my $already_joined;
  
    # we consider the last one thus reverse
    for my $j (reverse @requested_joins) {
-     if ($rel eq $j->[0]{-join_path}[-1]) {
-       $j->[0]{-relation_chain_depth} += 0.5;
+     my ($last_j) = keys %{$j->[0]{-join_path}[-1]};
+     if ($rel eq $last_j) {
+       $j->[0]{-relation_chain_depth}++;
        $already_joined++;
        last;
      }
    }
  
- # alternative way to scan the entire chain - not backwards compatible
- #  for my $j (reverse @$from) {
- #    next unless ref $j eq 'ARRAY';
- #    if ($j->[0]{-join_path} && $j->[0]{-join_path}[-1] eq $rel) {
- #      $j->[0]{-relation_chain_depth} += 0.5;
- #      $already_joined++;
- #      last;
- #    }
- #  }
    unless ($already_joined) {
      push @$from, $source->_resolve_join(
        $rel,
      );
    }
  
-   $seen->{-relation_chain_depth} += 0.5;
+   $seen->{-relation_chain_depth}++;
  
-   return ($from,$seen);
+   return {%$attrs, from => $from, seen_join => $seen};
  }
  
  # too many times we have to do $attrs = { %{$self->_resolved_attrs} }
@@@ -2755,41 -2799,46 +2775,46 @@@ sub _resolved_attrs 
    # build columns (as long as select isn't set) into a set of as/select hashes
    unless ( $attrs->{select} ) {
  
-     my @cols = ( ref($attrs->{columns}) eq 'ARRAY' )
-       ? @{ delete $attrs->{columns}}
-       : (
-           ( delete $attrs->{columns} )
-             ||
-           $source->columns
-         )
-     ;
+     my @cols;
+     if ( ref $attrs->{columns} eq 'ARRAY' ) {
+       @cols = @{ delete $attrs->{columns}}
+     } elsif ( defined $attrs->{columns} ) {
+       @cols = delete $attrs->{columns}
+     } else {
+       @cols = $source->columns
+     }
  
-     @colbits = map {
-       ( ref($_) eq 'HASH' )
-       ? $_
-       : {
-           (
-             /^\Q${alias}.\E(.+)$/
-               ? "$1"
-               : "$_"
-           )
-             =>
-           (
-             /\./
-               ? "$_"
-               : "${alias}.$_"
-           )
-         }
-     } @cols;
+     for (@cols) {
+       if ( ref $_ eq 'HASH' ) {
+         push @colbits, $_
+       } else {
+         my $key = /^\Q${alias}.\E(.+)$/
+           ? "$1"
+           : "$_";
+         my $value = /\./
+           ? "$_"
+           : "${alias}.$_";
+         push @colbits, { $key => $value };
+       }
+     }
    }
  
    # add the additional columns on
-   foreach ( 'include_columns', '+columns' ) {
-       push @colbits, map {
-           ( ref($_) eq 'HASH' )
-             ? $_
-             : { ( split( /\./, $_ ) )[-1] => ( /\./ ? $_ : "${alias}.$_" ) }
-       } ( ref($attrs->{$_}) eq 'ARRAY' ) ? @{ delete $attrs->{$_} } : delete $attrs->{$_} if ( $attrs->{$_} );
+   foreach (qw{include_columns +columns}) {
+     if ( $attrs->{$_} ) {
+       my @list = ( ref($attrs->{$_}) eq 'ARRAY' )
+         ? @{ delete $attrs->{$_} }
+         : delete $attrs->{$_};
+       for (@list) {
+         if ( ref($_) eq 'HASH' ) {
+           push @colbits, $_
+         } else {
+           my $key = ( split /\./, $_ )[-1];
+           my $value = ( /\./ ? $_ : "$alias.$_" );
+           push @colbits, { $key => $value };
+         }
+       }
+     }
    }
  
    # start with initial select items
          ( ref $attrs->{select} eq 'ARRAY' )
        ? [ @{ $attrs->{select} } ]
        : [ $attrs->{select} ];
-     $attrs->{as} = (
-       $attrs->{as}
-       ? (
-         ref $attrs->{as} eq 'ARRAY'
-         ? [ @{ $attrs->{as} } ]
-         : [ $attrs->{as} ]
+     if ( $attrs->{as} ) {
+       $attrs->{as} =
+         (
+           ref $attrs->{as} eq 'ARRAY'
+             ? [ @{ $attrs->{as} } ]
+             : [ $attrs->{as} ]
          )
-       : [ map { m/^\Q${alias}.\E(.+)$/ ? $1 : $_ } @{ $attrs->{select} } ]
-     );
+     } else {
+       $attrs->{as} = [ map {
+          m/^\Q${alias}.\E(.+)$/
+            ? $1
+            : $_
+          } @{ $attrs->{select} }
+       ]
+     }
    }
    else {
  
    }
  
    # now add colbits to select/as
-   push( @{ $attrs->{select} }, map { values( %{$_} ) } @colbits );
-   push( @{ $attrs->{as} },     map { keys( %{$_} ) } @colbits );
+   push @{ $attrs->{select} }, map values %{$_}, @colbits;
+   push @{ $attrs->{as}     }, map keys   %{$_}, @colbits;
  
-   my $adds;
-   if ( $adds = delete $attrs->{'+select'} ) {
+   if ( my $adds = delete $attrs->{'+select'} ) {
      $adds = [$adds] unless ref $adds eq 'ARRAY';
-     push(
-       @{ $attrs->{select} },
-       map { /\./ || ref $_ ? $_ : "${alias}.$_" } @$adds
-     );
+     push @{ $attrs->{select} },
+       map { /\./ || ref $_ ? $_ : "$alias.$_" } @$adds;
    }
-   if ( $adds = delete $attrs->{'+as'} ) {
+   if ( my $adds = delete $attrs->{'+as'} ) {
      $adds = [$adds] unless ref $adds eq 'ARRAY';
-     push( @{ $attrs->{as} }, @$adds );
+     push @{ $attrs->{as} }, @$adds;
    }
  
-   $attrs->{from} ||= [ {
+   $attrs->{from} ||= [{
      -source_handle => $source->handle,
      -alias => $self->{attrs}{alias},
      $self->{attrs}{alias} => $source->from,
-   } ];
+   }];
  
    if ( $attrs->{join} || $attrs->{prefetch} ) {
  
            $join,
            $alias,
            { %{ $attrs->{seen_join} || {} } },
-           ($attrs->{seen_join} && keys %{$attrs->{seen_join}})
+           ( $attrs->{seen_join} && keys %{$attrs->{seen_join}})
              ? $attrs->{from}[-1][0]{-join_path}
              : []
            ,
      }
      else {
        $attrs->{group_by} = [ grep { !ref($_) || (ref($_) ne 'HASH') } @{$attrs->{select}} ];
+       # add any order_by parts that are not already present in the group_by
+       # we need to be careful not to add any named functions/aggregates
+       # i.e. select => [ ... { count => 'foo', -as 'foocount' } ... ]
+       my %already_grouped = map { $_ => 1 } (@{$attrs->{group_by}});
+       my $storage = $self->result_source->schema->storage;
+       my $rs_column_list = $storage->_resolve_column_info ($attrs->{from});
+       for my $chunk ($storage->_parse_order_by($attrs->{order_by})) {
+         if ($rs_column_list->{$chunk} && not $already_grouped{$chunk}++) {
+           push @{$attrs->{group_by}}, $chunk;
+         }
+       }
      }
    }
  
  
      my $prefetch_ordering = [];
  
-     my $join_map = $self->_joinpath_aliases ($attrs->{from}, $attrs->{seen_join});
+     # this is a separate structure (we don't look in {from} directly)
+     # as the resolver needs to shift things off the lists to work
+     # properly (identical-prefetches on different branches)
+     my $join_map = {};
+     if (ref $attrs->{from} eq 'ARRAY') {
+       my $start_depth = $attrs->{seen_join}{-relation_chain_depth} || 0;
+       for my $j ( @{$attrs->{from}}[1 .. $#{$attrs->{from}} ] ) {
+         next unless $j->[0]{-alias};
+         next unless $j->[0]{-join_path};
+         next if ($j->[0]{-relation_chain_depth} || 0) < $start_depth;
+         my @jpath = map { keys %$_ } @{$j->[0]{-join_path}};
+         my $p = $join_map;
+         $p = $p->{$_} ||= {} for @jpath[ ($start_depth/2) .. $#jpath]; #only even depths are actual jpath boundaries
+         push @{$p->{-join_aliases} }, $j->[0]{-alias};
+       }
+     }
  
      my @prefetch =
        $source->_resolve_prefetch( $prefetch, $alias, $join_map, $prefetch_ordering, $attrs->{collapse} );
    # even though it doesn't make much sense, this is what pre 081xx has
    # been doing
    if (my $page = delete $attrs->{page}) {
-     $attrs->{offset} = 
+     $attrs->{offset} =
        ($attrs->{rows} * ($page - 1))
              +
        ($attrs->{offset} || 0)
    return $self->{_attrs} = $attrs;
  }
  
- sub _joinpath_aliases {
-   my ($self, $fromspec, $seen) = @_;
-   my $paths = {};
-   return $paths unless ref $fromspec eq 'ARRAY';
-   my $cur_depth = $seen->{-relation_chain_depth} || 0;
-   if (int ($cur_depth) != $cur_depth) {
-     $self->throw_exception ("-relation_chain_depth is not an integer, something went horribly wrong ($cur_depth)");
-   }
-   for my $j (@$fromspec) {
-     next if ref $j ne 'ARRAY';
-     next if ($j->[0]{-relation_chain_depth} || 0) < $cur_depth;
-     my $jpath = $j->[0]{-join_path};
-     my $p = $paths;
-     $p = $p->{$_} ||= {} for @{$jpath}[$cur_depth .. $#$jpath];
-     push @{$p->{-join_aliases} }, $j->[0]{-alias};
-   }
-   return $paths;
- }
  sub _rollout_attr {
    my ($self, $attr) = @_;
  
@@@ -3107,7 -3167,7 +3143,7 @@@ These are in no particular order
  
  =back
  
- Which column(s) to order the results by. 
+ Which column(s) to order the results by.
  
  [The full list of suitable values is documented in
  L<SQL::Abstract/"ORDER BY CLAUSES">; the following is a summary of
@@@ -3202,6 -3262,9 +3238,9 @@@ When you use function/stored procedure 
  attribute, the column names returned are storage-dependent. E.g. MySQL would
  return a column named C<count(employeeid)> in the above example.
  
+ B<NOTE:> You will almost always need a corresponding 'as' entry when you use
+ 'select'.
  =head2 +select
  
  =over 4
@@@ -3399,12 -3462,12 +3438,12 @@@ exactly as you might expect
  
  =over 4
  
- =item * 
+ =item *
  
  Prefetch uses the L</cache> to populate the prefetched relationships. This
  may or may not be what you want.
  
- =item * 
+ =item *
  
  If you specify a condition on a prefetched relationship, ONLY those
  rows that match the prefetched condition will be fetched into that relationship.
@@@ -3516,8 -3579,8 +3555,8 @@@ Adds to the WHERE clause
    # only return rows WHERE deleted IS NULL for all searches
    __PACKAGE__->resultset_attributes({ where => { deleted => undef } }); )
  
- Can be overridden by passing C<{ where => undef }> as an attribute
- to a resulset.
+ Can be overridden by passing C<< { where => undef } >> as an attribute
+ to a resultset.
  
  =back
  
@@@ -28,9 -28,8 +28,8 @@@ DBIx::Class::ResultSource - Result sour
    # Create a table based result source, in a result class.
  
    package MyDB::Schema::Result::Artist;
-   use base qw/DBIx::Class/;
+   use base qw/DBIx::Class::Core/;
  
-   __PACKAGE__->load_components(qw/Core/);
    __PACKAGE__->table('artist');
    __PACKAGE__->add_columns(qw/ artistid name /);
    __PACKAGE__->set_primary_key('artistid');
@@@ -40,8 -39,9 +39,9 @@@
  
    # Create a query (view) based result source, in a result class
    package MyDB::Schema::Result::Year2000CDs;
+   use base qw/DBIx::Class::Core/;
  
-   __PACKAGE__->load_components('Core');
+   __PACKAGE__->load_components('InflateColumn::DateTime');
    __PACKAGE__->table_class('DBIx::Class::ResultSource::View');
  
    __PACKAGE__->table('year2000cds');
@@@ -60,10 -60,10 +60,10 @@@ sources, for example L<DBIx::Class::Res
  default result source type, so one is created for you when defining a
  result class as described in the synopsis above.
  
- More specifically, the L<DBIx::Class::Core> component pulls in the
- L<DBIx::Class::ResultSourceProxy::Table> as a base class, which
- defines the L<table|DBIx::Class::ResultSourceProxy::Table/table>
- method. When called, C<table> creates and stores an instance of
+ More specifically, the L<DBIx::Class::Core> base class pulls in the
+ L<DBIx::Class::ResultSourceProxy::Table> component, which defines
+ the L<table|DBIx::Class::ResultSourceProxy::Table/table> method.
+ When called, C<table> creates and stores an instance of
  L<DBIx::Class::ResultSoure::Table>. Luckily, to use tables as result
  sources, you don't need to remember any of this.
  
@@@ -389,7 -389,7 +389,7 @@@ sub columns 
    my $self = shift;
    $self->throw_exception(
      "columns() is a read-only accessor, did you mean add_columns()?"
-   ) if (@_ > 1);
+   ) if @_;
    return @{$self->{_ordered_columns}||[]};
  }
  
@@@ -1199,7 -1199,7 +1199,7 @@@ sub _resolve_join 
    $self->throw_exception ('You must supply a joinpath arrayref as the 4th argument to _resolve_join')
      unless ref $jpath eq 'ARRAY';
  
-   $jpath = [@$jpath];
+   $jpath = [@$jpath]; # copy
  
    if (not defined $join) {
      return ();
        $force_left ||= lc($rel_info->{attrs}{join_type}||'') eq 'left';
  
        # the actual seen value will be incremented by the recursion
-       my $as = ($seen->{$rel} ? join ('_', $rel, $seen->{$rel} + 1) : $rel);
+       my $as = $self->storage->relname_to_table_alias(
+         $rel, ($seen->{$rel} && $seen->{$rel} + 1)
+       );
  
        push @ret, (
          $self->_resolve_join($rel, $alias, $seen, [@$jpath], $force_left),
          $self->related_source($rel)->_resolve_join(
-           $join->{$rel}, $as, $seen, [@$jpath, $rel], $force_left
+           $join->{$rel}, $as, $seen, [@$jpath, { $rel => $as }], $force_left
          )
        );
      }
    }
    else {
      my $count = ++$seen->{$join};
-     my $as = ($count > 1 ? "${join}_${count}" : $join);
+     my $as = $self->storage->relname_to_table_alias(
+       $join, ($count > 1 && $count)
+     );
  
      my $rel_info = $self->relationship_info($join)
        or $self->throw_exception("No such relationship ${join}");
                    ? 'left'
                    : $rel_info->{attrs}{join_type}
                  ,
-                -join_path => [@$jpath, $join],
+                -join_path => [@$jpath, { $join => $as } ],
+                -is_single => (
+                   $rel_info->{attrs}{accessor}
+                     &&
+                   List::Util::first { $rel_info->{attrs}{accessor} eq $_ } (qw/single filter/)
+                 ),
                 -alias => $as,
                 -relation_chain_depth => $seen->{-relation_chain_depth} || 0,
               },
@@@ -1328,13 -1337,13 +1337,13 @@@ sub _resolve_condition 
          unless ($for->has_column_loaded($v)) {
            if ($for->in_storage) {
              $self->throw_exception(sprintf
-               'Unable to resolve relationship from %s to %s: column %s.%s not '
-             . 'loaded from storage (or not passed to new() prior to insert()). '
-             . 'Maybe you forgot to call ->discard_changes to get defaults from the db.',
-               $for->result_source->source_name,
+               "Unable to resolve relationship '%s' from object %s: column '%s' not "
+             . 'loaded from storage (or not passed to new() prior to insert()). You '
+             . 'probably need to call ->discard_changes to get the server-side defaults '
+             . 'from the database.',
                $as,
-               $as, $v,
+               $for,
+               $v,
              );
            }
            return $UNRESOLVABLE_CONDITION;
  # Accepts one or more relationships for the current source and returns an
  # array of column names for each of those relationships. Column names are
  # prefixed relative to the current source, in accordance with where they appear
- # in the supplied relationships. Needs an alias_map generated by
- # $rs->_joinpath_aliases
+ # in the supplied relationships.
  
  sub _resolve_prefetch {
    my ($self, $pre, $alias, $alias_map, $order, $collapse, $pref_path) = @_;
      my $as_prefix = ($alias =~ /^.*?\.(.+)$/ ? $1.'.' : '');
      my $rel_source = $self->related_source($pre);
  
-     if (exists $rel_info->{attrs}{accessor}
-          && $rel_info->{attrs}{accessor} eq 'multi') {
+     if ($rel_info->{attrs}{accessor} && $rel_info->{attrs}{accessor} eq 'multi') {
        $self->throw_exception(
          "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 ];
                      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}
+    
+                 : (defined $rel_info->{attrs}{order_by}
                         ? ($rel_info->{attrs}{order_by})
                         : ()));
        push(@$order, map { "${as}.$_" } (@key, @ord));
    }
  }
  
 +# 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
@@@ -1530,7 -1510,7 +1538,7 @@@ L<DBIx::Class::ResultSourceHandle>
  =cut
  
  sub handle {
-     return new DBIx::Class::ResultSourceHandle({
+     return DBIx::Class::ResultSourceHandle->new({
          schema         => $_[0]->schema,
          source_moniker => $_[0]->source_name
      });
diff --combined t/inflate/hri.t
@@@ -1,7 -1,7 +1,7 @@@
  use strict;
- use warnings;  
+ use warnings;
  
- use Test::More qw(no_plan);
+ use Test::More;
  use lib qw(t/lib);
  use DBICTest;
  my $schema = DBICTest->init_schema();
@@@ -9,7 -9,7 +9,7 @@@
  # Under some versions of SQLite if the $rs is left hanging around it will lock
  # So we create a scope here cos I'm lazy
  {
-     my $rs = $schema->resultset('CD');
+     my $rs = $schema->resultset('CD')->search ({}, { order_by => 'cdid' });
  
      # get the defined columns
      my @dbic_cols = sort $rs->result_source->columns;
      my @hashref_cols = sort keys %$datahashref1;
  
      is_deeply( \@dbic_cols, \@hashref_cols, 'returned columns' );
- }
  
+     my $cd1 = $rs->find ({cdid => 1});
+     is_deeply ( $cd1, $datahashref1, 'first/find return the same thing');
+ }
  
  sub check_cols_of {
      my ($dbic_obj, $datahashref) = @_;
@@@ -43,7 -45,7 +45,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) {
@@@ -135,3 -137,6 +137,6 @@@ is_deeply
    [{ $artist->get_columns, cds => [] }],
    'nested has_many prefetch without entries'
  );
+ done_testing;