X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FRow.pm;h=ae89b78a2a5a533a048b55df4b91c9efc526d4d9;hb=1bd54f3d4bc8428d602d2e28cb410b303bb242b7;hp=2aa5b2395148e499acb91e94c4d38eca5c64eb9f;hpb=a5f5e47019daf25c0b0f9708cbd3ab2695584c5a;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Row.pm b/lib/DBIx/Class/Row.pm index 2aa5b23..ae89b78 100644 --- a/lib/DBIx/Class/Row.pm +++ b/lib/DBIx/Class/Row.pm @@ -6,10 +6,12 @@ use warnings; use base qw/DBIx::Class/; use Scalar::Util 'blessed'; -use List::Util 'first'; -use Try::Tiny; +use DBIx::Class::_Util qw( + dbic_internal_try fail_on_internal_call + DUMMY_ALIASPAIR +); use DBIx::Class::Carp; -use DBIx::Class::_Util 'is_literal_value'; +use SQL::Abstract qw( is_literal_value is_plain_value ); ### ### Internal method @@ -52,7 +54,7 @@ All "Row objects" derived from a Schema-attached L object (such as a typical C<< L-> L >> call) are actually Result instances, based on your application's -L. +L. L implements most of the row-based communication with the underlying storage, but a Result class B. @@ -191,13 +193,13 @@ sub new { $rsrc ||= $h->resolve; } - $new->result_source($rsrc) if $rsrc; + $new->result_source_instance($rsrc) if $rsrc; if (my $col_from_rel = delete $attrs->{-cols_from_relations}) { @{$new->{_ignore_at_insert}={}}{@$col_from_rel} = (); } - my ($related,$inflated); + my( $related, $inflated, $colinfos ); foreach my $key (keys %$attrs) { if (ref $attrs->{$key} and ! is_literal_value($attrs->{$key}) ) { @@ -257,14 +259,15 @@ sub new { } $inflated->{$key} = $rel_obj; next; - } elsif ($class->has_column($key) - && $class->column_info($key)->{_inflate_info}) { + } + elsif ( + ( $colinfos ||= $rsrc->columns_info ) + ->{$key}{_inflate_info} + ) { $inflated->{$key} = $attrs->{$key}; next; } } - $new->throw_exception("No such column '$key' on $class") - unless $class->has_column($key); $new->store_column($key => $attrs->{$key}); } @@ -342,7 +345,7 @@ sub insert { $self->throw_exception("No result_source set on this object; can't insert") unless $rsrc; - my $storage = $rsrc->storage; + my $storage = $rsrc->schema->storage; my $rollback_guard; @@ -356,7 +359,7 @@ sub insert { my $rel_obj = $related_stuff{$rel_name}; if (! $self->{_rel_in_storage}{$rel_name}) { - next unless (blessed $rel_obj && $rel_obj->isa('DBIx::Class::Row')); + next unless (blessed $rel_obj && $rel_obj->isa(__PACKAGE__)); next unless $rsrc->_pk_depends_on( $rel_name, { $rel_obj->get_columns } @@ -371,8 +374,7 @@ sub insert { my $existing; # if there are no keys - nothing to search for - if (keys %$them and $existing = $self->result_source - ->related_source($rel_name) + if (keys %$them and $existing = $rsrc->related_source($rel_name) ->resultset ->find($them) ) { @@ -417,7 +419,14 @@ sub insert { or (defined $current_rowdata{$_} xor defined $returned_cols->{$_}) or - (defined $current_rowdata{$_} and $current_rowdata{$_} ne $returned_cols->{$_}) + ( + defined $current_rowdata{$_} + and + # one of the few spots doing forced-stringification + # needed to work around objects with defined stringification + # but *without* overloaded comparison (ugh!) + "$current_rowdata{$_}" ne "$returned_cols->{$_}" + ) ); } @@ -435,7 +444,7 @@ sub insert { : $related_stuff{$rel_name} ; - if (@cands && blessed $cands[0] && $cands[0]->isa('DBIx::Class::Row') + if (@cands && blessed $cands[0] && $cands[0]->isa(__PACKAGE__) ) { my $reverse = $rsrc->reverse_relationship_info($rel_name); foreach my $obj (@cands) { @@ -478,8 +487,8 @@ sub insert { Indicates whether the object exists as a row in the database or not. This is set to true when L, -L or L -are used. +L or L +are invoked. Creating a result object using L, or calling L on one, sets it to false. @@ -549,7 +558,7 @@ sub update { $self->throw_exception( "Not in database" ) unless $self->in_storage; - my $rows = $self->result_source->storage->update( + my $rows = $self->result_source->schema->storage->update( $self->result_source, \%to_update, $self->_storage_ident_condition ); if ($rows == 0) { @@ -611,7 +620,7 @@ sub delete { if (ref $self) { $self->throw_exception( "Not in database" ) unless $self->in_storage; - $self->result_source->storage->delete( + $self->result_source->schema->storage->delete( $self->result_source, $self->_storage_ident_condition ); @@ -619,12 +628,9 @@ sub delete { $self->in_storage(0); } else { - my $rsrc = try { $self->result_source_instance } - or $self->throw_exception("Can't do class delete without a ResultSource instance"); - - my $attrs = @_ > 1 && ref $_[$#_] eq 'HASH' ? { %{pop(@_)} } : {}; + my $attrs = @_ > 1 && ref $_[-1] eq 'HASH' ? { %{pop(@_)} } : {}; my $query = ref $_[0] eq 'HASH' ? $_[0] : {@_}; - $rsrc->resultset->search(@_)->delete; + $self->result_source->resultset->search_rs(@_)->delete; } return $self; } @@ -673,8 +679,8 @@ sub get_column { )); } - $self->throw_exception( "No such column '${column}'" ) - unless $self->has_column($column); + $self->throw_exception( "No such column '${column}' on " . ref $self ) + unless $self->result_source->has_column($column); return undef; } @@ -801,8 +807,8 @@ really changed. sub make_column_dirty { my ($self, $column) = @_; - $self->throw_exception( "No such column '${column}'" ) - unless exists $self->{_column_data}{$column} || $self->has_column($column); + $self->throw_exception( "No such column '${column}' on " . ref $self ) + unless exists $self->{_column_data}{$column} || $self->result_source->has_column($column); # the entire clean/dirty code relies on exists, not on true/false return 1 if exists $self->{_dirty_columns}{$column}; @@ -844,9 +850,9 @@ See L for how to setup inflation. sub get_inflated_columns { my $self = shift; - my $loaded_colinfo = $self->columns_info ([ - grep { $self->has_column_loaded($_) } $self->columns - ]); + my $loaded_colinfo = $self->result_source->columns_info; + $self->has_column_loaded($_) or delete $loaded_colinfo->{$_} + for keys %$loaded_colinfo; my %cols_to_return = ( %{$self->{_column_data}}, %$loaded_colinfo ); @@ -888,14 +894,20 @@ sub get_inflated_columns { } sub _is_column_numeric { - my ($self, $column) = @_; - my $colinfo = $self->column_info ($column); + my ($self, $column) = @_; + + my $rsrc; + + return undef + unless ( $rsrc = $self->result_source )->has_column($column); + + my $colinfo = $rsrc->columns_info->{$column}; # cache for speed (the object may *not* have a resultsource instance) if ( ! defined $colinfo->{is_numeric} and - my $storage = try { $self->result_source->schema->storage } + my $storage = dbic_internal_try { $rsrc->schema->storage } ) { $colinfo->{is_numeric} = $storage->is_datatype_numeric ($colinfo->{data_type}) @@ -940,9 +952,10 @@ sub set_column { my $dirty = $self->{_dirty_columns}{$column} || - $self->in_storage # no point tracking dirtyness on uninserted data + ( $self->in_storage # no point tracking dirtyness on uninserted data ? ! $self->_eq_column_values ($column, $old_value, $new_value) : 1 + ) ; if ($dirty) { @@ -1020,7 +1033,10 @@ sub _eq_column_values { # value tracked between column changes and commitment to storage sub _track_storage_value { my ($self, $col) = @_; - return defined first { $col eq $_ } ($self->primary_columns); + return scalar grep + { $col eq $_ } + $self->result_source->primary_columns + ; } =head2 set_columns @@ -1082,9 +1098,13 @@ See also L. sub set_inflated_columns { my ( $self, $upd ) = @_; + + my ($rsrc, $colinfos); + foreach my $key (keys %$upd) { if (ref $upd->{$key}) { - my $info = $self->relationship_info($key); + $rsrc ||= $self->result_source; + my $info = $rsrc->relationship_info($key); my $acc_type = $info->{attrs}{accessor} || ''; if ($acc_type eq 'single') { @@ -1097,7 +1117,13 @@ sub set_inflated_columns { "Recursive update is not supported over relationships of type '$acc_type' ($key)" ); } - elsif ($self->has_column($key) && exists $self->column_info($key)->{_inflate_info}) { + elsif ( + exists( ( + ( $colinfos ||= $rsrc->columns_info )->{$key} + || + {} + )->{_inflate_info} ) + ) { $self->set_inflated_column($key, delete $upd->{$key}); } } @@ -1135,18 +1161,20 @@ is set by default on C relationships and unset on all others. sub copy { my ($self, $changes) = @_; $changes ||= {}; - my $col_data = { %{$self->{_column_data}} }; + my $col_data = { $self->get_columns }; - my $colinfo = $self->columns_info([ keys %$col_data ]); + my $rsrc = $self->result_source; + + my $colinfo = $rsrc->columns_info; foreach my $col (keys %$col_data) { delete $col_data->{$col} - if $colinfo->{$col}{is_auto_increment}; + if ( ! $colinfo->{$col} or $colinfo->{$col}{is_auto_increment} ); } my $new = { _column_data => $col_data }; bless $new, ref $self; - $new->result_source($self->result_source); + $new->result_source_instance($rsrc); $new->set_inflated_columns($changes); $new->insert; @@ -1155,23 +1183,27 @@ sub copy { # constraints my $rel_names_copied = {}; - foreach my $rel_name ($self->result_source->relationships) { - my $rel_info = $self->result_source->relationship_info($rel_name); + foreach my $rel_name ($rsrc->relationships) { + my $rel_info = $rsrc->relationship_info($rel_name); next unless $rel_info->{attrs}{cascade_copy}; - my $resolved = $self->result_source->_resolve_condition( - $rel_info->{cond}, $rel_name, $new, $rel_name - ); - + my $foreign_vals; my $copied = $rel_names_copied->{ $rel_info->{source} } ||= {}; - foreach my $related ($self->search_related($rel_name)->all) { - my $id_str = join("\0", $related->id); - next if $copied->{$id_str}; - $copied->{$id_str} = 1; - my $rel_copy = $related->copy($resolved); - } + $copied->{$_->ID}++ or $_->copy( + + $foreign_vals ||= $rsrc->_resolve_relationship_condition( + require_join_free_values => 1, + rel_name => $rel_name, + self_result_object => $new, + + # an API where these are optional would be too cumbersome, + # instead always pass in some dummy values + DUMMY_ALIASPAIR, + )->{join_free_values} + + ) for $self->related_resultset($rel_name)->all; } return $new; } @@ -1199,11 +1231,18 @@ extend this method to catch all data setting methods. sub store_column { my ($self, $column, $value) = @_; - $self->throw_exception( "No such column '${column}'" ) - unless exists $self->{_column_data}{$column} || $self->has_column($column); + $self->throw_exception( "No such column '${column}' on " . ref $self ) + unless exists $self->{_column_data}{$column} || $self->result_source->has_column($column); $self->throw_exception( "set_column called for ${column} without value" ) if @_ < 3; - return $self->{_column_data}{$column} = $value; + + my $vref; + $self->{_column_data}{$column} = ( + # unpack potential { -value => "foo" } + ( length ref $value and $vref = is_plain_value( $value ) ) + ? $$vref + : $value + ); } =head2 inflate_result @@ -1313,7 +1352,7 @@ sub inflate_result { =back -Ls the object if it's already in the database, according to +Ls the object if it's already in the database, according to L, else Ls it. =head2 insert_or_update @@ -1324,7 +1363,10 @@ Alias for L =cut -sub insert_or_update { shift->update_or_insert(@_) } +sub insert_or_update :DBIC_method_is_indirect_sugar { + DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call; + shift->update_or_insert(@_); +} sub update_or_insert { my $self = shift; @@ -1391,23 +1433,23 @@ Accessor to the L this object was created from. =cut -sub result_source { - $_[0]->throw_exception( 'result_source can be called on instances only' ) - unless ref $_[0]; - +sub result_source :DBIC_method_is_indirect_sugar { + # While getter calls are routed through here for sensible exception text + # it makes no sense to have setters do the same thing + DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS + and @_ > 1 - ? $_[0]->{_result_source} = $_[1] - - # note this is a || not a ||=, the difference is important - : $_[0]->{_result_source} || do { - my $class = ref $_[0]; - $_[0]->can('result_source_instance') - ? $_[0]->result_source_instance - : $_[0]->throw_exception( - "No result source instance registered for $class, did you forget to call $class->table(...) ?" - ) - } - ; + and + fail_on_internal_call; + + # this is essentially a `shift->result_source_instance(@_)` with handholding + &{ + $_[0]->can('result_source_instance') + || + $_[0]->throw_exception( + "No ResultSource instance registered for '@{[ $_[0] ]}', did you forget to call @{[ ref $_[0] || $_[0] ]}->table(...) ?" + ) + }; } =head2 register_column @@ -1553,8 +1595,13 @@ See L. sub throw_exception { my $self=shift; - if (ref $self && ref $self->result_source ) { - $self->result_source->throw_exception(@_) + if ( + ! DBIx::Class::_Util::in_internal_try + and + # FIXME - the try is 99% superfluous, but just in case + my $rsrc = dbic_internal_try { $self->result_source_instance } + ) { + $rsrc->throw_exception(@_) } else { DBIx::Class::Exception->throw(@_); @@ -1576,13 +1623,16 @@ sub throw_exception { Returns the primary key(s) for a row. Can't be called as a class method. Actually implemented in L -=head1 AUTHOR AND CONTRIBUTORS +=head1 FURTHER QUESTIONS? -See L and L in DBIx::Class +Check the list of L. -=head1 LICENSE +=head1 COPYRIGHT AND LICENSE -You may distribute this code under the same terms as Perl itself. +This module is free software L +by the L. You can +redistribute it and/or modify it under the same terms as the +L. =cut