X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FRow.pm;h=45cff9bfe80cbea57600fafad37e13d51c3f1da3;hb=cf85635700695fcf07a2383270c9c3d0ac1baa73;hp=a6dcbeba5dffa94f5b0fd9756a4bd71b1e91efd2;hpb=d61b2132d3028062a4a2d7cad8a835d6af4477f8;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Row.pm b/lib/DBIx/Class/Row.pm index a6dcbeb..45cff9b 100644 --- a/lib/DBIx/Class/Row.pm +++ b/lib/DBIx/Class/Row.pm @@ -171,9 +171,8 @@ sub new { $new->throw_exception("Can't do multi-create without result source") unless $source; my $info = $source->relationship_info($key); - if ($info && $info->{attrs}{accessor} - && $info->{attrs}{accessor} eq 'single') - { + my $acc_type = $info->{attrs}{accessor} || ''; + if ($acc_type eq 'single') { my $rel_obj = delete $attrs->{$key}; if(!Scalar::Util::blessed($rel_obj)) { $rel_obj = $new->__new_related_find_or_new_helper($key, $rel_obj); @@ -188,9 +187,8 @@ sub new { $related->{$key} = $rel_obj; next; - } elsif ($info && $info->{attrs}{accessor} - && $info->{attrs}{accessor} eq 'multi' - && ref $attrs->{$key} eq 'ARRAY') { + } + elsif ($acc_type eq 'multi' && ref $attrs->{$key} eq 'ARRAY' ) { my $others = delete $attrs->{$key}; my $total = @$others; my @objects; @@ -210,9 +208,8 @@ sub new { } $related->{$key} = \@objects; next; - } elsif ($info && $info->{attrs}{accessor} - && $info->{attrs}{accessor} eq 'filter') - { + } + elsif ($acc_type eq 'filter') { ## 'filter' should disappear and get merged in with 'single' above! my $rel_obj = delete $attrs->{$key}; if(!Scalar::Util::blessed($rel_obj)) { @@ -453,7 +450,7 @@ need to preserve the hashref, it is sufficient to pass a shallow copy to C, e.g. ( { %{ $href } } ) If the values passed or any of the column values set on the object -contain scalar references, eg: +contain scalar references, e.g.: $row->last_modified(\'NOW()'); # OR @@ -480,17 +477,18 @@ this method. sub update { my ($self, $upd) = @_; $self->throw_exception( "Not in database" ) unless $self->in_storage; - my $ident_cond = $self->ident_condition; - $self->throw_exception("Cannot safely update a row in a PK-less table") + + my $ident_cond = $self->{_orig_ident} || $self->ident_condition; + + $self->throw_exception('Unable to update a row with incomplete or no identity') if ! keys %$ident_cond; $self->set_inflated_columns($upd) if $upd; my %to_update = $self->get_dirty_columns; return $self unless keys %to_update; my $rows = $self->result_source->storage->update( - $self->result_source, \%to_update, - $self->{_orig_ident} || $ident_cond - ); + $self->result_source, \%to_update, $ident_cond + ); if ($rows == 0) { $self->throw_exception( "Can't update ${self}: row not found" ); } elsif ($rows > 1) { @@ -498,7 +496,7 @@ sub update { } $self->{_dirty_columns} = {}; $self->{related_resultsets} = {}; - undef $self->{_orig_ident}; + delete $self->{_orig_ident}; return $self; } @@ -527,7 +525,9 @@ attempt is made to delete all the related objects as well. To turn this behaviour off, pass C<< cascade_delete => 0 >> in the C<$attr> hashref of the relationship, see L. Any database-level cascade or restrict will take precedence over a -DBIx-Class-based cascading delete. +DBIx-Class-based cascading delete, since DBIx-Class B and only then attempts to delete any remaining related +rows. If you delete an object within a txn_do() (see L) and the transaction subsequently fails, the row object will remain marked as @@ -545,17 +545,19 @@ sub delete { my $self = shift; if (ref $self) { $self->throw_exception( "Not in database" ) unless $self->in_storage; + my $ident_cond = $self->{_orig_ident} || $self->ident_condition; - $self->throw_exception("Cannot safely delete a row in a PK-less table") + $self->throw_exception('Unable to delete a row with incomplete or no identity') if ! keys %$ident_cond; - foreach my $column (keys %$ident_cond) { - $self->throw_exception("Can't delete the object unless it has loaded the primary keys") - unless exists $self->{_column_data}{$column}; - } + $self->result_source->storage->delete( - $self->result_source, $ident_cond); + $self->result_source, $ident_cond + ); + + delete $self->{_orig_ident}; $self->in_storage(undef); - } else { + } + else { $self->throw_exception("Can't do class delete without a ResultSource instance") unless $self->can('result_source_instance'); my $attrs = @_ > 1 && ref $_[$#_] eq 'HASH' ? { %{pop(@_)} } : {}; @@ -761,9 +763,7 @@ sub get_inflated_columns { for my $col (keys %loaded_colinfo) { if (exists $loaded_colinfo{$col}{accessor}) { my $acc = $loaded_colinfo{$col}{accessor}; - if (defined $acc) { - $inflated{$col} = $self->$acc; - } + $inflated{$col} = $self->$acc if defined $acc; } else { $inflated{$col} = $self->$col; @@ -774,6 +774,22 @@ sub get_inflated_columns { return ($self->get_columns, %inflated); } +sub _is_column_numeric { + my ($self, $column) = @_; + my $colinfo = $self->column_info ($column); + + # cache for speed (the object may *not* have a resultsource instance) + if (not defined $colinfo->{is_numeric} && $self->_source_handle) { + $colinfo->{is_numeric} = + $self->result_source->schema->storage->is_datatype_numeric ($colinfo->{data_type}) + ? 1 + : 0 + ; + } + + return $colinfo->{is_numeric}; +} + =head2 set_column $row->set_column($col => $val); @@ -799,10 +815,11 @@ instead, see L. sub set_column { my ($self, $column, $new_value) = @_; - $self->{_orig_ident} ||= $self->ident_condition; - my $old_value = $self->get_column($column); + # if we can't get an ident condition on first try - mark the object as unidentifiable + $self->{_orig_ident} ||= (eval { $self->ident_condition }) || {}; - $self->store_column($column, $new_value); + my $old_value = $self->get_column($column); + $new_value = $self->store_column($column, $new_value); my $dirty; if (!$self->in_storage) { # no point tracking dirtyness on uninserted data @@ -818,18 +835,7 @@ sub set_column { $dirty = 0; } else { # do a numeric comparison if datatype allows it - my $colinfo = $self->column_info ($column); - - # cache for speed (the object may *not* have a resultsource instance) - if (not defined $colinfo->{is_numeric} && $self->_source_handle) { - $colinfo->{is_numeric} = - $self->result_source->schema->storage->is_datatype_numeric ($colinfo->{data_type}) - ? 1 - : 0 - ; - } - - if ($colinfo->{is_numeric}) { + if ($self->_is_column_numeric($column)) { $dirty = $old_value != $new_value; } else { @@ -896,7 +902,7 @@ Will even accept arrayrefs of data as a value to a L key, and create the related objects if necessary. -Be aware that the input hashref might be edited in place, so dont rely +Be aware that the input hashref might be edited in place, so don't rely on it being the same after a call to C. If you need to preserve the hashref, it is sufficient to pass a shallow copy to C, e.g. ( { %{ $href } } ) @@ -910,21 +916,18 @@ sub set_inflated_columns { foreach my $key (keys %$upd) { if (ref $upd->{$key}) { my $info = $self->relationship_info($key); - if ($info && $info->{attrs}{accessor} - && $info->{attrs}{accessor} eq 'single') - { + my $acc_type = $info->{attrs}{accessor} || ''; + if ($acc_type eq 'single') { 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') { - $self->throw_exception( - "Recursive update is not supported over relationships of type multi ($key)" - ); } - elsif ($self->has_column($key) - && exists $self->column_info($key)->{_inflate_info}) - { + elsif ($acc_type eq 'multi') { + $self->throw_exception( + "Recursive update is not supported over relationships of type '$acc_type' ($key)" + ); + } + elsif ($self->has_column($key) && exists $self->column_info($key)->{_inflate_info}) { $self->set_inflated_column($key, delete $upd->{$key}); } } @@ -953,7 +956,7 @@ so that the database can insert its own autoincremented values into the new object. Relationships will be followed by the copy procedure B if the -relationship specifes a true value for its +relationship specifies a true value for its L attribute. C is set by default on C relationships and unset on all others. @@ -976,7 +979,7 @@ sub copy { $new->insert; # Its possible we'll have 2 relations to the same Source. We need to make - # sure we don't try to insert the same row twice esle we'll violate unique + # sure we don't try to insert the same row twice else we'll violate unique # constraints my $rels_copied = {}; @@ -1063,9 +1066,10 @@ sub inflate_result { my ($source_handle) = $source; if ($source->isa('DBIx::Class::ResultSourceHandle')) { - $source = $source_handle->resolve - } else { - $source_handle = $source->handle + $source = $source_handle->resolve + } + else { + $source_handle = $source->handle } my $new = { @@ -1074,17 +1078,29 @@ sub inflate_result { }; bless $new, (ref $class || $class); - my $schema; foreach my $pre (keys %{$prefetch||{}}) { - my $pre_val = $prefetch->{$pre}; - my $pre_source = $source->related_source($pre); - $class->throw_exception("Can't prefetch non-existent relationship ${pre}") - unless $pre_source; - if (ref($pre_val->[0]) eq 'ARRAY') { # multi - my @pre_objects; - for my $me_pref (@$pre_val) { + my $pre_source = $source->related_source($pre) + or $class->throw_exception("Can't prefetch non-existent relationship ${pre}"); + + my $accessor = $source->relationship_info($pre)->{attrs}{accessor} + or $class->throw_exception("No accessor for prefetched $pre"); + + my @pre_vals; + if (ref $prefetch->{$pre}[0] eq 'ARRAY') { + @pre_vals = @{$prefetch->{$pre}}; + } + elsif ($accessor eq 'multi') { + $class->throw_exception("Implicit prefetch (via select/columns) not supported with accessor 'multi'"); + } + else { + @pre_vals = $prefetch->{$pre}; + } + my @pre_objects; + for my $me_pref (@pre_vals) { + + # FIXME - this should not be necessary # the collapser currently *could* return bogus elements with all # columns set to undef my $has_def; @@ -1099,29 +1115,16 @@ sub inflate_result { push @pre_objects, $pre_source->result_class->inflate_result( $pre_source, @$me_pref ); - } + } - $new->related_resultset($pre)->set_cache(\@pre_objects); - } elsif (defined $pre_val->[0]) { - my $fetched; - unless ($pre_source->primary_columns == grep { exists $pre_val->[0]{$_} - and !defined $pre_val->[0]{$_} } $pre_source->primary_columns) - { - $fetched = $pre_source->result_class->inflate_result( - $pre_source, @{$pre_val}); - } - my $accessor = $source->relationship_info($pre)->{attrs}{accessor}; - $class->throw_exception("No accessor for prefetched $pre") - unless defined $accessor; - if ($accessor eq 'single') { - $new->{_relationship_data}{$pre} = $fetched; - } elsif ($accessor eq 'filter') { - $new->{_inflated_column}{$pre} = $fetched; - } else { - $class->throw_exception("Implicit prefetch (via select/columns) not supported with accessor '$accessor'"); - } - $new->related_resultset($pre)->set_cache([ $fetched ]); + if ($accessor eq 'single') { + $new->{_relationship_data}{$pre} = $pre_objects[0]; + } + elsif ($accessor eq 'filter') { + $new->{_inflated_column}{$pre} = $pre_objects[0]; } + + $new->related_resultset($pre)->set_cache(\@pre_objects); } $new->in_storage (1); @@ -1298,7 +1301,12 @@ sub get_from_storage { $resultset = $resultset->search(undef, $attrs); } - return $resultset->find($self->{_orig_ident} || $self->ident_condition); + my $ident_cond = $self->{_orig_ident} || $self->ident_condition; + + $self->throw_exception('Unable to requery a row with incomplete or no identity') + if ! keys %$ident_cond; + + return $resultset->find($ident_cond); } =head2 discard_changes ($attrs)