Start setting the 'c3' mro unambiguously everywhere
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Schema.pm
index 16a0878..04f92cc 100644 (file)
@@ -4,12 +4,15 @@ use strict;
 use warnings;
 
 use base 'DBIx::Class';
+use mro 'c3';
 
 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
+);
 use Devel::GlobalDestruction;
 use namespace::clean;
 
@@ -109,11 +112,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</resultset_class> 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<resultset_class|DBIx::Class::ResultSource/resultset_class> 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 +159,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 +167,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 +195,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 +209,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 +229,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 +239,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 +368,7 @@ sub load_classes {
     }
   } else {
     my @comp = map { substr $_, length "${class}::"  }
-                 $class->_findallmod;
+                 $class->_findallmod($class);
     $comps_for{$class} = \@comp;
   }
 
@@ -564,7 +565,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
 
@@ -799,12 +800,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 {
@@ -812,7 +815,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;
@@ -862,25 +866,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) = @_;
 
@@ -897,7 +882,6 @@ 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);
 
@@ -919,11 +903,8 @@ sub compose_namespace {
       }
     }
 
-    foreach my $meth (qw/class source resultset/) {
-      no warnings 'redefine';
-      *{"${target}::${meth}"} = subname "${target}::${meth}" =>
-        sub { shift->schema->$meth(@_) };
-    }
+    quote_sub "${target}::${_}" => "shift->schema->$_(\@_)"
+      for qw(class source resultset);
   }
 
   Class::C3->reinitialize() if DBIx::Class::_ENV_::OLD_MRO;
@@ -1059,26 +1040,70 @@ default behavior will provide a detailed stack trace.
 =cut
 
 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;
+      local $SIG{__WARN__};
+      Carp::cluck("
+                    !!! DBIx::Class INTERNAL PANIC !!!
+
+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 beginning"
+      );
+    };
 
-  if (my $act = $self->exception_action) {
-    if ($act->(@_)) {
-      DBIx::Class::Exception->throw(
+    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])"
-      );
+        .' 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'
+        );
+      }
+
+      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] = $_
+      );
+    };
 
-    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.'
-    );
+    # 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
@@ -1122,8 +1147,8 @@ sub deploy {
 
 A convenient shortcut to
 C<< $self->storage->deployment_statements($self, @args) >>.
-Returns the SQL statements used by L</deploy> and
-L<DBIx::Class::Schema::Storage/deploy>.
+Returns the statements used by L</deploy> and
+L<DBIx::Class::Storage/deploy>.
 
 =cut
 
@@ -1196,14 +1221,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
@@ -1217,19 +1240,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<Storable/nfreeze>, it is just
-provided here for symmetry.
+This doesn't actually do anything beyond calling L<nfreeze|Storable/SYNOPSIS>,
+it is just provided here for symmetry.
 
 =cut
 
 sub freeze {
-  require Storable;
   return Storable::nfreeze($_[1]);
 }
 
@@ -1252,7 +1273,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);
 }
 
@@ -1367,7 +1387,7 @@ sub _register_source {
   return $source if $params->{extra};
 
   my $rs_class = $source->result_class;
-  if ($rs_class and my $rsrc = try { $rs_class->result_source_instance } ) {
+  if ($rs_class and my $rsrc = dbic_internal_try { $rs_class->result_source_instance } ) {
     my %map = %{$self->class_mappings};
     if (
       exists $map{$rs_class}
@@ -1392,6 +1412,9 @@ sub _register_source {
 
 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;
@@ -1405,7 +1428,8 @@ 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) {
+    if (length ref $srcs->{$source_name} and refcount($srcs->{$source_name}) > 1) {
+      local $SIG{__DIE__} if $SIG{__DIE__};
       local $@;
       eval {
         $srcs->{$source_name}->schema($self);
@@ -1418,6 +1442,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 {
@@ -1474,13 +1503,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) {
@@ -1488,7 +1516,7 @@ 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);
+      $self->inject_base($class, 'DBIx::Class::ResultSetProxy');
       $class->mk_classdata(resultset_instance => $source->resultset);
       $class->mk_classdata(class_resolver => $self);
     }
@@ -1496,12 +1524,8 @@ sub compose_connection {
     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) {
@@ -1515,14 +1539,17 @@ sub compose_connection {
   return $schema;
 }
 
-1;
-
-=head1 AUTHOR AND CONTRIBUTORS
+=head1 FURTHER QUESTIONS?
 
-See L<AUTHOR|DBIx::Class/AUTHOR> and L<CONTRIBUTORS|DBIx::Class/CONTRIBUTORS> in DBIx::Class
+Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
 
-=head1 LICENSE
+=head1 COPYRIGHT AND LICENSE
 
-You may distribute this code under the same terms as Perl itself.
+This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
+by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
+redistribute it and/or modify it under the same terms as the
+L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.
 
 =cut
+
+1;