Merge 'DBIx-Class-current' into 'bulk_create'
Matt S Trout [Mon, 28 May 2007 21:57:08 +0000 (21:57 +0000)]
r51652@cain (orig r3338):  matthewt | 2007-05-20 00:05:23 +0000
find/next change to return undef rather than () on fail from Bernhard Graf
r51653@cain (orig r3339):  matthewt | 2007-05-20 00:09:33 +0000
deprecation warning for compose_connection now caught and tested
r51654@cain (orig r3340):  ilmari | 2007-05-20 00:10:20 +0000
add tests for nested prefetch with many_to_many and chained search_related
r51667@cain (orig r3341):  matthewt | 2007-05-21 14:22:52 +0000
fixed WhereJoins to handle conditions edge cases
r51760@cain (orig r3352):  groditi | 2007-05-21 22:29:11 +0000
result_class is getting leaked somewhere in related_resultset, failing test included
r51762@cain (orig r3354):  claco | 2007-05-22 00:32:35 +0000
added get_inflated_columns to Row

r51764@cain (orig r3356):  blblack | 2007-05-22 01:40:14 +0000
 r30913@brandon-blacks-computer (orig r3263):  matthewt | 2007-05-06 11:52:36 -0500
 patch from soulchild (thanks!)
 r31140@brandon-blacks-computer (orig r3318):  castaway | 2007-05-17 07:56:49 -0500
 Applied patch from Pedro Melo to fix order of components in the example

 r31938@brandon-blacks-computer (orig r3348):  ilmari | 2007-05-21 15:23:36 -0500
 Copy the working mk_hash from HashRefInflator in -current into Cookbook

 r31946@brandon-blacks-computer (orig r3355):  blblack | 2007-05-21 20:21:42 -0500
 connect_info should return the same data it was given

r51765@cain (orig r3357):  blblack | 2007-05-22 01:44:57 +0000
cleanup on aisle 7
r51766@cain (orig r3358):  claco | 2007-05-22 02:36:28 +0000
%colinfo accessor and inflate_column now work together

r51772@cain (orig r3364):  groditi | 2007-05-22 16:24:15 +0000
fix to t/97result_class.t and fixed tests too
r51820@cain (orig r3383):  matthewt | 2007-05-25 16:00:52 +0000
prefetch/cache fixes for all but find

1  2 
lib/DBIx/Class/ResultSet.pm
lib/DBIx/Class/Row.pm

@@@ -34,7 -34,7 +34,7 @@@ In the examples below, the following ta
  
    package MyApp::Schema::Artist;
    use base qw/DBIx::Class/;
 -  __PACKAGE__->load_components(qw/Core/);
 +  __PACKAGE__->load_components(qw/Core/)
    __PACKAGE__->table('artist');
    __PACKAGE__->add_columns(qw/artistid name/);
    __PACKAGE__->set_primary_key('artistid');
@@@ -350,13 -350,11 +350,13 @@@ sub find 
  
    my (%related, $info);
  
 -  foreach my $key (keys %$input_query) {
 +  KEY: foreach my $key (keys %$input_query) {
      if (ref($input_query->{$key})
          && ($info = $self->result_source->relationship_info($key))) {
 +      my $val = delete $input_query->{$key};
 +      next KEY if (ref($val) eq 'ARRAY'); # has_many for multi_create
        my $rel_q = $self->result_source->resolve_condition(
 -                    $info->{cond}, delete $input_query->{$key}, $key
 +                    $info->{cond}, $val, $key
                    );
        die "Can't handle OR join condition in find" if ref($rel_q) eq 'ARRAY';
        @related{keys %$rel_q} = values %$rel_q;
@@@ -545,7 -543,7 +545,7 @@@ sub single 
      $attrs->{where}, $attrs
    );
  
-   return (@data ? ($self->_construct_object(@data))[0] : ());
+   return (@data ? ($self->_construct_object(@data))[0] : undef);
  }
  
  # _is_unique_query
@@@ -740,7 -738,7 +740,7 @@@ sub next 
        ? @{delete $self->{stashed_row}}
        : $self->cursor->next
    );
-   return unless (@row);
+   return undef unless (@row);
    my ($row, @more) = $self->_construct_object(@row);
    $self->{stashed_objects} = \@more if @more;
    return $row;
@@@ -1240,135 -1238,6 +1240,135 @@@ sub delete_all 
    return 1;
  }
  
 +=head2 populate
 +
 +=over 4
 +
 +=item Arguments: $source_name, \@data;
 +
 +=back
 +
 +Pass an arrayref of hashrefs. Each hashref should be a structure suitable for
 +submitting to a $resultset->create(...) method.
 +
 +In void context, C<insert_bulk> in L<DBIx::Class::Storage::DBI> is used
 +to insert the data, as this is a faster method.
 +
 +Otherwise, each set of data is inserted into the database using
 +L<DBIx::Class::ResultSet/create>, and a arrayref of the resulting row
 +objects is returned.
 +
 +Example:  Assuming an Artist Class that has many CDs Classes relating:
 +
 +  my $Artist_rs = $schema->resultset("Artist");
 +  
 +  ## Void Context Example 
 +  $Artist_rs->populate([
 +     { artistid => 4, name => 'Manufactured Crap', cds => [ 
 +        { title => 'My First CD', year => 2006 },
 +        { title => 'Yet More Tweeny-Pop crap', year => 2007 },
 +      ],
 +     },
 +     { artistid => 5, name => 'Angsty-Whiny Girl', cds => [
 +        { 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 }
 +      ],
 +     },
 +  ]);
 +  
 +  ## Array Context Example
 +  my ($ArtistOne, $ArtistTwo, $ArtistThree) = $Artist_rs->populate([
 +    { name => "Artist One"},
 +      { name => "Artist Two"},
 +      { name => "Artist Three", cds=> [
 +        { title => "First CD", year => 2007},
 +        { title => "Second CD", year => 2008},
 +      ]}
 +  ]);
 +  
 +  print $ArtistOne->name; ## response is 'Artist One'
 +  print $ArtistThree->cds->count ## reponse is '2'
 +
 +=cut
 +use Data::Dump qw/dump/;
 +sub populate {
 +  my ($self, $data) = @_;
 +  
 +  if(defined wantarray) {
 +    my @created;
 +    foreach my $item (@$data) {
 +      push(@created, $self->create($item));
 +    }
 +    return @created;
 +  } else {
 +    my ($first, @rest) = @$data;
 +
 +      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      
 +    foreach my $index (0..$#{@$data})
 +      {
 +              foreach my $rel (@rels)
 +              {
 +                      next unless $data->[$index]->{$rel} && ref $data->[$index]->{$rel} eq "HASH";
 +                      
 +                      my $result = $self->related_resultset($rel)->create($data->[$index]->{$rel});
 +                      
 +                      my ($reverse) = keys %{$self->result_source->reverse_relationship_info($rel)};
 +                      
 +                      my $related = $result->result_source->resolve_condition(
 +
 +                              $result->result_source->relationship_info($reverse)->{cond},
 +                              $self,                          
 +                              $result,                                
 +                      );
 +
 +                      delete $data->[$index]->{$rel};
 +                      $data->[$index] = {%{$data->[$index]}, %$related};
 +                      
 +                      push @names, keys %$related if $index == 0;
 +              }
 +      }
 +      
 +    my @values = map {
 +      [ map {
 +         defined $_ ? $_ : $self->throw_exception("Undefined value for column!")
 +      } @$_{@names} ]
 +    } @$data;
 +
 +    $self->result_source->storage->insert_bulk(
 +      $self->result_source, 
 +      \@names, 
 +      \@values,
 +    );
 +
 +      ## do the has_many relationships
 +    foreach my $item (@$data) {
 +
 +      foreach my $rel (@rels) {
 +        next unless $item->{$rel} && ref $item->{$rel} eq "ARRAY";
 +
 +        my $parent = $self->find(map {{$_=>$item->{$_}} } @pks) || next;
 +        my $child = $parent->$rel;
 +              
 +        my $related = $child->result_source->resolve_condition(
 +          $parent->result_source->relationship_info($rel)->{cond},
 +          $child,
 +          $parent,
 +        );
 +
 +        my @rows_to_add = ref $item->{$rel} eq 'ARRAY' ? @{$item->{$rel}} : ($item->{$rel});
 +        my @populate = map { {%$_, %$related} } @rows_to_add;
 +
 +        $child->populate( \@populate );
 +      }
 +    }
 +  }
 +}
 +
  =head2 pager
  
  =over 4
@@@ -1749,18 -1618,36 +1749,36 @@@ sub related_resultset 
      my $join_count = $seen->{$rel};
      my $alias = ($join_count > 1 ? join('_', $rel, $join_count) : $rel);
  
-     $self->_source_handle->schema->resultset($rel_obj->{class})->search_rs(
-       undef, {
-         %{$self->{attrs}||{}},
-         join => undef,
-         prefetch => undef,
-         select => undef,
-         as => undef,
-         alias => $alias,
-         where => $self->{cond},
-         seen_join => $seen,
-         from => $from,
-     });
+     #XXX - temp fix for result_class bug. There likely is a more elegant fix -groditi
+     my %attrs = %{$self->{attrs}||{}};
+     delete $attrs{result_class};
+     my $new_cache;
+     if (my $cache = $self->get_cache) {
+       if ($cache->[0] && $cache->[0]->related_resultset($rel)->get_cache) {
+         $new_cache = [ map { @{$_->related_resultset($rel)->get_cache} }
+                         @$cache ];
+       }
+     }
+     my $new = $self->_source_handle
+                    ->schema
+                    ->resultset($rel_obj->{class})
+                    ->search_rs(
+                        undef, {
+                          %attrs,
+                          join => undef,
+                          prefetch => undef,
+                          select => undef,
+                          as => undef,
+                          alias => $alias,
+                          where => $self->{cond},
+                          seen_join => $seen,
+                          from => $from,
+                      });
+     $new->set_cache($new_cache) if $new_cache;
+     $new;
    };
  }
  
diff --combined lib/DBIx/Class/Row.pm
@@@ -5,7 -5,6 +5,7 @@@ use warnings
  
  use base qw/DBIx::Class/;
  use Carp::Clan qw/^DBIx::Class/;
 +use Scalar::Util ();
  
  __PACKAGE__->mk_group_accessors('simple' => qw/_source_handle/);
  
@@@ -28,21 -27,8 +28,21 @@@ derived from L<DBIx::Class::ResultSourc
  
  Creates a new row object from column => value mappings passed as a hash ref
  
 +Passing an object, or an arrayref of objects as a value will call
 +L<DBIx::Class::Relationship::Base/set_from_related> for you. When
 +passed a hashref or an arrayref of hashrefs as the value, these will
 +be turned into objects via new_related, and treated as if you had
 +passed objects.
 +
  =cut
  
 +## It needs to store the new objects somewhere, and call insert on that list later when insert is called on this object. We may need an accessor for these so the user can retrieve them, if just doing ->new().
 +## This only works because DBIC doesnt yet care to check whether the new_related objects have been passed all their mandatory columns
 +## When doing the later insert, we need to make sure the PKs are set.
 +## using _relationship_data in new and funky ways..
 +## check Relationship::CascadeActions and Relationship::Accessor for compat
 +## tests!
 +
  sub new {
    my ($class, $attrs) = @_;
    $class = ref $class if ref $class;
        unless ref($attrs) eq 'HASH';
      
      my ($related,$inflated);
 +    ## Pretend all the rels are actual objects, unset below if not, for insert() to fix
 +    $new->{_rel_in_storage} = 1;
 +
      foreach my $key (keys %$attrs) {
        if (ref $attrs->{$key}) {
 +        ## Can we extract this lot to use with update(_or .. ) ?
          my $info = $class->relationship_info($key);
          if ($info && $info->{attrs}{accessor}
            && $info->{attrs}{accessor} eq 'single')
          {
 -          $new->set_from_related($key, $attrs->{$key});        
 -          $related->{$key} = $attrs->{$key};
 +          my $rel_obj = delete $attrs->{$key};
 +          if(!Scalar::Util::blessed($rel_obj)) {
 +            $rel_obj = $new->find_or_new_related($key, $rel_obj);
 +            $new->{_rel_in_storage} = 0 unless ($rel_obj->in_storage);
 +          }
 +          $new->set_from_related($key, $rel_obj);        
 +          $related->{$key} = $rel_obj;
            next;
 -        }
 -        elsif ($class->has_column($key)
 -          && exists $class->column_info($key)->{_inflate_info})
 +        } elsif ($info && $info->{attrs}{accessor}
 +            && $info->{attrs}{accessor} eq 'multi'
 +            && ref $attrs->{$key} eq 'ARRAY') {
 +          my $others = delete $attrs->{$key};
 +          foreach my $rel_obj (@$others) {
 +            if(!Scalar::Util::blessed($rel_obj)) {
 +              $rel_obj = $new->new_related($key, $rel_obj);
 +              $new->{_rel_in_storage} = 0;
 +            }
 +          }
 +          $related->{$key} = $others;
 +          next;
 +        } elsif ($info && $info->{attrs}{accessor}
 +          && $info->{attrs}{accessor} eq 'filter')
          {
 +          ## 'filter' should disappear and get merged in with 'single' above!
 +          my $rel_obj = delete $attrs->{$key};
 +          if(!Scalar::Util::blessed($rel_obj)) {
 +            $rel_obj = $new->find_or_new_related($key, $rel_obj);
 +            $new->{_rel_in_storage} = 0 unless ($rel_obj->in_storage);
 +          }
 +          $inflated->{$key} = $rel_obj;
 +          next;
 +        } elsif ($class->has_column($key)
 +            && $class->column_info($key)->{_inflate_info}) {
            $inflated->{$key} = $attrs->{$key};
            next;
          }
        }
 +      use Data::Dumper;
        $new->throw_exception("No such column $key on $class")
          unless $class->has_column($key);
        $new->store_column($key => $attrs->{$key});          
@@@ -143,60 -98,7 +143,60 @@@ sub insert 
    $self->throw_exception("No result_source set on this object; can't insert")
      unless $source;
  
 +  # Check if we stored uninserted relobjs here in new()
 +  my %related_stuff = (%{$self->{_relationship_data} || {}}, 
 +                       %{$self->{_inflated_column} || {}});
 +  if(!$self->{_rel_in_storage})
 +  {
 +    $source->storage->txn_begin;
 +
 +    ## Should all be in relationship_data, but we need to get rid of the
 +    ## 'filter' reltype..
 +    ## These are the FK rels, need their IDs for the insert.
 +    foreach my $relname (keys %related_stuff) {
 +      my $rel_obj = $related_stuff{$relname};
 +      if(Scalar::Util::blessed($rel_obj) && $rel_obj->isa('DBIx::Class::Row')) {
 +        $rel_obj->insert();
 +        $self->set_from_related($relname, $rel_obj);
 +      }
 +    }
 +  }
 +
    $source->storage->insert($source, { $self->get_columns });
 +
 +  ## PK::Auto
 +  my ($pri, $too_many) = grep { !defined $self->get_column($_) || 
 +                                    ref($self->get_column($_)) eq 'SCALAR'} $self->primary_columns;
 +  if(defined $pri) {
 +    $self->throw_exception( "More than one possible key found for auto-inc on ".ref $self )
 +      if defined $too_many;
 +
 +    my $storage = $self->result_source->storage;
 +    $self->throw_exception( "Missing primary key but Storage doesn't support last_insert_id" )
 +      unless $storage->can('last_insert_id');
 +    my $id = $storage->last_insert_id($self->result_source,$pri);
 +    $self->throw_exception( "Can't get last insert id" ) unless $id;
 +    $self->store_column($pri => $id);
 +  }
 +
 +  if(!$self->{_rel_in_storage})
 +  {
 +    ## Now do the has_many rels, that need $selfs ID.
 +    foreach my $relname (keys %related_stuff) {
 +      my $relobj = $related_stuff{$relname};
 +      if(ref $relobj eq 'ARRAY') {
 +        foreach my $obj (@$relobj) {
 +          my $info = $self->relationship_info($relname);
 +          ## What about multi-col FKs ?
 +          my $key = $1 if($info && (keys %{$info->{cond}})[0] =~ /^foreign\.(\w+)/);
 +          $obj->set_from_related($key, $self);
 +          $obj->insert() if(!$obj->in_storage);
 +        }
 +      }
 +    }
 +    $source->storage->txn_commit;
 +  }
 +
    $self->in_storage(1);
    $self->{_dirty_columns} = {};
    $self->{related_resultsets} = {};
@@@ -250,19 -152,7 +250,19 @@@ sub update 
            my $rel = delete $upd->{$key};
            $self->set_from_related($key => $rel);
            $self->{_relationship_data}{$key} = $rel;          
 -        } 
 +        } elsif ($info && $info->{attrs}{accessor}
 +            && $info->{attrs}{accessor} eq 'multi'
 +            && ref $upd->{$key} eq 'ARRAY') {
 +            my $others = delete $upd->{$key};
 +            foreach my $rel_obj (@$others) {
 +              if(!Scalar::Util::blessed($rel_obj)) {
 +                $rel_obj = $self->create_related($key, $rel_obj);
 +              }
 +            }
 +            $self->{_relationship_data}{$key} = $others; 
 +#            $related->{$key} = $others;
 +            next;
 +        }
          elsif ($self->has_column($key)
            && exists $self->column_info($key)->{_inflate_info})
          {
@@@ -402,6 -292,22 +402,22 @@@ sub get_dirty_columns 
             keys %{$self->{_dirty_columns}};
  }
  
+ =head2 get_inflated_columns
+   my $inflated_data = $obj->get_inflated_columns;
+ Similar to get_columns but objects are returned for inflated columns instead of their raw non-inflated values.
+ =cut
+ sub get_inflated_columns {
+   my $self = shift;
+   return map {
+     my $accessor = $self->column_info($_)->{'accessor'} || $_;
+     ($_ => $self->$accessor);
+   } $self->columns;
+ }
  =head2 set_column
  
    $obj->set_column($col => $val);
@@@ -542,6 -448,7 +558,7 @@@ sub inflate_result 
          $fetched = $pre_source->result_class->inflate_result(
                        $pre_source, @{$pre_val});
        }
+       $new->related_resultset($pre)->set_cache([ $fetched ]);
        my $accessor = $source->relationship_info($pre)->{attrs}{accessor};
        $class->throw_exception("No accessor for prefetched $pre")
         unless defined $accessor;