Updated version and Changes for 0.06999_02
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / ResultSet.pm
index ec41bc5..f72dc66 100644 (file)
@@ -11,7 +11,7 @@ use Data::Page;
 use Storable;
 use Data::Dumper;
 use Scalar::Util qw/weaken/;
-
+use Data::Dumper;
 use DBIx::Class::ResultSetColumn;
 use base qw/DBIx::Class/;
 __PACKAGE__->load_components(qw/AccessorGroup/);
@@ -160,14 +160,29 @@ always return a resultset, even in list context.
 sub search_rs {
   my $self = shift;
 
-  my $our_attrs = { %{$self->{attrs}} };
-  my $having = delete $our_attrs->{having};
   my $attrs = {};
   $attrs = pop(@_) if @_ > 1 and ref $_[$#_] eq 'HASH';
-  
+  my $our_attrs = ($attrs->{_parent_attrs}) ? { %{$attrs->{_parent_attrs}} } : { %{$self->{attrs}} };
+  my $having = delete $our_attrs->{having};
+
+       # XXX this is getting messy
+       if ($attrs->{_live_join_stack}) {
+               my $live_join = $attrs->{_live_join_stack};
+               foreach (reverse @{$live_join}) {
+                       $attrs->{_live_join_h} = (defined $attrs->{_live_join_h}) ? { $_ => $attrs->{_live_join_h} } : $_;
+               }
+       }
+
   # merge new attrs into old
   foreach my $key (qw/join prefetch/) {
     next unless (exists $attrs->{$key});
+    if ($attrs->{_live_join_stack} || $our_attrs->{_live_join_stack}) {
+                       my $live_join = $attrs->{_live_join_stack} || $our_attrs->{_live_join_stack};
+                       foreach (reverse @{$live_join}) {
+                               $attrs->{$key} = { $_ => $attrs->{$key} };
+                       }
+    }
+
     if (exists $our_attrs->{$key}) {
       $our_attrs->{$key} = $self->_merge_attr($our_attrs->{$key}, $attrs->{$key});
     } else {
@@ -176,13 +191,13 @@ sub search_rs {
     delete $attrs->{$key};
   }
 
+       $our_attrs->{join} = $self->_merge_attr($our_attrs->{join}, $attrs->{_live_join_h}, 1) if ($attrs->{_live_join_h});
+
   if (exists $our_attrs->{prefetch}) {
       $our_attrs->{join} = $self->_merge_attr($our_attrs->{join}, $our_attrs->{prefetch}, 1);
   }
 
   my $new_attrs = { %{$our_attrs}, %{$attrs} };
-
-  # merge new where and having into old
   my $where = (@_
                 ? ((@_ == 1 || ref $_[0] eq "HASH")
                     ? shift
@@ -314,7 +329,6 @@ sub find {
   }
 
   my @unique_queries = $self->_unique_queries($input_query, $attrs);
-#  use Data::Dumper; warn Dumper $self->result_source->name, $input_query, \@unique_queries, $self->{attrs}->{where};
 
   # Handle cases where the ResultSet defines the query, or where the user is
   # abusing find
@@ -354,7 +368,8 @@ sub _unique_queries {
 
     # Add the ResultSet's alias
     foreach my $key (grep { ! m/\./ } keys %$unique_query) {
-      $unique_query->{"$self->{attrs}->{alias}.$key"} = delete $unique_query->{$key};
+                       my $alias = ($self->{attrs}->{_live_join}) ? $self->{attrs}->{_live_join} : $self->{attrs}->{alias};
+      $unique_query->{"$alias.$key"} = delete $unique_query->{$key};
     }
 
     push @unique_queries, $unique_query;
@@ -464,7 +479,7 @@ sub single {
   }
 
   unless ($self->_is_unique_query($attrs->{where})) {
-    carp "Query not guarnteed to return a single row"
+    carp "Query not guaranteed to return a single row"
       . "; please declare your unique constraints or use search instead";
   }
 
@@ -483,10 +498,10 @@ sub _is_unique_query {
   my ($self, $query) = @_;
 
   my $collapsed = $self->_collapse_query($query);
-#  use Data::Dumper; warn Dumper $query, $collapsed;
 
+       my $alias = ($self->{attrs}->{_live_join}) ? $self->{attrs}->{_live_join} : $self->{attrs}->{alias};
   foreach my $name ($self->result_source->unique_constraint_names) {
-    my @unique_cols = map { "$self->{attrs}->{alias}.$_" }
+    my @unique_cols = map { "$alias.$_" }
       $self->result_source->unique_constraint_columns($name);
 
     # Count the values for each unique column
@@ -494,7 +509,7 @@ sub _is_unique_query {
 
     foreach my $key (keys %$collapsed) {
       my $aliased = $key;
-      $aliased = "$self->{attrs}->{alias}.$key" unless $key =~ /\./;
+      $aliased = "$alias.$key" unless $key =~ /\./;
 
       next unless exists $seen{$aliased};  # Additional constraints are okay
       $seen{$aliased} = scalar @{ $collapsed->{$key} };
@@ -670,7 +685,7 @@ sub _resolve {
 
   return if(exists $self->{_attrs}); #return if _resolve has already been called
 
-  my $attrs = $self->{attrs};  
+  my $attrs = $self->{attrs};    
   my $source = ($self->{_parent_rs}) ? $self->{_parent_rs} : $self->{result_source};
 
   # XXX - lose storable dclone
@@ -700,33 +715,32 @@ sub _resolve {
   $attrs->{seen_join} ||= {};
   my %seen;
   if (my $join = delete $attrs->{join}) {
-      foreach my $j (ref $join eq 'ARRAY' ? @$join : ($join)) {
-         if (ref $j eq 'HASH') {
-             $seen{$_} = 1 foreach keys %$j;
-         } else {
-             $seen{$j} = 1;
-         }
+    foreach my $j (ref $join eq 'ARRAY' ? @$join : ($join)) {
+      if (ref $j eq 'HASH') {
+        $seen{$_} = 1 foreach keys %$j;
+      } else {
+        $seen{$j} = 1;
       }
-
-      push(@{$attrs->{from}}, $source->resolve_join($join, $attrs->{alias}, $attrs->{seen_join}));
+    }
+    
+    push(@{$attrs->{from}}, $source->resolve_join($join, $attrs->{alias}, $attrs->{seen_join}));
   }
   $attrs->{group_by} ||= $attrs->{select} if delete $attrs->{distinct};
   $attrs->{order_by} = [ $attrs->{order_by} ] if
       $attrs->{order_by} and !ref($attrs->{order_by});
   $attrs->{order_by} ||= [];
 
- if(my $seladds = delete($attrs->{'+select'})) {
-   my @seladds = (ref($seladds) eq 'ARRAY' ? @$seladds : ($seladds));
-   $attrs->{select} = [
-     @{ $attrs->{select} },
-     map { (m/\./ || ref($_)) ? $_ : "${alias}.$_" } $seladds
-   ];
- }
- if(my $asadds = delete($attrs->{'+as'})) {
-   my @asadds = (ref($asadds) eq 'ARRAY' ? @$asadds : ($asadds));
-   $attrs->{as} = [ @{ $attrs->{as} }, @asadds ];
- }
-  
+  if(my $seladds = delete($attrs->{'+select'})) {
+    my @seladds = (ref($seladds) eq 'ARRAY' ? @$seladds : ($seladds));
+    $attrs->{select} = [
+                        @{ $attrs->{select} },
+                        map { (m/\./ || ref($_)) ? $_ : "${alias}.$_" } $seladds
+                        ];
+  }
+  if(my $asadds = delete($attrs->{'+as'})) {
+    my @asadds = (ref($asadds) eq 'ARRAY' ? @$asadds : ($asadds));
+    $attrs->{as} = [ @{ $attrs->{as} }, @asadds ];
+  }
   my $collapse = $attrs->{collapse} || {};
   if (my $prefetch = delete $attrs->{prefetch}) {
       my @pre_order;
@@ -740,10 +754,46 @@ sub _resolve {
              push(@{$attrs->{from}}, $source->resolve_join($p, $attrs->{alias}))
                  unless $seen{$p};
          }
-         my @prefetch = $source->resolve_prefetch(
+
+               # we're about to resolve_join on the current class, so we need to bring
+               # the joins (which are from the original class) to the right level
+               # XXX the below alg is ridiculous
+               if ($attrs->{_live_join_stack}) {
+                       STACK: foreach (@{$attrs->{_live_join_stack}}) {
+                               if (ref $p eq 'HASH') {
+                                       if (exists $p->{$_}) {
+                                               $p = $p->{$_};
+                                       } else {
+                                               $p = undef;
+                                               last STACK;
+                                       }
+                               } elsif (ref $p eq 'ARRAY') {
+                                       foreach my $pe (@{$p}) {
+                                               if ($pe eq $_) {
+                                                       $p = undef;
+                                                       last STACK;
+                                               }
+                                               next unless(ref $pe eq 'HASH');
+                                               next unless(exists $pe->{$_});
+                                               $p = $pe->{$_};
+                                               next STACK;
+                                       }                                               
+                                       $p = undef;
+                                       last STACK;
+                               } else {
+                                       $p = undef;
+                                       last STACK;
+                               }
+                       }
+               }
+               
+               if ($p) {
+                       my @prefetch = $self->result_source->resolve_prefetch(
                                                   $p, $attrs->{alias}, {}, \@pre_order, $collapse);
-         push(@{$attrs->{select}}, map { $_->[0] } @prefetch);
-         push(@{$attrs->{as}}, map { $_->[1] } @prefetch);
+               
+                       push(@{$attrs->{select}}, map { $_->[0] } @prefetch);
+                       push(@{$attrs->{as}}, map { $_->[1] } @prefetch);
+               }
       }
       push(@{$attrs->{order_by}}, @pre_order);
   }
@@ -756,58 +806,64 @@ sub _merge_attr {
     
   return $b unless $a;
   if (ref $b eq 'HASH' && ref $a eq 'HASH') {
-               foreach my $key (keys %{$b}) {
-                       if (exists $a->{$key}) {
-             $a->{$key} = $self->_merge_attr($a->{$key}, $b->{$key}, $is_prefetch);
-                       } else {
-             $a->{$key} = delete $b->{$key};
-                       }
-               }
-               return $a;
+    foreach my $key (keys %{$b}) {
+      if (exists $a->{$key}) {
+        $a->{$key} = $self->_merge_attr($a->{$key}, $b->{$key}, $is_prefetch);
+      } else {
+        $a->{$key} = $b->{$key};
+      }
+    }
+    return $a;
   } else {
-               $a = [$a] unless (ref $a eq 'ARRAY');
-               $b = [$b] unless (ref $b eq 'ARRAY');
-
-               my $hash = {};
-               my $array = [];      
-               foreach ($a, $b) {
-                       foreach my $element (@{$_}) {
-             if (ref $element eq 'HASH') {
-                                       $hash = $self->_merge_attr($hash, $element, $is_prefetch);
-             } elsif (ref $element eq 'ARRAY') {
-                                       $array = [@{$array}, @{$element}];
-             } else {  
-                                       if (($b == $_) && $is_prefetch) {
-                                               $self->_merge_array($array, $element, $is_prefetch);
-                                       } else {
-                                               push(@{$array}, $element);
-                                       }
-             }
-                       }
-               }
+    $a = [$a] unless (ref $a eq 'ARRAY');
+    $b = [$b] unless (ref $b eq 'ARRAY');
+    
+    my $hash = {};
+    my $array = [];      
+    foreach ($a, $b) {
+      foreach my $element (@{$_}) {
+        if (ref $element eq 'HASH') {
+          $hash = $self->_merge_attr($hash, $element, $is_prefetch);
+        } elsif (ref $element eq 'ARRAY') {
+          $array = [@{$array}, @{$element}];
+        } else {       
+          if (($b == $_) && $is_prefetch) {
+            $self->_merge_array($array, $element, $is_prefetch);
+          } else {
+            push(@{$array}, $element);
+          }
+        }
+      }
+    }
 
-               if ((keys %{$hash}) && (scalar(@{$array} > 0))) {
-                       return [$hash, @{$array}];
-               } else {        
-                       return (keys %{$hash}) ? $hash : $array;
+               my $final_array = [];
+               foreach my $element (@{$array}) {
+                       push(@{$final_array}, $element) unless (exists $hash->{$element});
                }
+               $array = $final_array;
+
+    if ((keys %{$hash}) && (scalar(@{$array} > 0))) {
+      return [$hash, @{$array}];
+    } else {   
+      return (keys %{$hash}) ? $hash : $array;
+    }
   }
 }
 
 sub _merge_array {
-       my ($self, $a, $b) = @_;
-       $b = [$b] unless (ref $b eq 'ARRAY');
-       # add elements from @{$b} to @{$a} which aren't already in @{$a}
-       foreach my $b_element (@{$b}) {
-               push(@{$a}, $b_element) unless grep {$b_element eq $_} @{$a};
-       }
+  my ($self, $a, $b) = @_;
+  
+  $b = [$b] unless (ref $b eq 'ARRAY');
+  # add elements from @{$b} to @{$a} which aren't already in @{$a}
+  foreach my $b_element (@{$b}) {
+    push(@{$a}, $b_element) unless grep {$b_element eq $_} @{$a};
+  }
 }
 
 sub _construct_object {
   my ($self, @row) = @_;
   my @as = @{ $self->{_attrs}{as} };
-
+  
   my $info = $self->_collapse_result(\@as, \@row);
   my $new = $self->result_class->inflate_result($self->result_source, @$info);
   $new = $self->{_attrs}{record_filter}->($new)
@@ -849,8 +905,8 @@ sub _collapse_result {
       $info->[0] = $const{$key};
     }
   }
-
   my @collapse;
+
   if (defined $prefix) {
     @collapse = map {
         m/^\Q${prefix}.\E(.+)$/ ? ($1) : ()
@@ -867,9 +923,10 @@ sub _collapse_result {
     }
     my $c_prefix = (defined($prefix) ? "${prefix}.${c}" : $c);
     my @co_key = @{$self->{_attrs}->{collapse}{$c_prefix}};
-    my %co_check = map { ($_, $target->[0]->{$_}); } @co_key;
     my $tree = $self->_collapse_result($as, $row, $c_prefix);
+    my %co_check = map { ($_, $tree->[0]->{$_}); } @co_key;
     my (@final, @raw);
+
     while ( !(grep {
                 !defined($tree->[0]->{$_}) ||
                 $co_check{$_} ne $tree->[0]->{$_}
@@ -882,6 +939,8 @@ sub _collapse_result {
     @$target = (@final ? @final : [ {}, {} ]); 
       # single empty result to indicate an empty prefetched has_many
   }
+
+  #print "final info: " . Dumper($info);
   return $info;
 }
 
@@ -927,7 +986,6 @@ sub count {
   my $self = shift;
   return $self->search(@_)->count if @_ and defined $_[0];
   return scalar @{ $self->get_cache } if $self->get_cache;
-
   my $count = $self->_count;
   return 0 unless $count;
 
@@ -965,7 +1023,10 @@ sub _count { # Separated out so pager can get the full count
 
   # offset, order by and page are not needed to count. record_filter is cdbi
   delete $attrs->{$_} for qw/rows offset order_by page pager record_filter/;
-  my ($count) = (ref $self)->new($self->result_source, $attrs)->cursor->next;
+       my $tmp_rs = (ref $self)->new($self->result_source, $attrs);
+       $tmp_rs->{_parent_rs} = $self->{_parent_rs} if ($self->{_parent_rs}); #XXX - hack to pass through parent of related resultsets
+
+  my ($count) = $tmp_rs->cursor->next;
   return $count;
 }
 
@@ -1543,28 +1604,34 @@ Returns a related resultset for the supplied relationship name.
 
 sub related_resultset {
   my ( $self, $rel ) = @_;
-
+  
   $self->{related_resultsets} ||= {};
   return $self->{related_resultsets}{$rel} ||= do {
-      #warn "fetching related resultset for rel '$rel' " . $self->result_source->{name};
-      my $rel_obj = $self->result_source->relationship_info($rel);
-      $self->throw_exception(
+    #warn "fetching related resultset for rel '$rel' " . $self->result_source->{name};
+    my $rel_obj = $self->result_source->relationship_info($rel);
+               #print Dumper($self->result_source->_relationships);
+    $self->throw_exception(
         "search_related: result source '" . $self->result_source->name .
         "' has no such relationship ${rel}")
         unless $rel_obj; #die Dumper $self->{attrs};
 
-      my $rs = $self->result_source->schema->resultset($rel_obj->{class}
+               my @live_join_stack = (exists $self->{attrs}->{_live_join_stack}) ?
+                       @{$self->{attrs}->{_live_join_stack}}:
+                       ();             
+               push(@live_join_stack, $rel);
+               
+    my $rs = $self->result_source->schema->resultset($rel_obj->{class}
            )->search( undef,
-             { %{$self->{attrs}},
-               select => undef,
-               as => undef,
-              join => $rel,
-              _live_join => $rel }
-           );
-
-      # keep reference of the original resultset
-      $rs->{_parent_rs} = $self->result_source;
-      return $rs;
+                      { select => undef,
+                        as => undef,
+                        _live_join => $rel, #the most recent
+                        _live_join_stack => \@live_join_stack, #the trail of rels
+                        _parent_attrs => $self->{attrs}}
+                       );    
+
+    # keep reference of the original resultset
+    $rs->{_parent_rs} = ($self->{_parent_rs}) ? $self->{_parent_rs} : $self->result_source;
+    return $rs;
   };
 }