X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=dbsrgits%2FDBIx-Class.git;a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FResultSource.pm;h=fe82facd4d18b878979f333115ff19be3a0a38b0;hp=9468e95aee1a6b22fb8a32ff768864c6e3e180d6;hb=6bf7e59400fd71ec3d3e27155b354ee2369e49c4;hpb=5b9ecfccf4d159b155c80fff6f12b701651f9f09 diff --git a/lib/DBIx/Class/ResultSource.pm b/lib/DBIx/Class/ResultSource.pm index 9468e95..fe82fac 100644 --- a/lib/DBIx/Class/ResultSource.pm +++ b/lib/DBIx/Class/ResultSource.pm @@ -7,22 +7,28 @@ use DBIx::Class::ResultSet; use DBIx::Class::ResultSourceHandle; use DBIx::Class::Exception; -use Carp::Clan qw/^DBIx::Class/; +use DBIx::Class::Carp; +use Devel::GlobalDestruction; use Try::Tiny; use List::Util 'first'; -use Scalar::Util qw/weaken isweak/; -use Storable qw/nfreeze thaw/; +use Scalar::Util qw/blessed weaken isweak/; use namespace::clean; use base qw/DBIx::Class/; -__PACKAGE__->mk_group_accessors('simple' => qw/_ordered_columns - _columns _primaries _unique_constraints name resultset_attributes - from _relationships column_info_from_storage source_info - source_name sqlt_deploy_callback/); +__PACKAGE__->mk_group_accessors(simple => qw/ + source_name name source_info + _ordered_columns _columns _primaries _unique_constraints + _relationships resultset_attributes + column_info_from_storage +/); -__PACKAGE__->mk_group_accessors('component_class' => qw/resultset_class - result_class/); +__PACKAGE__->mk_group_accessors(component_class => qw/ + resultset_class + result_class +/); + +__PACKAGE__->mk_classdata( sqlt_deploy_callback => 'default_sqlt_deploy_hook' ); =head1 NAME @@ -32,18 +38,18 @@ DBIx::Class::ResultSource - Result source object # Create a table based result source, in a result class. - package MyDB::Schema::Result::Artist; + package MyApp::Schema::Result::Artist; use base qw/DBIx::Class::Core/; __PACKAGE__->table('artist'); __PACKAGE__->add_columns(qw/ artistid name /); __PACKAGE__->set_primary_key('artistid'); - __PACKAGE__->has_many(cds => 'MyDB::Schema::Result::CD'); + __PACKAGE__->has_many(cds => 'MyApp::Schema::Result::CD'); 1; # Create a query (view) based result source, in a result class - package MyDB::Schema::Result::Year2000CDs; + package MyApp::Schema::Result::Year2000CDs; use base qw/DBIx::Class::Core/; __PACKAGE__->load_components('InflateColumn::DateTime'); @@ -88,7 +94,7 @@ You can retrieve the result source at runtime in the following ways: $schema->source($source_name); -=item From a Row object: +=item From a Result object: $row->result_source; @@ -116,7 +122,6 @@ sub new { $new->{_relationships} = { %{$new->{_relationships}||{}} }; $new->{name} ||= "!!NAME NOT SET!!"; $new->{_columns_info_loaded} ||= 0; - $new->{sqlt_deploy_callback} ||= "default_sqlt_deploy_hook"; return $new; } @@ -128,7 +133,7 @@ sub new { =item Arguments: @columns -=item Return value: The ResultSource object +=item Return Value: L<$result_source|/new> =back @@ -141,7 +146,7 @@ pairs, uses the hashref as the L for that column. Repeated calls of this method will add more columns, not replace them. The column names given will be created as accessor methods on your -L objects. You can change the name of the accessor +L objects. You can change the name of the accessor by supplying an L in the column_info hash. If a column name beginning with a plus sign ('+col1') is provided, the @@ -254,8 +259,20 @@ generate a new key value. If not specified, L will attempt to retrieve the name of the sequence from the database automatically. +=item retrieve_on_insert + + { retrieve_on_insert => 1 } + +For every column where this is set to true, DBIC will retrieve the RDBMS-side +value upon a new row insertion (normally only the autoincrement PK is +retrieved on insert). C is used automatically if +supported by the underlying storage, otherwise an extra SELECT statement is +executed to retrieve the missing data. + =item auto_nextval + { auto_nextval => 1 } + Set this to a true value for a column whose value is retrieved automatically from a sequence or function (if supported by your Storage driver.) For a sequence, if you do not use a trigger to get the nextval, you have to set the @@ -282,7 +299,7 @@ L. =item Arguments: $colname, \%columninfo? -=item Return value: 1/0 (true/false) +=item Return Value: 1/0 (true/false) =back @@ -326,7 +343,7 @@ sub add_column { shift->add_columns(@_); } # DO NOT CHANGE THIS TO GLOB =item Arguments: $colname -=item Return value: 1/0 (true/false) +=item Return Value: 1/0 (true/false) =back @@ -347,7 +364,7 @@ sub has_column { =item Arguments: $colname -=item Return value: Hashref of info +=item Return Value: Hashref of info =back @@ -395,9 +412,9 @@ sub column_info { =over -=item Arguments: None +=item Arguments: none -=item Return value: Ordered list of column names +=item Return Value: Ordered list of column names =back @@ -421,14 +438,14 @@ sub columns { =item Arguments: \@colnames ? -=item Return value: Hashref of column name/info pairs +=item Return Value: Hashref of column name/info pairs =back my $columns_info = $source->columns_info; Like L but returns information for the requested columns. If -the optional column-list arrayref is ommitted it returns info on all columns +the optional column-list arrayref is omitted it returns info on all columns currently defined on the ResultSource via L. =cut @@ -495,7 +512,7 @@ sub columns_info { =item Arguments: @colnames -=item Return value: undefined +=item Return Value: not defined =back @@ -513,7 +530,7 @@ broken result source. =item Arguments: $colname -=item Return value: undefined +=item Return Value: not defined =back @@ -551,7 +568,7 @@ sub remove_column { shift->remove_columns(@_); } # DO NOT CHANGE THIS TO GLOB =item Arguments: @cols -=item Return value: undefined +=item Return Value: not defined =back @@ -585,9 +602,9 @@ sub set_primary_key { =over 4 -=item Arguments: None +=item Arguments: none -=item Return value: Ordered list of primary column names +=item Return Value: Ordered list of primary column names =back @@ -624,7 +641,7 @@ will be applied to the L of each L =item Arguments: $sequence_name -=item Return value: undefined +=item Return Value: not defined =back @@ -634,7 +651,7 @@ sub sequence { my ($self,$seq) = @_; my @pks = $self->primary_columns - or next; + or return; $_->{sequence} = $seq for values %{ $self->columns_info (\@pks) }; @@ -647,7 +664,7 @@ sub sequence { =item Arguments: $name?, \@colnames -=item Return value: undefined +=item Return Value: not defined =back @@ -713,7 +730,7 @@ sub add_unique_constraint { =item Arguments: @constraints -=item Return value: undefined +=item Return Value: not defined =back @@ -765,7 +782,7 @@ sub add_unique_constraints { =item Arguments: \@colnames -=item Return value: Constraint name +=item Return Value: Constraint name =back @@ -799,9 +816,9 @@ sub name_unique_constraint { =over 4 -=item Arguments: None +=item Arguments: none -=item Return value: Hash of unique constraint data +=item Return Value: Hash of unique constraint data =back @@ -823,9 +840,9 @@ sub unique_constraints { =over 4 -=item Arguments: None +=item Arguments: none -=item Return value: Unique constraint names +=item Return Value: Unique constraint names =back @@ -849,7 +866,7 @@ sub unique_constraint_names { =item Arguments: $constraintname -=item Return value: List of constraint columns +=item Return Value: List of constraint columns =back @@ -875,12 +892,21 @@ sub unique_constraint_columns { =over -=item Arguments: $callback +=item Arguments: $callback_name | \&callback_code + +=item Return Value: $callback_name | \&callback_code =back __PACKAGE__->sqlt_deploy_callback('mycallbackmethod'); + or + + __PACKAGE__->sqlt_deploy_callback(sub { + my ($source_instance, $sqlt_table) = @_; + ... + } ); + An accessor to set a callback to be called during deployment of the schema via L or L. @@ -888,7 +914,7 @@ L. The callback can be set as either a code reference or the name of a method in the current result class. -If not set, the L is called. +Defaults to L. Your callback will be passed the $source object representing the ResultSource instance being deployed, and the @@ -908,19 +934,13 @@ and call L. =head2 default_sqlt_deploy_hook -=over - -=item Arguments: $source, $sqlt_table - -=item Return value: undefined - -=back - -This is the sensible default for L. - -If a method named C exists in your Result class, it -will be called and passed the current C<$source> and the -C<$sqlt_table> being deployed. +This is the default deploy hook implementation which checks if your +current Result class has a C method, and if present +invokes it B. This is to preserve the +semantics of C which was originally designed to expect +the Result class name and the +L<$sqlt_table instance|SQL::Translator::Schema::Table> of the table being +deployed. =cut @@ -941,13 +961,39 @@ sub _invoke_sqlt_deploy_hook { } } +=head2 result_class + +=over 4 + +=item Arguments: $classname + +=item Return Value: $classname + +=back + + use My::Schema::ResultClass::Inflator; + ... + + use My::Schema::Artist; + ... + __PACKAGE__->result_class('My::Schema::ResultClass::Inflator'); + +Set the default result class for this source. You can use this to create +and use your own result inflator. See L +for more details. + +Please note that setting this to something like +L will make every result unblessed +and make life more difficult. Inflators like those are better suited to +temporary usage via L. + =head2 resultset =over 4 -=item Arguments: None +=item Arguments: none -=item Return value: $resultset +=item Return Value: L<$resultset|DBIx::Class::ResultSet> =back @@ -964,7 +1010,7 @@ but is cached from then on unless resultset_class changes. =item Arguments: $classname -=item Return value: $classname +=item Return Value: $classname =back @@ -988,9 +1034,9 @@ exists. =over 4 -=item Arguments: \%attrs +=item Arguments: L<\%attrs|DBIx::Class::ResultSet/ATTRIBUTES> -=item Return value: \%attrs +=item Return Value: L<\%attrs|DBIx::Class::ResultSet/ATTRIBUTES> =back @@ -1001,8 +1047,35 @@ exists. $source->resultset_attributes({ order_by => [ 'id' ] }); Store a collection of resultset attributes, that will be set on every -L produced from this result source. For a full -list see L. +L produced from this result source. + +B: C comes with its own set of issues and +bugs! While C isn't deprecated per se, its usage is +not recommended! + +Since relationships use attributes to link tables together, the "default" +attributes you set may cause unpredictable and undesired behavior. Furthermore, +the defaults cannot be turned off, so you are stuck with them. + +In most cases, what you should actually be using are project-specific methods: + + package My::Schema::ResultSet::Artist; + use base 'DBIx::Class::ResultSet'; + ... + + # BAD IDEA! + #__PACKAGE__->resultset_attributes({ prefetch => 'tracks' }); + + # GOOD IDEA! + sub with_tracks { shift->search({}, { prefetch => 'tracks' }) } + + # in your code + $schema->resultset('Artist')->with_tracks->... + +This gives you the flexibility of not using it when you don't need it. + +For more complex situations, another solution would be to use a virtual view +via L. =cut @@ -1022,6 +1095,20 @@ sub resultset { ); } +=head2 name + +=over 4 + +=item Arguments: none + +=item Result value: $name + +=back + +Returns the name of the result source, which will typically be the table +name. This may be a scalar reference if the result source has a non-standard +name. + =head2 source_name =over 4 @@ -1048,9 +1135,9 @@ its class name. =over 4 -=item Arguments: None +=item Arguments: none -=item Return value: FROM clause +=item Return Value: FROM clause =back @@ -1060,13 +1147,17 @@ Returns an expression of the source to be supplied to storage to specify retrieval from this source. In the case of a database, the required FROM clause contents. +=cut + +sub from { die 'Virtual method!' } + =head2 schema =over 4 -=item Arguments: $schema +=item Arguments: L<$schema?|DBIx::Class::Schema> -=item Return value: A schema object +=item Return Value: L<$schema|DBIx::Class::Schema> =back @@ -1100,17 +1191,15 @@ sub schema { =over 4 -=item Arguments: None +=item Arguments: none -=item Return value: A Storage object +=item Return Value: L<$storage|DBIx::Class::Storage> =back $source->storage->debug(1); -Returns the storage handle for the current schema. - -See also: L +Returns the L for the current schema. =cut @@ -1120,13 +1209,13 @@ sub storage { shift->schema->storage; } =over 4 -=item Arguments: $relname, $related_source_name, \%cond, [ \%attrs ] +=item Arguments: $rel_name, $related_source_name, \%cond, \%attrs? -=item Return value: 1/true if it succeeded +=item Return Value: 1/true if it succeeded =back - $source->add_relationship('relname', 'related_source', $cond, $attrs); + $source->add_relationship('rel_name', 'related_source', $cond, $attrs); L describes a series of methods which create pre-defined useful types of relationships. Look there first @@ -1246,9 +1335,9 @@ sub add_relationship { =over 4 -=item Arguments: None +=item Arguments: none -=item Return value: List of relationship names +=item Return Value: L<@rel_names|DBIx::Class::Relationship> =back @@ -1266,14 +1355,14 @@ sub relationships { =over 4 -=item Arguments: $relname +=item Arguments: L<$rel_name|DBIx::Class::Relationship> -=item Return value: Hashref of relation data, +=item Return Value: L<\%rel_data|DBIx::Class::Relationship::Base/add_relationship> =back Returns a hash of relationship information for the specified relationship -name. The keys/values are as specified for L. +name. The keys/values are as specified for L. =cut @@ -1286,9 +1375,9 @@ sub relationship_info { =over 4 -=item Arguments: $rel +=item Arguments: L<$rel_name|DBIx::Class::Relationship> -=item Return value: 1/0 (true/false) +=item Return Value: 1/0 (true/false) =back @@ -1305,9 +1394,9 @@ sub has_relationship { =over 4 -=item Arguments: $relname +=item Arguments: L<$rel_name|DBIx::Class::Relationship> -=item Return value: Hashref of relationship data +=item Return Value: L<\%rel_data|DBIx::Class::Relationship::Base/add_relationship> =back @@ -1327,56 +1416,74 @@ L. sub reverse_relationship_info { my ($self, $rel) = @_; - my $rel_info = $self->relationship_info($rel); + + my $rel_info = $self->relationship_info($rel) + or $self->throw_exception("No such relationship '$rel'"); + my $ret = {}; return $ret unless ((ref $rel_info->{cond}) eq 'HASH'); - my @cond = keys(%{$rel_info->{cond}}); - my @refkeys = map {/^\w+\.(\w+)$/} @cond; - my @keys = map {$rel_info->{cond}->{$_} =~ /^\w+\.(\w+)$/} @cond; + my $stripped_cond = $self->__strip_relcond ($rel_info->{cond}); - # Get the related result source for this relationship - my $othertable = $self->related_source($rel); + my $rsrc_schema_moniker = $self->source_name + if try { $self->schema }; + + # this may be a partial schema or something else equally esoteric + my $other_rsrc = try { $self->related_source($rel) } + or return $ret; # Get all the relationships for that source that related to this source # whose foreign column set are our self columns on $rel and whose self - # columns are our foreign columns on $rel. - my @otherrels = $othertable->relationships(); - my $otherrelationship; - foreach my $otherrel (@otherrels) { - # this may be a partial schema with the related source not being - # available at all - my $back = try { $othertable->related_source($otherrel) } or next; - - # did we get back to ourselves? - next unless $back->source_name eq $self->source_name; - - my $otherrel_info = $othertable->relationship_info($otherrel); - my @othertestconds; - - if (ref $otherrel_info->{cond} eq 'HASH') { - @othertestconds = ($otherrel_info->{cond}); - } - elsif (ref $otherrel_info->{cond} eq 'ARRAY') { - @othertestconds = @{$otherrel_info->{cond}}; + # columns are our foreign columns on $rel + foreach my $other_rel ($other_rsrc->relationships) { + + # only consider stuff that points back to us + # "us" here is tricky - if we are in a schema registration, we want + # to use the source_names, otherwise we will use the actual classes + + # the schema may be partial + my $roundtrip_rsrc = try { $other_rsrc->related_source($other_rel) } + or next; + + if ($rsrc_schema_moniker and try { $roundtrip_rsrc->schema } ) { + next unless $rsrc_schema_moniker eq $roundtrip_rsrc->source_name; } else { - next; + next unless $self->result_class eq $roundtrip_rsrc->result_class; } - foreach my $othercond (@othertestconds) { - my @other_cond = keys(%$othercond); - my @other_refkeys = map {/^\w+\.(\w+)$/} @other_cond; - my @other_keys = map {$othercond->{$_} =~ /^\w+\.(\w+)$/} @other_cond; - next if (!$self->_compare_relationship_keys(\@refkeys, \@other_keys) || - !$self->_compare_relationship_keys(\@other_refkeys, \@keys)); - $ret->{$otherrel} = $otherrel_info; - } + my $other_rel_info = $other_rsrc->relationship_info($other_rel); + + # this can happen when we have a self-referential class + next if $other_rel_info eq $rel_info; + + next unless ref $other_rel_info->{cond} eq 'HASH'; + my $other_stripped_cond = $self->__strip_relcond($other_rel_info->{cond}); + + $ret->{$other_rel} = $other_rel_info if ( + $self->_compare_relationship_keys ( + [ keys %$stripped_cond ], [ values %$other_stripped_cond ] + ) + and + $self->_compare_relationship_keys ( + [ values %$stripped_cond ], [ keys %$other_stripped_cond ] + ) + ); } + return $ret; } +# all this does is removes the foreign/self prefix from a condition +sub __strip_relcond { + +{ + map + { map { /^ (?:foreign|self) \. (\w+) $/x } ($_, $_[1]{$_}) } + keys %{$_[1]} + } +} + sub compare_relationship_keys { carp 'compare_relationship_keys is a private method, stop calling it'; my $self = shift; @@ -1385,36 +1492,38 @@ sub compare_relationship_keys { # Returns true if both sets of keynames are the same, false otherwise. sub _compare_relationship_keys { - my ($self, $keys1, $keys2) = @_; - - # Make sure every keys1 is in keys2 - my $found; - foreach my $key (@$keys1) { - $found = 0; - foreach my $prim (@$keys2) { - if ($prim eq $key) { - $found = 1; - last; - } - } - last unless $found; - } +# my ($self, $keys1, $keys2) = @_; + return + join ("\x00", sort @{$_[1]}) + eq + join ("\x00", sort @{$_[2]}) + ; +} - # Make sure every key2 is in key1 - if ($found) { - foreach my $prim (@$keys2) { - $found = 0; - foreach my $key (@$keys1) { - if ($prim eq $key) { - $found = 1; - last; - } - } - last unless $found; +# optionally takes either an arrayref of column names, or a hashref of already +# retrieved colinfos +# returns an arrayref of column names of the shortest unique constraint +# (matching some of the input if any), giving preference to the PK +sub _identifying_column_set { + my ($self, $cols) = @_; + + my %unique = $self->unique_constraints; + my $colinfos = ref $cols eq 'HASH' ? $cols : $self->columns_info($cols||()); + + # always prefer the PK first, and then shortest constraints first + USET: + for my $set (delete $unique{primary}, sort { @$a <=> @$b } (values %unique) ) { + next unless $set && @$set; + + for (@$set) { + next USET unless ($colinfos->{$_} && !$colinfos->{$_}{is_nullable} ); } + + # copy so we can mangle it at will + return [ @$set ]; } - return $found; + return undef; } # Returns the {from} structure used to express JOIN conditions @@ -1430,7 +1539,7 @@ sub _resolve_join { $jpath = [@$jpath]; # copy - if (not defined $join) { + if (not defined $join or not length $join) { return (); } elsif (ref $join eq 'ARRAY') { @@ -1493,7 +1602,8 @@ sub _resolve_join { -alias => $as, -relation_chain_depth => $seen->{-relation_chain_depth} || 0, }, - $self->_resolve_condition($rel_info->{cond}, $as, $alias) ]; + scalar $self->_resolve_condition($rel_info->{cond}, $as, $alias, $join) + ]; } } @@ -1507,9 +1617,9 @@ sub pk_depends_on { # having already been inserted. Takes the name of the relationship and a # hashref of columns of the related object. sub _pk_depends_on { - my ($self, $relname, $rel_data) = @_; + my ($self, $rel_name, $rel_data) = @_; - my $relinfo = $self->relationship_info($relname); + my $relinfo = $self->relationship_info($rel_name); # don't assume things if the relationship direction is specified return $relinfo->{attrs}{is_foreign_key_constraint} @@ -1524,7 +1634,7 @@ sub _pk_depends_on { # assume anything that references our PK probably is dependent on us # rather than vice versa, unless the far side is (a) defined or (b) # auto-increment - my $rel_source = $self->related_source($relname); + my $rel_source = $self->related_source($rel_name); foreach my $p ($self->primary_columns) { if (exists $keyhash->{$p}) { @@ -1545,14 +1655,89 @@ sub resolve_condition { $self->_resolve_condition (@_); } -# Resolves the passed condition to a concrete query fragment. If given an alias, -# returns a join condition; if given an object, inverts that object to produce -# a related conditional from that object. -our $UNRESOLVABLE_CONDITION = \'1 = 0'; +our $UNRESOLVABLE_CONDITION = \ '1 = 0'; +# Resolves the passed condition to a concrete query fragment and a flag +# indicating whether this is a cross-table condition. Also an optional +# list of non-triviail values (notmally conditions) returned as a part +# of a joinfree condition hash sub _resolve_condition { - my ($self, $cond, $as, $for) = @_; - if (ref $cond eq 'HASH') { + my ($self, $cond, $as, $for, $rel_name) = @_; + + my $obj_rel = !!blessed $for; + + if (ref $cond eq 'CODE') { + my $relalias = $obj_rel ? 'me' : $as; + + my ($crosstable_cond, $joinfree_cond) = $cond->({ + self_alias => $obj_rel ? $as : $for, + foreign_alias => $relalias, + self_resultsource => $self, + foreign_relname => $rel_name || ($obj_rel ? $as : $for), + self_rowobj => $obj_rel ? $for : undef + }); + + my $cond_cols; + if ($joinfree_cond) { + + # FIXME sanity check until things stabilize, remove at some point + $self->throw_exception ( + "A join-free condition returned for relationship '$rel_name' without a row-object to chain from" + ) unless $obj_rel; + + # FIXME another sanity check + if ( + ref $joinfree_cond ne 'HASH' + or + first { $_ !~ /^\Q$relalias.\E.+/ } keys %$joinfree_cond + ) { + $self->throw_exception ( + "The join-free condition returned for relationship '$rel_name' must be a hash " + .'reference with all keys being valid columns on the related result source' + ); + } + + # normalize + for (values %$joinfree_cond) { + $_ = $_->{'='} if ( + ref $_ eq 'HASH' + and + keys %$_ == 1 + and + exists $_->{'='} + ); + } + + # see which parts of the joinfree cond are conditionals + my $relcol_list = { map { $_ => 1 } $self->related_source($rel_name)->columns }; + + for my $c (keys %$joinfree_cond) { + my ($colname) = $c =~ /^ (?: \Q$relalias.\E )? (.+)/x; + + unless ($relcol_list->{$colname}) { + push @$cond_cols, $colname; + next; + } + + if ( + ref $joinfree_cond->{$c} + and + ref $joinfree_cond->{$c} ne 'SCALAR' + and + ref $joinfree_cond->{$c} ne 'REF' + ) { + push @$cond_cols, $colname; + next; + } + } + + return wantarray ? ($joinfree_cond, 0, $cond_cols) : $joinfree_cond; + } + else { + return wantarray ? ($crosstable_cond, 1) : $crosstable_cond; + } + } + elsif (ref $cond eq 'HASH') { my %ret; foreach my $k (keys %{$cond}) { my $v = $cond->{$k}; @@ -1589,28 +1774,38 @@ sub _resolve_condition { } elsif (!defined $as) { # undef, i.e. "no reverse object" $ret{$v} = undef; } else { - $ret{"${as}.${k}"} = "${for}.${v}"; + $ret{"${as}.${k}"} = { -ident => "${for}.${v}" }; } } - return \%ret; - } elsif (ref $cond eq 'ARRAY') { - return [ map { $self->_resolve_condition($_, $as, $for) } @$cond ]; - } else { - die("Can't handle condition $cond yet :("); + + return wantarray + ? ( \%ret, ($obj_rel || !defined $as || ref $as) ? 0 : 1 ) + : \%ret + ; + } + elsif (ref $cond eq 'ARRAY') { + my (@ret, $crosstable); + for (@$cond) { + my ($cond, $crosstab) = $self->_resolve_condition($_, $as, $for, $rel_name); + push @ret, $cond; + $crosstable ||= $crosstab; + } + return wantarray ? (\@ret, $crosstable) : \@ret; + } + else { + $self->throw_exception ("Can't handle condition $cond for relationship '$rel_name' yet :("); } } - # Accepts one or more relationships for the current source and returns an # array of column names for each of those relationships. Column names are # prefixed relative to the current source, in accordance with where they appear # in the supplied relationships. - sub _resolve_prefetch { my ($self, $pre, $alias, $alias_map, $order, $collapse, $pref_path) = @_; $pref_path ||= []; - if (not defined $pre) { + if (not defined $pre or not length $pre) { return (); } elsif( ref $pre eq 'ARRAY' ) { @@ -1653,6 +1848,7 @@ sub _resolve_prefetch { "Can't prefetch has_many ${pre} (join cond too complex)") unless ref($rel_info->{cond}) eq 'HASH'; my $dots = @{[$as_prefix =~ m/\./g]} + 1; # +1 to match the ".${as_prefix}" + if (my ($fail) = grep { @{[$_ =~ m/\./g]} == $dots } keys %{$collapse}) { my ($last) = ($fail =~ /([^\.]+)$/); @@ -1666,6 +1862,7 @@ sub _resolve_prefetch { . 'Use at your own risk.' ); } + #my @col = map { (/^self\.(.+)$/ ? ("${as_prefix}.$1") : ()); } # values %{$rel_info->{cond}}; $collapse->{".${as_prefix}${pre}"} = [ $rel_source->_pri_cols ]; @@ -1711,9 +1908,9 @@ sub _resolve_prefetch { =over 4 -=item Arguments: $relname +=item Arguments: $rel_name -=item Return value: $source +=item Return Value: $source =back @@ -1726,16 +1923,27 @@ sub related_source { if( !$self->has_relationship( $rel ) ) { $self->throw_exception("No such relationship '$rel' on " . $self->source_name); } - return $self->schema->source($self->relationship_info($rel)->{source}); + + # if we are not registered with a schema - just use the prototype + # however if we do have a schema - ask for the source by name (and + # throw in the process if all fails) + if (my $schema = try { $self->schema }) { + $schema->source($self->relationship_info($rel)->{source}); + } + else { + my $class = $self->relationship_info($rel)->{class}; + $self->ensure_class_loaded($class); + $class->result_source_instance; + } } =head2 related_class =over 4 -=item Arguments: $relname +=item Arguments: $rel_name -=item Return value: $classname +=item Return Value: $classname =back @@ -1755,9 +1963,9 @@ sub related_class { =over 4 -=item Arguments: None +=item Arguments: none -=item Return value: $source_handle +=item Return Value: L<$source_handle|DBIx::Class::ResultSourceHandle> =back @@ -1780,13 +1988,9 @@ sub handle { }); } -{ - my $global_phase_destroy; - - END { $global_phase_destroy++ } - - sub DESTROY { - return if $global_phase_destroy; +my $global_phase_destroy; +sub DESTROY { + return if $global_phase_destroy ||= in_global_destruction; ###### # !!! ACHTUNG !!!! @@ -1798,31 +2002,44 @@ sub handle { # we are trying to save to reattach back to the source we are destroying. # The relevant code checking refcounts is in ::Schema::DESTROY() - # if we are not a schema instance holder - we don't matter - return if( - ! ref $_[0]->{schema} - or - isweak $_[0]->{schema} - ); + # if we are not a schema instance holder - we don't matter + return if( + ! ref $_[0]->{schema} + or + isweak $_[0]->{schema} + ); - # weaken our schema hold forcing the schema to find somewhere else to live + # weaken our schema hold forcing the schema to find somewhere else to live + # during global destruction (if we have not yet bailed out) this will throw + # which will serve as a signal to not try doing anything else + # however beware - on older perls the exception seems randomly untrappable + # due to some weird race condition during thread joining :((( + local $@; + eval { weaken $_[0]->{schema}; - # if schema is still there reintroduce ourselves with strong refs back + # if schema is still there reintroduce ourselves with strong refs back to us if ($_[0]->{schema}) { my $srcregs = $_[0]->{schema}->source_registrations; for (keys %$srcregs) { + next unless $srcregs->{$_}; $srcregs->{$_} = $_[0] if $srcregs->{$_} == $_[0]; } } - } + + 1; + } or do { + $global_phase_destroy = 1; + }; + + return; } -sub STORABLE_freeze { nfreeze($_[0]->handle) } +sub STORABLE_freeze { Storable::nfreeze($_[0]->handle) } sub STORABLE_thaw { my ($self, $cloning, $ice) = @_; - %$self = %{ (thaw $ice)->resolve }; + %$self = %{ (Storable::thaw($ice))->resolve }; } =head2 throw_exception @@ -1865,7 +2082,7 @@ Creates a new ResultSource object. Not normally called directly by end users. =item Arguments: 1/0 (default: 0) -=item Return value: 1/0 +=item Return Value: 1/0 =back @@ -1876,9 +2093,9 @@ metadata from storage as necessary. This is *deprecated*, and should not be used. It will be removed before 1.0. -=head1 AUTHORS +=head1 AUTHOR AND CONTRIBUTORS -Matt S. Trout +See L and L in DBIx::Class =head1 LICENSE