use base qw/DBIx::Class::Componentised DBIx::Class::AccessorGroup/;
use DBIx::Class::Exception;
+use DBIx::Class::_Util qw( uniq refdesc visit_namespaces );
+use Scalar::Util qw( weaken refaddr );
+use namespace::clean;
+
__PACKAGE__->mk_group_accessors(inherited => '_skip_namespace_frames');
__PACKAGE__->_skip_namespace_frames('^DBIx::Class|^SQL::Abstract|^Try::Tiny|^Class::Accessor::Grouped|^Context::Preserve|^Moose::Meta::');
sub component_base_class { 'DBIx::Class' }
+
+my $cref_registry;
+sub DBIx::Class::__Attr_iThreads_handler__::CLONE {
+
+ # this is disgusting, but the best we can do without even more surgery
+ visit_namespaces( action => sub {
+ my $pkg = shift;
+
+ # skip dangerous namespaces
+ return 1 if $pkg =~ /^ (?:
+ DB | next | B | .+? ::::ISA (?: ::CACHE ) | Class::C3
+ ) $/x;
+
+ no strict 'refs';
+
+ if (
+ exists ${"${pkg}::"}{__cag___attr_cache}
+ and
+ ref( my $attr_stash = ${"${pkg}::__cag___attr_cache"} ) eq 'HASH'
+ ) {
+ $attr_stash->{ $cref_registry->{$_} } = delete $attr_stash->{$_}
+ for keys %$attr_stash;
+ }
+
+ return 1;
+ })
+}
+
sub MODIFY_CODE_ATTRIBUTES {
my ($class,$code,@attrs) = @_;
$class->mk_classaccessor('__attr_cache' => {})
unless $class->can('__attr_cache');
- $class->__attr_cache->{$code} = [@attrs];
+
+ # compaction step
+ defined $cref_registry->{$_} or delete $cref_registry->{$_}
+ for keys %$cref_registry;
+
+ # The original API used stringification instead of refaddr - can't change that now
+ if( $cref_registry->{$code} ) {
+ Carp::confess( sprintf
+ "Coderefs '%s' and '%s' stringify to the same value '%s': nothing will work",
+ refdesc($code),
+ refdesc($cref_registry->{$code}),
+ "$code"
+ ) if refaddr($cref_registry->{$code}) != refaddr($code);
+ }
+ else {
+ weaken( $cref_registry->{$code} = $code )
+ }
+
+ $class->__attr_cache->{$code} = [ sort( uniq(
+ @{ $class->__attr_cache->{$code} || [] },
+ @attrs,
+ ))];
+
+ # FIXME - DBIC essentially gobbles up any attribute it can lay its hands on:
+ # decidedly not cool
+ #
+ # There should be some sort of warning on unrecognized attributes or
+ # somesuch... OTOH people do use things in the wild hence the plan of action
+ # is anything but clear :/
+ #
+ # https://metacpan.org/source/ZIGOROU/DBIx-Class-Service-0.02/lib/DBIx/Class/Service.pm#L93-110
+ # https://metacpan.org/source/ZIGOROU/DBIx-Class-Service-0.02/t/lib/DBIC/Test/Service/User.pm#L29
+ # https://metacpan.org/source/ZIGOROU/DBIx-Class-Service-0.02/t/lib/DBIC/Test/Service/User.pm#L36
+ #
return ();
}
sub _attr_cache {
my $self = shift;
- my $cache = $self->can('__attr_cache') ? $self->__attr_cache : {};
-
- return {
- %$cache,
+ +{
+ %{ $self->can('__attr_cache') ? $self->__attr_cache : {} },
%{ $self->maybe::next::method || {} },
};
}
--- /dev/null
+use warnings;
+use strict;
+
+use Config;
+my $skip_threads;
+BEGIN {
+ if( ! $Config{useithreads} ) {
+ $skip_threads = 'your perl does not support ithreads';
+ }
+ elsif( "$]" < 5.008005 ) {
+ $skip_threads = 'DBIC does not actively support threads before perl 5.8.5';
+ }
+ elsif( $INC{'Devel/Cover.pm'} ) {
+ $skip_threads = 'Devel::Cover does not work with ithreads yet';
+ }
+
+ unless( $skip_threads ) {
+ require threads;
+ threads->import;
+ }
+}
+
+use Test::More;
+use DBIx::Class::_Util qw( quote_sub modver_gt_or_eq );
+
+### Test the upcoming attributes support
+require DBIx::Class;
+@DBICTest::ATTRTEST::ISA = 'DBIx::Class';
+
+my $var = \42;
+my $s = quote_sub(
+ 'DBICTest::ATTRTEST::attr',
+ '$v',
+ { '$v' => $var },
+ {
+ attributes => [qw( ResultSet )],
+ package => 'DBICTest::ATTRTEST',
+ },
+);
+
+is $s, \&DBICTest::ATTRTEST::attr, 'Same cref installed';
+
+is DBICTest::ATTRTEST::attr(), 42, 'Sub properly installed and callable';
+
+is_deeply
+ [ attributes::get( $s ) ],
+ [ 'ResultSet' ],
+ 'Attribute installed',
+unless $^V =~ /c/; # FIXME work around https://github.com/perl11/cperl/issues/147
+
+sub add_more_attrs {
+ # Test that secondary attribute application works
+ attributes->import(
+ 'DBICTest::ATTRTEST',
+ DBICTest::ATTRTEST->can('attr'),
+ 'method',
+ 'SomethingNobodyUses',
+ );
+
+ # and that double-application also works
+ attributes->import(
+ 'DBICTest::ATTRTEST',
+ DBICTest::ATTRTEST->can('attr'),
+ 'SomethingNobodyUses',
+ );
+
+ is_deeply
+ [ sort( attributes::get( $s ) )],
+ [
+ qw( ResultSet SomethingNobodyUses method ),
+
+ # before 5.10/5.8.9 internal reserved would get doubled, sigh
+ #
+ # FIXME - perhaps need to weed them out somehow at FETCH_CODE_ATTRIBUTES
+ # time...? In any case - this is not important at this stage
+ ( modver_gt_or_eq( attributes => '0.08' ) ? () : 'method' )
+ ],
+ 'Secondary attributes installed',
+ unless $^V =~ /c/; # FIXME work around https://github.com/perl11/cperl/issues/147
+
+ is_deeply (
+ DBICTest::ATTRTEST->_attr_cache->{$s},
+ [
+ qw( ResultSet SomethingNobodyUses ),
+
+ # after 5.10/5.8.9 FETCH_CODE_ATTRIBUTES is never called for reserved
+ # attribute names, so there is nothing for DBIC to see
+ #
+ # FIXME - perhaps need to teach ->_attr to reinvoke attributes::get() ?
+ # In any case - this is not important at this stage
+ ( modver_gt_or_eq( attributes => '0.08' ) ? () : 'method' )
+ ],
+ 'Attributes visible in DBIC-specific attribute API',
+ );
+}
+
+
+if ($skip_threads) {
+ SKIP: { skip "Skipping the thread test: $skip_threads", 1 }
+
+ add_more_attrs();
+}
+else {
+ threads->create(sub {
+ add_more_attrs();
+ select( undef, undef, undef, 0.2 ); # without this many tasty crashes even on latest perls
+ })->join;
+}
+
+
+done_testing;
}
;
-### Test the upcoming attributes support
-require DBIx::Class;
-@DBICTest::QSUB::ISA = 'DBIx::Class';
-
-my $var = \42;
-my $s = quote_sub(
- 'DBICTest::QSUB::attr',
- '$v',
- { '$v' => $var },
- {
- # use grandfathered 'ResultSet' attribute for starters
- attributes => [qw( ResultSet )],
- package => 'DBICTest::QSUB',
- },
-);
-
-is $s, \&DBICTest::QSUB::attr, 'Same cref installed';
-
-is DBICTest::QSUB::attr(), 42, 'Sub properly installed and callable';
-
-is_deeply
- [ attributes::get( $s ) ],
- [ 'ResultSet' ],
- 'Attribute installed',
-unless $^V =~ /c/; # FIXME work around https://github.com/perl11/cperl/issues/147
-
done_testing;