use DBIx::Class::_Util 'fail_on_internal_call';
use namespace::clean;
-sub mk_classdata {
+sub mk_classdata :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
shift->mk_classaccessor(@_);
}
-sub mk_classaccessor {
+sub mk_classaccessor :DBIC_method_is_indirect_sugar {
my $self = shift;
$self->mk_group_accessors('inherited', $_[0]);
(@_ > 1)
sub VALID_DBIC_CODE_ATTRIBUTE {
#my ($class, $attr) = @_;
- # initially no valid attributes
- 0;
+###
+### !!! IMPORTANT !!!
+###
+### *DO NOT* yield to the temptation of using free-form-argument attributes.
+### The technique was proven instrumental in Catalyst a decade ago, and
+### was more recently revived in Sub::Attributes. Yet, while on the surface
+### they seem immensely useful, per-attribute argument lists are in fact an
+### architectural dead end.
+###
+### In other words: you are *very strongly urged* to ensure the regex below
+### does not allow anything beyond qr/^ DBIC_method_is_ [A-Z_a-z0-9]+ $/x
+###
+
+ $_[1] =~ /^ DBIC_method_is_ (?:
+ indirect_sugar
+ ) $/x;
}
sub FETCH_CODE_ATTRIBUTES {
The following method attributes are currently recognized under the C<DBIC_*>
prefix:
-=over
-
-=item * None so far
+=head3 DBIC_method_is_indirect_sugar
-=back
+The presence of this attribute indicates a helper "sugar" method. Overriding
+such methods in your subclasses will be of limited success at best, as DBIC
+itself and various plugins are much more likely to invoke alternative direct
+call paths, bypassing your override entirely. Good examples of this are
+L<DBIx::Class::ResultSet/create> and L<DBIx::Class::Schema/connect>.
=head1 METHODS
elsif ($acc_type eq 'multi') {
- quote_sub "${class}::${rel}", sprintf( <<'EOC', perlstring $rel );
+ my @qsub_args = (
+ {},
+ {
+ attributes => [qw(
+ DBIC_method_is_indirect_sugar
+ )]
+ },
+ );
+
+
+ quote_sub "${class}::${rel}", sprintf( <<'EOC', perlstring $rel ), @qsub_args;
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and DBIx::Class::_Util::fail_on_internal_call;
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_WANTARRAY and my $sog = DBIx::Class::_Util::fail_on_internal_wantarray;
shift->related_resultset(%s)->search( @_ )
EOC
- quote_sub "${class}::${rel}_rs", sprintf( <<'EOC', perlstring $rel );
+ quote_sub "${class}::${rel}_rs", sprintf( <<'EOC', perlstring $rel ), @qsub_args;
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and DBIx::Class::_Util::fail_on_internal_call;
shift->related_resultset(%s)->search_rs( @_ )
EOC
- quote_sub "${class}::add_to_${rel}", sprintf( <<'EOC', perlstring $rel );
+ quote_sub "${class}::add_to_${rel}", sprintf( <<'EOC', perlstring $rel ), @qsub_args;
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and DBIx::Class::_Util::fail_on_internal_call;
shift->create_related( %s => @_ );
EOC
=cut
-sub search_related {
+sub search_related :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
shift->related_resultset(shift)->search(@_);
}
=cut
-sub search_related_rs {
+sub search_related_rs :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
shift->related_resultset(shift)->search_rs(@_)
}
=cut
-sub count_related {
+sub count_related :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
shift->related_resultset(shift)->search_rs(@_)->count;
}
=cut
-sub find_related {
+sub find_related :DBIC_method_is_indirect_sugar {
#my ($self, $rel, @args) = @_;
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
return shift->related_resultset(shift)->find(@_);
=cut
-sub update_or_create_related {
+sub update_or_create_related :DBIC_method_is_indirect_sugar {
#my ($self, $rel, @args) = @_;
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
shift->related_resultset(shift)->update_or_create(@_);
}
}
- quote_sub "${class}::${meth}", sprintf( <<'EOC', $rs_meth );
+ my @main_meth_qsub_args = (
+ {},
+ { attributes => [
+ 'DBIC_method_is_indirect_sugar',
+ ] },
+ );
+
+
+ quote_sub "${class}::${meth}", sprintf( <<'EOC', $rs_meth ), @main_meth_qsub_args;
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and DBIx::Class::_Util::fail_on_internal_call;
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_WANTARRAY and my $sog = DBIx::Class::_Util::fail_on_internal_wantarray;
EOC
- my $qsub_attrs = {
- '$rel_attrs' => \{ alias => $f_rel, %{ $rel_attrs||{} } },
- '$carp_unique' => \$cu,
- };
+ my @extra_meth_qsub_args = (
+ {
+ '$rel_attrs' => \{ alias => $f_rel, %{ $rel_attrs||{} } },
+ '$carp_unique' => \$cu,
+ },
+ { attributes => [
+ 'DBIC_method_is_indirect_sugar',
+ ] },
+ );
- quote_sub "${class}::${rs_meth}", sprintf( <<'EOC', map { perlstring $_ } ( "${class}::${meth}", $rel, $f_rel ) ), $qsub_attrs;
+ quote_sub "${class}::${rs_meth}", sprintf( <<'EOC', map { perlstring $_ } ( "${class}::${meth}", $rel, $f_rel ) ), @extra_meth_qsub_args;
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS
and
;
EOC
+ # the above is the only indirect method, the 3 below have too much logic
+ shift @{$extra_meth_qsub_args[1]{attributes}};
- quote_sub "${class}::${add_meth}", sprintf( <<'EOC', $add_meth, $rel, $f_rel ), $qsub_attrs;
+
+ quote_sub "${class}::${add_meth}", sprintf( <<'EOC', $add_meth, $rel, $f_rel ), @extra_meth_qsub_args;
( @_ >= 2 and @_ <= 3 ) or $_[0]->throw_exception(
"'%1$s' expects an object or hashref to link to, and an optional hashref of link data"
EOC
- quote_sub "${class}::${set_meth}", sprintf( <<'EOC', $set_meth, $add_meth, $rel, $f_rel ), $qsub_attrs;
+ quote_sub "${class}::${set_meth}", sprintf( <<'EOC', $set_meth, $add_meth, $rel, $f_rel ), @extra_meth_qsub_args;
my $self = shift;
$guard->commit if $guard;
EOC
-
- quote_sub "${class}::${remove_meth}", sprintf( <<'EOC', $remove_meth, $rel, $f_rel );
+ quote_sub "${class}::${remove_meth}", sprintf( <<'EOC', $remove_meth, $rel, $f_rel ), @extra_meth_qsub_args;
$_[0]->throw_exception("'%1$s' expects an object")
unless defined Scalar::Util::blessed( $_[1] );
=cut
-sub search_related {
+sub search_related :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
return shift->related_resultset(shift)->search(@_);
}
=cut
-sub search_related_rs {
+sub search_related_rs :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
return shift->related_resultset(shift)->search_rs(@_);
}
=cut
-sub count_literal {
+sub count_literal :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
shift->search_literal(@_)->count
}
=cut
-sub first {
+sub first :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
return $_[0]->reset->next;
}
=cut
-sub create {
+sub create :DBIC_method_is_indirect_sugar {
#my ($self, $col_data) = @_;
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
return shift->new_result(shift)->insert;
=cut
-sub min {
+sub min :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
$_[0]->func('MIN');
}
=cut
-sub min_rs {
+sub min_rs :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
$_[0]->func_rs('MIN')
}
=cut
-sub max {
+sub max :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
$_[0]->func('MAX');
}
=cut
-sub max_rs {
+sub max_rs :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
$_[0]->func_rs('MAX')
}
=cut
-sub sum {
+sub sum :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
$_[0]->func('SUM');
}
=cut
-sub sum_rs {
+sub sum_rs :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
$_[0]->func_rs('SUM')
}
return $self;
}
-sub add_column {
+sub add_column :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
shift->add_columns(@_)
}
=cut
-sub column_info {
+sub column_info :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
#my ($self, $column) = @_;
$self->_ordered_columns([ grep { not $to_remove{$_} } @{$self->_ordered_columns} ]);
}
-sub remove_column {
+sub remove_column :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
shift->remove_columns(@_)
}
=cut
-sub add_unique_constraints {
+sub add_unique_constraints :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
my $self = shift;
=cut
-sub storage {
+sub storage :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
$_[0]->schema->storage
}
use base 'DBIx::Class';
+# needs to be loaded early to query method attributes below
+use DBIx::Class::ResultSource;
+
use DBIx::Class::_Util qw( quote_sub fail_on_internal_call );
use namespace::clean;
}
}
-sub add_column {
+sub add_column :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
shift->add_columns(@_)
}
# legacy resultset_class accessor, seems to be used by cdbi only
-sub iterator_class {
+sub iterator_class :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
shift->result_source->resultset_class(@_)
}
relationship_info
has_relationship
/) {
- quote_sub __PACKAGE__."::$method_to_proxy", sprintf( <<'EOC', $method_to_proxy );
+
+ my $qsub_opts = { attributes => [ do {
+ no strict 'refs';
+ attributes::get( \&{"DBIx::Class::ResultSource::$method_to_proxy"} )
+ } ] };
+
+ quote_sub __PACKAGE__."::$method_to_proxy", sprintf( <<'EOC', $method_to_proxy ), {}, $qsub_opts;
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and DBIx::Class::_Util::fail_on_internal_call;
shift->result_source->%s (@_);
=cut
-sub insert_or_update {
+sub insert_or_update :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
shift->update_or_insert(@_);
}
=cut
-sub result_source {
+sub result_source :DBIC_method_is_indirect_sugar {
# While getter calls are routed through here for sensible exception text
# it makes no sense to have setters do the same thing
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS
=cut
-sub connect {
+sub connect :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
shift->clone->connection(@_);
}
=cut
-sub populate {
+sub populate :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
my ($self, $name, $data) = @_;
__PACKAGE__->cursor_class('DBIx::Class::Cursor');
-sub cursor {
+sub cursor :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
shift->cursor_class(@_);
}
: $fr
;
+
+ die "\nMethod $fr->[3] is not marked with the 'DBIC_method_is_indirect_sugar' attribute\n\n" unless (
+
+ # unlikely but who knows...
+ ! @$fr
+
+ or
+
+ # This is a weird-ass double-purpose method, only one branch of which is marked
+ # as an illegal indirect call
+ # Hence the 'indirect' attribute makes no sense
+ # FIXME - likely need to mark this in some other manner
+ $fr->[3] eq 'DBIx::Class::ResultSet::new'
+
+ or
+
+ # RsrcProxy stuff is special and not attr-annotated on purpose
+ # Yet it is marked (correctly) as fail_on_internal_call(), as DBIC
+ # itself should not call these methods as first-entry
+ $fr->[3] =~ /^DBIx::Class::ResultSourceProxy::[^:]+$/
+
+ or
+
+ # FIXME - there is likely a more fine-graned way to escape "foreign"
+ # callers, based on annotations... (albeit a slower one)
+ # For the time being just skip in a dumb way
+ $fr->[3] !~ /^DBIx::Class|^DBICx::|^DBICTest::/
+
+ or
+
+ grep
+ { $_ eq 'DBIC_method_is_indirect_sugar' }
+ do { no strict 'refs'; attributes::get( \&{ $fr->[3] }) }
+ );
+
+
if (
defined $fr->[0]
and
};
weaken( $assertion_arounds->{refaddr $replacement} = $replacement );
+
+ attributes->import(
+ $origin,
+ $replacement,
+ attributes::get($orig_rsrc)
+ );
}
};
weaken( $assertion_arounds->{refaddr $replacement} = $replacement );
- }
+ attributes->import(
+ $origin,
+ $replacement,
+ attributes::get($orig_rsrc_instance)
+ );
+ }
}
Class::C3::initialize if DBIx::Class::_ENV_::OLD_MRO;