X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FRelationship%2FBase.pm;h=b7e74eb580ff809e64af769ad33e716e2affdd7f;hb=367eaf50970dd3fd223ce5e1f0337703f2a6c70e;hp=64536797cf62dd09294b6895f542d40d7951161d;hpb=09d2e66a5d5558ef9a19dc2ec510d5dafd2fb7d8;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Relationship/Base.pm b/lib/DBIx/Class/Relationship/Base.pm index 6453679..b7e74eb 100644 --- a/lib/DBIx/Class/Relationship/Base.pm +++ b/lib/DBIx/Class/Relationship/Base.pm @@ -8,9 +8,16 @@ use base qw/DBIx::Class/; use Scalar::Util qw/weaken blessed/; use DBIx::Class::_Util qw( UNRESOLVABLE_CONDITION DUMMY_ALIASPAIR - fail_on_internal_call + dbic_internal_try dbic_internal_catch fail_on_internal_call ); +use DBIx::Class::SQLMaker::Util 'extract_equality_conditions'; use DBIx::Class::Carp; + +# FIXME - this should go away +# instead Carp::Skip should export usable keywords or something like that +my $unique_carper; +BEGIN { $unique_carper = \&carp_unique } + use namespace::clean; =head1 NAME @@ -524,13 +531,16 @@ sub related_resultset { my $relcond_is_freeform = ref $rel_info->{cond} eq 'CODE'; - my $jfc = $rsrc->_resolve_relationship_condition( - + my $rrc_args = { rel_name => $rel, self_result_object => $self, # an extra sanity check guard - require_join_free_condition => ! $relcond_is_freeform, + require_join_free_condition => !!( + ! $relcond_is_freeform + and + $self->in_storage + ), # an API where these are optional would be too cumbersome, # instead always pass in some dummy values @@ -540,16 +550,48 @@ sub related_resultset { # out of an existing object, with the new source being at the head # of the FROM chain. Having a 'me' alias is nothing but expected there foreign_alias => 'me', + }; - )->{join_free_condition}; + my $jfc = ( + # In certain extraordinary circumstances the relationship resolution may + # throw (e.g. when walking through elaborate custom conds) + # In case the object is "real" (i.e. in_storage) we just go ahead and + # let the exception surface. Otherwise we carp and move on. + # + # The elaborate code-duplicating ternary is there because the xsified + # ->in_storage() is orders of magnitude faster than the Try::Tiny-like + # construct below ( perl's low level tooling is truly shit :/ ) + ( $self->in_storage or DBIx::Class::_Util::in_internal_try ) + ? $rsrc->resolve_relationship_condition($rrc_args)->{join_free_condition} + : dbic_internal_try { + $rsrc->resolve_relationship_condition($rrc_args)->{join_free_condition} + } + dbic_internal_catch { + $unique_carper->( + "Resolution of relationship '$rel' failed unexpectedly, " + . 'please relay the following error and seek assistance via ' + . DBIx::Class::_ENV_::HELP_URL . ". Encountered error: $_" + ); + + # FIXME - this is questionable + # force skipping re-resolution, and instead just return an UC rset + $relcond_is_freeform = 0; + + # RV + undef; + } + ); my $rel_rset; - if ( - ! $jfc - and - $relcond_is_freeform - ) { + if( defined $jfc ) { + + $rel_rset = $rsrc->related_source($rel)->resultset->search_rs( + $jfc, + $rel_info->{attrs}, + ); + } + elsif( $relcond_is_freeform ) { # A WHOREIFFIC hack to reinvoke the entire condition resolution # with the correct alias. Another way of doing this involves a @@ -570,32 +612,27 @@ sub related_resultset { my $obj_table_alias = lc($rsrc->source_name) . '__row'; $obj_table_alias =~ s/\W+/_/g; - $rel_rset = $rsrc->resultset->search( + $rel_rset = $rsrc->resultset->search_rs( $self->ident_condition($obj_table_alias), { alias => $obj_table_alias }, - )->related_resultset('me')->search(undef, $rel_info->{attrs}) + )->related_resultset('me')->search_rs(undef, $rel_info->{attrs}) } else { - # FIXME - this conditional doesn't seem correct - got to figure out - # at some point what it does. Also the entire UNRESOLVABLE_CONDITION - # business seems shady - we could simply not query *at all* - my $attrs; - if ( $jfc eq UNRESOLVABLE_CONDITION ) { - $attrs = { %{$rel_info->{attrs}} }; - my $reverse = $rsrc->reverse_relationship_info($rel); - foreach my $rev_rel (keys %$reverse) { - if ($reverse->{$rev_rel}{attrs}{accessor} && $reverse->{$rev_rel}{attrs}{accessor} eq 'multi') { - weaken($attrs->{related_objects}{$rev_rel}[0] = $self); - } else { - weaken($attrs->{related_objects}{$rev_rel} = $self); - } - } - } + my $attrs = { %{$rel_info->{attrs}} }; + my $reverse = $rsrc->reverse_relationship_info($rel); - $rel_rset = $rsrc->related_source($rel)->resultset->search( - $jfc, - $attrs || $rel_info->{attrs}, + # FIXME - this loop doesn't seem correct - got to figure out + # at some point what exactly it does. + # See also the FIXME at the end of new_related() + ( ( $reverse->{$_}{attrs}{accessor}||'') eq 'multi' ) + ? weaken( $attrs->{related_objects}{$_}[0] = $self ) + : weaken( $attrs->{related_objects}{$_} = $self ) + for keys %$reverse; + + $rel_rset = $rsrc->related_source($rel)->resultset->search_rs( + UNRESOLVABLE_CONDITION, # guards potential use of the $rs in the future + $attrs, ); } @@ -677,16 +714,94 @@ your storage until you call L on it. sub new_related { my ($self, $rel, $data) = @_; - $self->related_resultset($rel)->new_result( $self->result_source->_resolve_relationship_condition ( - infer_values_based_on => $data, + $self->throw_exception( + "Result object instantiation requires a hashref as argument" + ) unless ref $data eq 'HASH'; + + my $rsrc = $self->result_source; + my $rel_rsrc = $rsrc->related_source($rel); + +### +### This section deliberately does not rely on require_join_free_values, +### as quite often the resulting related object is useless without the +### contents of $data mixed in. Originally this code was part of +### resolve_relationship_condition() but given it has a single, very +### context-specific call-site it made no sense to expose it to end users. +### + + my $rel_resolution = $rsrc->resolve_relationship_condition ( rel_name => $rel, self_result_object => $self, - # an API where these are optional would be too cumbersome, - # instead always pass in some dummy values - DUMMY_ALIASPAIR, + # In case we are *not* in_storage it is ok to treat failed resolution as an empty hash + # This happens e.g. as a result of various in-memory related graph of objects + require_join_free_condition => !! $self->in_storage, + + # dummy aliases with deliberately known lengths, so that we can + # quickly strip them below if needed + foreign_alias => 'F', + self_alias => 'S', + ); + + my $rel_values = + $rel_resolution->{join_free_values} + || + { map { substr( $_, 2 ) => $rel_resolution->{join_free_condition}{$_} } keys %{ $rel_resolution->{join_free_condition} } } + ; + + # mix everything together + my $amalgamated_values = { + %{ + # in case we got back join_free_values - they already have passed the extractor + $rel_resolution->{join_free_values} + ? $rel_values + : extract_equality_conditions( + $rel_values, + 'consider_nulls' + ) + }, + %$data, + }; + + # cleanup possible rogue { somecolumn => [ -and => 1,2 ] } + ($amalgamated_values->{$_}||'') eq UNRESOLVABLE_CONDITION + and + delete $amalgamated_values->{$_} + for keys %$amalgamated_values; + + if( my @nonvalues = grep { ! exists $amalgamated_values->{$_} } keys %$rel_values ) { + + $self->throw_exception( + "Unable to complete value inferrence - relationship '$rel' " + . "on source '@{[ $rsrc->source_name ]}' results " + . 'in expression(s) instead of definitive values: ' + . do { + # FIXME - used for diag only, but still icky + my $sqlm = + dbic_internal_try { $rsrc->schema->storage->sql_maker } + || + ( + require DBIx::Class::SQLMaker + and + DBIx::Class::SQLMaker->new + ) + ; + local $sqlm->{quote_char}; + local $sqlm->{_dequalify_idents} = 1; + ($sqlm->_recurse_where({ map { $_ => $rel_values->{$_} } @nonvalues }))[0] + } + ); + } - )->{inferred_values} ); + # And more complications - in case the relationship did not resolve + # we *have* to loop things through search_related ( essentially re-resolving + # everything we did so far, but with different type of handholding ) + # FIXME - this is still a mess, just a *little* better than it was + # See also the FIXME at the end of related_resultset() + exists $rel_resolution->{join_free_values} + ? $rel_rsrc->result_class->new({ -result_source => $rel_rsrc, %$amalgamated_values }) + : $self->related_resultset($rel)->new_result( $amalgamated_values ) + ; } =head2 create_related @@ -832,8 +947,8 @@ L to update them in the storage. sub set_from_related { my ($self, $rel, $f_obj) = @_; - $self->set_columns( $self->result_source->_resolve_relationship_condition ( - infer_values_based_on => {}, + $self->set_columns( $self->result_source->resolve_relationship_condition ( + require_join_free_values => 1, rel_name => $rel, foreign_values => ( # maintain crazy set_from_related interface @@ -868,7 +983,7 @@ sub set_from_related { # instead always pass in some dummy values DUMMY_ALIASPAIR, - )->{inferred_values} ); + )->{join_free_values} ); return 1; }