X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FSchema.pm;h=d5a8f35832bc3ca135f1f3b2b0b35cdc8b1360ef;hb=f064a2abb15858bb39a141ad50391d4191988d2c;hp=0d49b4bcbcf19084a6f2fbca6c0ca055469cfd9d;hpb=44e95db4537a7ace8aee44bcf74b8b9d79c03b6b;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Schema.pm b/lib/DBIx/Class/Schema.pm index 0d49b4b..d5a8f35 100644 --- a/lib/DBIx/Class/Schema.pm +++ b/lib/DBIx/Class/Schema.pm @@ -3,24 +3,29 @@ package DBIx::Class::Schema; use strict; use warnings; -use DBIx::Class::Exception; +use base 'DBIx::Class'; + use DBIx::Class::Carp; use Try::Tiny; use Scalar::Util qw/weaken blessed/; -use Sub::Name 'subname'; -use B 'svref_2object'; +use DBIx::Class::_Util qw( + refcount quote_sub scope_guard + is_exception dbic_internal_try + fail_on_internal_call emit_loud_diag +); use Devel::GlobalDestruction; use namespace::clean; -use base qw/DBIx::Class/; +__PACKAGE__->mk_group_accessors( inherited => qw( storage exception_action ) ); +__PACKAGE__->mk_classaccessor('storage_type' => '::DBI'); +__PACKAGE__->mk_classaccessor('stacktrace' => $ENV{DBIC_TRACE} || 0); +__PACKAGE__->mk_classaccessor('default_resultset_attributes' => {}); -__PACKAGE__->mk_classdata('class_mappings' => {}); -__PACKAGE__->mk_classdata('source_registrations' => {}); -__PACKAGE__->mk_classdata('storage_type' => '::DBI'); -__PACKAGE__->mk_classdata('storage'); -__PACKAGE__->mk_classdata('exception_action'); -__PACKAGE__->mk_classdata('stacktrace' => $ENV{DBIC_TRACE} || 0); -__PACKAGE__->mk_classdata('default_resultset_attributes' => {}); +# These two should have been private from the start but too late now +# Undocumented on purpose, hopefully it won't ever be necessary to +# screw with them +__PACKAGE__->mk_classaccessor('class_mappings' => {}); +__PACKAGE__->mk_classaccessor('source_registrations' => {}); =head1 NAME @@ -74,12 +79,13 @@ particular which module inherits off which. =back + package MyApp::Schema; __PACKAGE__->load_namespaces(); __PACKAGE__->load_namespaces( result_namespace => 'Res', resultset_namespace => 'RSet', - default_resultset_class => '+MyDB::Othernamespace::RSet', + default_resultset_class => '+MyApp::Othernamespace::RSet', ); With no arguments, this method uses L to load all of the @@ -109,11 +115,12 @@ are no matching Result classes like this: load_namespaces found ResultSet class $classname with no corresponding Result class -If a Result class is found to already have a ResultSet class set using -L to some other class, you will be warned like this: +If a ResultSource instance is found to already have a ResultSet class set +using L to some +other class, you will be warned like this: - We found ResultSet class '$rs_class' for '$result', but it seems - that you had already set '$result' to use '$rs_set' instead + We found ResultSet class '$rs_class' for '$result_class', but it seems + that you had already set '$result_class' to use '$rs_set' instead =head3 Examples @@ -155,8 +162,7 @@ entries in the list of namespaces will override earlier ones. # be stripped. sub _expand_relative_name { my ($class, $name) = @_; - return if !$name; - $name = $class . '::' . $name if ! ($name =~ s/^\+//); + $name =~ s/^\+// or $name = "${class}::${name}"; return $name; } @@ -164,31 +170,26 @@ sub _expand_relative_name { # namespace of $class. Untaints all findings as they can be assumed # to be safe sub _findallmod { - my $proto = shift; - my $ns = shift || ref $proto || $proto; - require Module::Find; - - # untaint result - return map { $_ =~ /(.+)/ } Module::Find::findallmod($ns); + return map + { $_ =~ /(.+)/ } # untaint result + Module::Find::findallmod( $_[1] || ref $_[0] || $_[0] ) + ; } # returns a hash of $shortname => $fullname for every package # found in the given namespaces ($shortname is with the $fullname's # namespace stripped off) sub _map_namespaces { - my ($class, @namespaces) = @_; - - my @results_hash; - foreach my $namespace (@namespaces) { - push( - @results_hash, - map { (substr($_, length "${namespace}::"), $_) } - $class->_findallmod($namespace) - ); + my ($me, $namespaces) = @_; + + my %res; + for my $ns (@$namespaces) { + $res{ substr($_, length "${ns}::") } = $_ + for $me->_findallmod($ns); } - @results_hash; + \%res; } # returns the result_source_instance for the passed class/object, @@ -197,7 +198,7 @@ sub _ns_get_rsrc_instance { my $me = shift; my $rs_class = ref ($_[0]) || $_[0]; - return try { + return dbic_internal_try { $rs_class->result_source_instance } catch { $me->throw_exception ( @@ -211,17 +212,18 @@ sub load_namespaces { my $result_namespace = delete $args{result_namespace} || 'Result'; my $resultset_namespace = delete $args{resultset_namespace} || 'ResultSet'; + my $default_resultset_class = delete $args{default_resultset_class}; + $default_resultset_class = $class->_expand_relative_name($default_resultset_class) + if $default_resultset_class; + $class->throw_exception('load_namespaces: unknown option(s): ' . join(q{,}, map { qq{'$_'} } keys %args)) if scalar keys %args; - $default_resultset_class - = $class->_expand_relative_name($default_resultset_class); - for my $arg ($result_namespace, $resultset_namespace) { - $arg = [ $arg ] if !ref($arg) && $arg; + $arg = [ $arg ] if ( $arg and ! ref $arg ); $class->throw_exception('load_namespaces: namespace arguments must be ' . 'a simple string or an arrayref') @@ -230,8 +232,8 @@ sub load_namespaces { $_ = $class->_expand_relative_name($_) for (@$arg); } - my %results = $class->_map_namespaces(@$result_namespace); - my %resultsets = $class->_map_namespaces(@$resultset_namespace); + my $results_by_source_name = $class->_map_namespaces($result_namespace); + my $resultsets_by_source_name = $class->_map_namespaces($resultset_namespace); my @to_register; { @@ -240,54 +242,56 @@ sub load_namespaces { use warnings qw/redefine/; # ensure classes are loaded and attached in inheritance order - for my $res (values %results) { - $class->ensure_class_loaded($res); + for my $result_class (values %$results_by_source_name) { + $class->ensure_class_loaded($result_class); } my %inh_idx; - my @subclass_last = sort { + my @source_names_by_subclass_last = sort { ($inh_idx{$a} ||= - scalar @{mro::get_linear_isa( $results{$a} )} + scalar @{mro::get_linear_isa( $results_by_source_name->{$a} )} ) <=> ($inh_idx{$b} ||= - scalar @{mro::get_linear_isa( $results{$b} )} + scalar @{mro::get_linear_isa( $results_by_source_name->{$b} )} ) - } keys(%results); + } keys(%$results_by_source_name); - foreach my $result (@subclass_last) { - my $result_class = $results{$result}; + foreach my $source_name (@source_names_by_subclass_last) { + my $result_class = $results_by_source_name->{$source_name}; - my $rs_class = delete $resultsets{$result}; - my $rs_set = $class->_ns_get_rsrc_instance ($result_class)->resultset_class; + my $preset_resultset_class = $class->_ns_get_rsrc_instance ($result_class)->resultset_class; + my $found_resultset_class = delete $resultsets_by_source_name->{$source_name}; - if($rs_set && $rs_set ne 'DBIx::Class::ResultSet') { - if($rs_class && $rs_class ne $rs_set) { - carp "We found ResultSet class '$rs_class' for '$result', but it seems " - . "that you had already set '$result' to use '$rs_set' instead"; + if($preset_resultset_class && $preset_resultset_class ne 'DBIx::Class::ResultSet') { + if($found_resultset_class && $found_resultset_class ne $preset_resultset_class) { + carp "We found ResultSet class '$found_resultset_class' matching '$results_by_source_name->{$source_name}', but it seems " + . "that you had already set the '$results_by_source_name->{$source_name}' resultet to '$preset_resultset_class' instead"; } } - elsif($rs_class ||= $default_resultset_class) { - $class->ensure_class_loaded($rs_class); - if(!$rs_class->isa("DBIx::Class::ResultSet")) { - carp "load_namespaces found ResultSet class $rs_class that does not subclass DBIx::Class::ResultSet"; + # elsif - there may be *no* default_resultset_class, in which case we fallback to + # DBIx::Class::Resultset and there is nothing to check + elsif($found_resultset_class ||= $default_resultset_class) { + $class->ensure_class_loaded($found_resultset_class); + if(!$found_resultset_class->isa("DBIx::Class::ResultSet")) { + carp "load_namespaces found ResultSet class '$found_resultset_class' that does not subclass DBIx::Class::ResultSet"; } - $class->_ns_get_rsrc_instance ($result_class)->resultset_class($rs_class); + $class->_ns_get_rsrc_instance ($result_class)->resultset_class($found_resultset_class); } - my $source_name = $class->_ns_get_rsrc_instance ($result_class)->source_name || $result; + my $source_name = $class->_ns_get_rsrc_instance ($result_class)->source_name || $source_name; push(@to_register, [ $source_name, $result_class ]); } } - foreach (sort keys %resultsets) { - carp "load_namespaces found ResultSet class $_ with no " - . 'corresponding Result class'; + foreach (sort keys %$resultsets_by_source_name) { + carp "load_namespaces found ResultSet class '$resultsets_by_source_name->{$_}' " + .'with no corresponding Result class'; } Class::C3->reinitialize if DBIx::Class::_ENV_::OLD_MRO; @@ -367,7 +371,7 @@ sub load_classes { } } else { my @comp = map { substr $_, length "${class}::" } - $class->_findallmod; + $class->_findallmod($class); $comps_for{$class} = \@comp; } @@ -426,6 +430,30 @@ both types of refs here in order to play nice with your Config::[class] or your choice. See L for an example of this. +=head2 default_resultset_attributes + +=over 4 + +=item Arguments: L<\%attrs|DBIx::Class::ResultSet/ATTRIBUTES> + +=item Return Value: L<\%attrs|DBIx::Class::ResultSet/ATTRIBUTES> + +=item Default value: None + +=back + +Like L stores a collection +of resultset attributes, to be used as defaults for B ResultSet +instance schema-wide. The same list of CAVEATS and WARNINGS applies, with +the extra downside of these defaults being practically inescapable: you will +B be able to derive a ResultSet instance with these attributes unset. + +Example: + + package My::Schema; + use base qw/DBIx::Class::Schema/; + __PACKAGE__->default_resultset_attributes( { software_limit => 1 } ); + =head2 exception_action =over 4 @@ -524,7 +552,10 @@ version, overload L instead. =cut -sub connect { shift->clone->connection(@_) } +sub connect { + DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call; + shift->clone->connection(@_); +} =head2 resultset @@ -564,7 +595,7 @@ Lists names of all the sources registered on this Schema object. =cut -sub sources { return keys %{shift->source_registrations}; } +sub sources { keys %{shift->source_registrations} } =head2 source @@ -584,21 +615,58 @@ source name. =cut sub source { - my $self = shift; + my ($self, $source_name) = @_; $self->throw_exception("source() expects a source name") - unless @_; - - my $source_name = shift; + unless $source_name; + + my $source_registrations; + + my $rsrc = + ( $source_registrations = $self->source_registrations )->{$source_name} + || + # if we got here, they probably passed a full class name + $source_registrations->{ $self->class_mappings->{$source_name} || '' } + || + $self->throw_exception( "Can't find source for ${source_name}" ) + ; + + # DO NOT REMOVE: + # We need to prevent alterations of pre-existing $@ due to where this call + # sits in the overall stack ( *unless* of course there is an actual error + # to report ). set_mro does alter $@ (and yes - it *can* throw an exception) + # We do not use local because set_mro *can* throw an actual exception + # We do not use a try/catch either, as on one hand it would slow things + # down for no reason (we would always rethrow), but also because adding *any* + # try/catch block below will segfault various threading tests on older perls + # ( which in itself is a FIXME but ENOTIMETODIG ) + my $old_dollarat = $@; + + no strict 'refs'; + mro::set_mro($_, 'c3') for + grep + { + # some pseudo-sources do not have a result/resultset yet + defined $_ + and + ( + ( + ${"${_}::__INITIAL_MRO_UPON_DBIC_LOAD__"} + ||= mro::get_mro($_) + ) + ne + 'c3' + ) + } + map + { length ref $_ ? ref $_ : $_ } + ( $rsrc, $rsrc->result_class, $rsrc->resultset_class ) + ; - my $sreg = $self->source_registrations; - return $sreg->{$source_name} if exists $sreg->{$source_name}; + # DO NOT REMOVE - see comment above + $@ = $old_dollarat; - # if we got here, they probably passed a full class name - my $mapped = $self->class_mappings->{$source_name}; - $self->throw_exception("Can't find source for ${source_name}") - unless $mapped && exists $sreg->{$mapped}; - return $sreg->{$mapped}; + $rsrc; } =head2 class @@ -618,8 +686,7 @@ Retrieves the Result class name for the given source name. =cut sub class { - my ($self, $source_name) = @_; - return $self->source($source_name)->result_class; + return shift->source(shift)->result_class; } =head2 txn_do @@ -769,16 +836,13 @@ those values. =cut sub populate { + DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call; + my ($self, $name, $data) = @_; - if(my $rs = $self->resultset($name)) { - if(defined wantarray) { - return $rs->populate($data); - } else { - $rs->populate($data); - } - } else { - $self->throw_exception("$name is not a resultset"); - } + my $rs = $self->resultset($name) + or $self->throw_exception("'$name' is not a resultset"); + + return $rs->populate($data); } =head2 connection @@ -787,13 +851,13 @@ sub populate { =item Arguments: @args -=item Return Value: $new_schema +=item Return Value: $self =back Similar to L except sets the storage object and connection -data in-place on the Schema class. You should probably be calling -L to get a proper Schema object instead. +data B on C<$self>. You should probably be calling +L to get a properly L Schema object instead. =head3 Overloading @@ -805,12 +869,14 @@ sub connection { my ($self, @info) = @_; return $self if !@info && $self->storage; - my ($storage_class, $args) = ref $self->storage_type ? - ($self->_normalize_storage_type($self->storage_type),{}) : ($self->storage_type, {}); + my ($storage_class, $args) = ref $self->storage_type + ? $self->_normalize_storage_type($self->storage_type) + : $self->storage_type + ; + + $storage_class =~ s/^::/DBIx::Class::Storage::/; - $storage_class = 'DBIx::Class::Storage'.$storage_class - if $storage_class =~ m/^::/; - try { + dbic_internal_try { $self->ensure_class_loaded ($storage_class); } catch { @@ -818,7 +884,8 @@ sub connection { "Unable to load storage class ${storage_class}: $_" ); }; - my $storage = $storage_class->new($self=>$args); + + my $storage = $storage_class->new( $self => $args||{} ); $storage->connect_info(\@info); $self->storage($storage); return $self; @@ -841,7 +908,7 @@ sub _normalize_storage_type { =item Arguments: $target_namespace, $additional_base_class? -=item Retur Value: $new_schema +=item Return Value: $new_schema =back @@ -868,25 +935,6 @@ will produce the output =cut -# this might be oversimplified -# sub compose_namespace { -# my ($self, $target, $base) = @_; - -# my $schema = $self->clone; -# foreach my $source_name ($schema->sources) { -# my $source = $schema->source($source_name); -# my $target_class = "${target}::${source_name}"; -# $self->inject_base( -# $target_class => $source->result_class, ($base ? $base : ()) -# ); -# $source->result_class($target_class); -# $target_class->result_source_instance($source) -# if $target_class->can('result_source_instance'); -# $schema->register_source($source_name, $source); -# } -# return $schema; -# } - sub compose_namespace { my ($self, $target, $base) = @_; @@ -903,40 +951,51 @@ sub compose_namespace { local *Class::C3::reinitialize = sub { } if DBIx::Class::_ENV_::OLD_MRO; use warnings qw/redefine/; - no strict qw/refs/; foreach my $source_name ($self->sources) { my $orig_source = $self->source($source_name); my $target_class = "${target}::${source_name}"; $self->inject_base($target_class, $orig_source->result_class, ($base || ()) ); - # register_source examines result_class, and then returns us a clone - my $new_source = $schema->register_source($source_name, bless - { %$orig_source, result_class => $target_class }, - ref $orig_source, + $schema->register_source( + $source_name, + $orig_source->clone( + result_class => $target_class + ), ); - - if ($target_class->can('result_source_instance')) { - # give the class a schema-less source copy - $target_class->result_source_instance( bless - { %$new_source, schema => ref $new_source->{schema} || $new_source->{schema} }, - ref $new_source, - ); - } } - foreach my $meth (qw/class source resultset/) { - no warnings 'redefine'; - *{"${target}::${meth}"} = subname "${target}::${meth}" => - sub { shift->schema->$meth(@_) }; - } + # Legacy stuff, not inserting INDIRECT assertions + quote_sub "${target}::${_}" => "shift->schema->$_(\@_)" + for qw(class source resultset); } Class::C3->reinitialize() if DBIx::Class::_ENV_::OLD_MRO; + # Give each composed class yet another *schema-less* source copy + # this is used for the freeze/thaw cycle + # + # This is not covered by any tests directly, but is indirectly exercised + # in t/cdbi/sweet/08pager by re-setting the schema on an existing object + # FIXME - there is likely a much cheaper way to take care of this + for my $source_name ($self->sources) { + + my $target_class = "${target}::${source_name}"; + + $target_class->result_source_instance( + $self->source($source_name)->clone( + result_class => $target_class, + schema => ( ref $schema || $schema ), + ) + ); + } + return $schema; } +# LEGACY: The intra-call to this was removed in 66d9ef6b and then +# the sub was de-documented way later in 249963d4. No way to be sure +# nothing on darkpan is calling it directly, so keeping as-is sub setup_connection_class { my ($class, $target, @info) = @_; $class->inject_base($target => 'DBIx::Class::DB'); @@ -1007,7 +1066,7 @@ sub svp_rollback { Clones the schema and its associated result_source objects and returns the copy. The resulting copy will have the same attributes as the source schema, -except for those attributes explicitly overriden by the provided C<%attrs>. +except for those attributes explicitly overridden by the provided C<%attrs>. =cut @@ -1035,13 +1094,10 @@ sub _copy_state_from { $self->class_mappings({ %{$from->class_mappings} }); $self->source_registrations({ %{$from->source_registrations} }); - foreach my $source_name ($from->sources) { - my $source = $from->source($source_name); - my $new = $source->new($source); - # we use extra here as we want to leave the class_mappings as they are - # but overwrite the source_registrations entry with the new source - $self->register_extra_source($source_name => $new); - } + # we use extra here as we want to leave the class_mappings as they are + # but overwrite the source_registrations entry with the new source + $self->register_extra_source( $_ => $from->source($_) ) + for $from->sources; if ($from->storage) { $self->storage($from->storage); @@ -1064,29 +1120,71 @@ default behavior will provide a detailed stack trace. =cut -my $false_exception_action_warned; sub throw_exception { - my $self = shift; + my ($self, @args) = @_; + + if ( + ! DBIx::Class::_Util::in_internal_try() + and + my $act = $self->exception_action + ) { + + my $guard_disarmed; + + my $guard = scope_guard { + return if $guard_disarmed; + emit_loud_diag( emit_dups => 1, msg => " + + !!! DBIx::Class INTERNAL PANIC !!! - if (my $act = $self->exception_action) { - if ($act->(@_)) { - DBIx::Class::Exception->throw( +The exception_action() handler installed on '$self' +aborted the stacktrace below via a longjmp (either via Return::Multilevel or +plain goto, or Scope::Upper or something equally nefarious). There currently +is nothing safe DBIx::Class can do, aside from displaying this error. A future +version ( 0.082900, when available ) will reduce the cases in which the +handler is invoked, but this is neither a complete solution, nor can it do +anything for other software that might be affected by a similar problem. + + !!! FIX YOUR ERROR HANDLING !!! + +This guard was activated starting", + ); + }; + + dbic_internal_try { + # if it throws - good, we'll assign to @args in the end + # if it doesn't - do different things depending on RV truthiness + if( $act->(@args) ) { + $args[0] = ( "Invocation of the exception_action handler installed on $self did *not*" .' result in an exception. DBIx::Class is unable to function without a reliable' - .' exception mechanism, ensure that exception_action does not hide exceptions' - ." (original error: $_[0])" - ); - } - elsif(! $false_exception_action_warned++) { - carp ( + .' exception mechanism, ensure your exception_action does not hide exceptions' + ." (original error: $args[0])" + ); + } + else { + carp_unique ( "The exception_action handler installed on $self returned false instead" .' of throwing an exception. This behavior has been deprecated, adjust your' - .' handler to always rethrow the supplied error.' - ); + .' handler to always rethrow the supplied error' + ); + } + + 1; } + catch { + # We call this to get the necessary warnings emitted and disregard the RV + # as it's definitely an exception if we got as far as this catch{} block + is_exception( + $args[0] = $_ + ); + }; + + # Done guarding against https://github.com/PerlDancer/Dancer2/issues/1125 + $guard_disarmed = 1; } - DBIx::Class::Exception->throw($_[0], $self->stacktrace); + DBIx::Class::Exception->throw( $args[0], $self->stacktrace ); } =head2 deploy @@ -1102,8 +1200,7 @@ Attempts to deploy the schema to the current storage using L. See L for a list of values for C<\%sqlt_args>. The most common value for this would be C<< { add_drop_table => 1 } >> to have the SQL produced include a C statement for each table -created. For quoting purposes supply C and -C. +created. For quoting purposes supply C. Additionally, the DBIx::Class parser accepts a C parameter as a hash ref or an array ref, containing a list of source to deploy. If present, then @@ -1131,8 +1228,8 @@ sub deploy { A convenient shortcut to C<< $self->storage->deployment_statements($self, @args) >>. -Returns the SQL statements used by L and -L. +Returns the statements used by L and +L. =cut @@ -1205,14 +1302,12 @@ format. sub ddl_filename { my ($self, $type, $version, $dir, $preversion) = @_; - require File::Spec; - $version = "$preversion-$version" if $preversion; my $class = blessed($self) || $self; $class =~ s/::/-/g; - return File::Spec->catfile($dir, "$class-$version-$type.sql"); + return "$dir/$class-$version-$type.sql"; } =head2 thaw @@ -1226,19 +1321,17 @@ reference to any schema, so are rather useless. sub thaw { my ($self, $obj) = @_; local $DBIx::Class::ResultSourceHandle::thaw_schema = $self; - require Storable; return Storable::thaw($obj); } =head2 freeze -This doesn't actually do anything more than call L, it is just -provided here for symmetry. +This doesn't actually do anything beyond calling L, +it is just provided here for symmetry. =cut sub freeze { - require Storable; return Storable::nfreeze($_[1]); } @@ -1261,7 +1354,6 @@ objects so their references to the schema object sub dclone { my ($self, $obj) = @_; local $DBIx::Class::ResultSourceHandle::thaw_schema = $self; - require Storable; return Storable::dclone($obj); } @@ -1362,45 +1454,60 @@ has a source and you want to register an extra one. sub register_extra_source { shift->_register_source(@_, { extra => 1 }) } sub _register_source { - my ($self, $source_name, $source, $params) = @_; + my ($self, $source_name, $supplied_rsrc, $params) = @_; - $source = $source->new({ %$source, source_name => $source_name }); + my $derived_rsrc = $supplied_rsrc->clone({ + source_name => $source_name, + }); - $source->schema($self); - weaken $source->{schema} if ref($self); + # Do not move into the clone-hashref above: there are things + # on CPAN that do hook 'sub schema' + # https://metacpan.org/source/LSAUNDERS/DBIx-Class-Preview-1.000003/lib/DBIx/Class/ResultSource/Table/Previewed.pm#L9-38 + $derived_rsrc->schema($self); + + weaken $derived_rsrc->{schema} + if length ref($self); my %reg = %{$self->source_registrations}; - $reg{$source_name} = $source; + $reg{$source_name} = $derived_rsrc; $self->source_registrations(\%reg); - return $source if $params->{extra}; + return $derived_rsrc if $params->{extra}; - my $rs_class = $source->result_class; - if ($rs_class and my $rsrc = try { $rs_class->result_source_instance } ) { + my( $result_class, $result_class_level_rsrc ); + if ( + $result_class = $derived_rsrc->result_class + and + # There are known cases where $rs_class is *ONLY* an inflator, without + # any hint of a rsrc (e.g. DBIx::Class::KiokuDB::EntryProxy) + $result_class_level_rsrc = dbic_internal_try { $result_class->result_source_instance } + ) { my %map = %{$self->class_mappings}; - if ( - exists $map{$rs_class} + + carp ( + "$result_class already had a registered source which was replaced by " + . 'this call. Perhaps you wanted register_extra_source(), though it is ' + . 'more likely you did something wrong.' + ) if ( + exists $map{$result_class} and - $map{$rs_class} ne $source_name + $map{$result_class} ne $source_name and - $rsrc ne $_[2] # orig_source - ) { - carp - "$rs_class already had a registered source which was replaced by this call. " - . 'Perhaps you wanted register_extra_source(), though it is more likely you did ' - . 'something wrong.' - ; - } + $result_class_level_rsrc != $supplied_rsrc + ); - $map{$rs_class} = $source_name; + $map{$result_class} = $source_name; $self->class_mappings(\%map); } - return $source; + $derived_rsrc; } my $global_phase_destroy; sub DESTROY { + ### NO detected_reinvoked_destructor check + ### This code very much relies on being called multuple times + return if $global_phase_destroy ||= in_global_destruction; my $self = shift; @@ -1414,8 +1521,9 @@ sub DESTROY { # 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 :((( - if (ref $srcs->{$source_name} and svref_2object($srcs->{$source_name})->REFCNT > 1) { - local $@; + if (length ref $srcs->{$source_name} and refcount($srcs->{$source_name}) > 1) { + local $SIG{__DIE__} if $SIG{__DIE__}; + local $@ if DBIx::Class::_ENV_::UNSTABLE_DOLLARAT; eval { $srcs->{$source_name}->schema($self); weaken $srcs->{$source_name}; @@ -1427,6 +1535,11 @@ sub DESTROY { last; } } + + # Dummy NEXTSTATE ensuring the all temporaries on the stack are garbage + # collected before leaving this scope. Depending on the code above, this + # may very well be just a preventive measure guarding future modifications + undef; } sub _unregister_source { @@ -1483,13 +1596,12 @@ sub compose_connection { carp_once "compose_connection deprecated as of 0.08000" unless $INC{"DBIx/Class/CDBICompat.pm"}; - my $base = 'DBIx::Class::ResultSetProxy'; - try { - eval "require ${base};" + dbic_internal_try { + require DBIx::Class::ResultSetProxy; } catch { $self->throw_exception - ("No arguments to load_classes and couldn't load ${base} ($_)") + ("No arguments to load_classes and couldn't load DBIx::Class::ResultSetProxy ($_)") }; if ($self eq $target) { @@ -1497,41 +1609,44 @@ sub compose_connection { foreach my $source_name ($self->sources) { my $source = $self->source($source_name); my $class = $source->result_class; - $self->inject_base($class, $base); - $class->mk_classdata(resultset_instance => $source->resultset); - $class->mk_classdata(class_resolver => $self); + $self->inject_base($class, 'DBIx::Class::ResultSetProxy'); + $class->mk_classaccessor(resultset_instance => $source->resultset); + $class->mk_classaccessor(class_resolver => $self); } $self->connection(@info); return $self; } - my $schema = $self->compose_namespace($target, $base); - { - no strict 'refs'; - my $name = join '::', $target, 'schema'; - *$name = subname $name, sub { $schema }; - } + my $schema = $self->compose_namespace($target, 'DBIx::Class::ResultSetProxy'); + quote_sub "${target}::schema", '$s', { '$s' => \$schema }; $schema->connection(@info); foreach my $source_name ($schema->sources) { my $source = $schema->source($source_name); my $class = $source->result_class; #warn "$source_name $class $source ".$source->storage; - $class->mk_classdata(result_source_instance => $source); - $class->mk_classdata(resultset_instance => $source->resultset); - $class->mk_classdata(class_resolver => $schema); + + $class->mk_group_accessors( inherited => [ result_source_instance => '_result_source' ] ); + # explicit set-call, avoid mro update lag + $class->set_inherited( result_source_instance => $source ); + + $class->mk_classaccessor(resultset_instance => $source->resultset); + $class->mk_classaccessor(class_resolver => $schema); } return $schema; } -1; - -=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 + +1;