1 package DBIx::Class::Schema::SanityChecker;
6 use DBIx::Class::_Util qw(
7 dbic_internal_try refdesc uniq serialize
8 describe_class_methods emit_loud_diag
11 use DBIx::Class::Exception ();
12 use Scalar::Util qw( blessed refaddr );
17 DBIx::Class::Schema::SanityChecker - Extensible "critic" for your Schema class hierarchy
21 package MyApp::Schema;
22 use base 'DBIx::Class::Schema';
24 # this is the default on Perl v5.10 and later
25 __PACKAGE__->schema_sanity_checker('DBIx::Class::Schema::SanityChecker');
30 This is the default implementation of the Schema and related classes
31 L<validation framework|DBIx::Class::Schema/schema_sanity_checker>.
33 The validator is B<enabled by default> on perls C<v5.10> and above. See
34 L</Performance considerations> for discussion of the runtime effects.
36 Use of this class begins by invoking L</perform_schema_sanity_checks>
37 (usually via L<DBIx::Class::Schema/connection>), which in turn starts
38 invoking validators I<C<check_$checkname()>> in the order listed in
39 L</available_checks>. For each set of returned errors (if any)
40 I<C<format_$checkname_errors()>> is called and the resulting strings are
41 passed to L</emit_errors>, where final headers are prepended and the entire
42 thing is printed on C<STDERR>.
44 The class does not provide a constructor, due to the lack of state to be
45 passed around: object orientation was chosen purely for the ease of
46 overriding parts of the chain of events as described above. The general
47 pattern of communicating errors between the individual methods (both
48 before and after formatting) is an arrayref of hash references.
52 DBIC existed for more than a decade without any such setup validation
53 fanciness, let alone something that is enabled by default (which in turn
54 L<isn't free|/Performance considerations>). The reason for this relatively
55 drastic change is a set of revamps within the metadata handling framework,
56 in order to resolve once and for all problems like
57 L<RT#107462|https://rt.cpan.org/Ticket/Display.html?id=107462>,
58 L<RT#114440|https://rt.cpan.org/Ticket/Display.html?id=114440>, etc. While
59 DBIC internals are now way more robust than they were before, this comes at
60 a price: some non-issues in code that has been working for a while, will
61 now become hard to explain, or if you are unlucky: B<silent breakages>.
63 Thus, in order to protect existing codebases to the fullest extent possible,
64 the executive decision (and substantial effort) was made to introduce this
65 on-by-default setup validation framework. A massive amount of work has been
66 invested ensuring that none of the builtin checks emit a false-positive:
67 each and every complaint made by these checks B<should be investigated>.
69 =head2 Performance considerations
71 First of all - after your connection has been established - there is B<no
72 runtime penalty> whenever the checks are enabled.
74 By default the checks are triggered every time
75 L<DBIx::Class::Schema/connection> is called. Thus there is a
76 noticeable startup slowdown, most notably during testing (each test is
77 effectively a standalone program connecting anew). As an example the test
78 execution phase of the L<DBIx::Class::Helpers> C<v2.032002> distribution
79 suffers a consistent slowdown of about C<16%>. This is considered a relatively
80 small price to pay for the benefits provided.
82 Nevertheless, there are valid cases for disabling the checks during
83 day-to-day development, and having them run only during CI builds. In fact
84 the test suite of DBIC does exactly this as can be seen in
85 F<t/lib/DBICTest/BaseSchema.pm>:
87 ~/dbic_repo$ git show 39636786 | perl -ne "print if 16..61"
89 Whatever you do, B<please do not disable the checks entirely>: it is not
94 The situation with perl interpreters before C<v5.10.0> is sadly more
95 complicated: due to lack of built-in L<pluggable mro support|mro>, the
96 mechanism used to interrogate various classes is
97 L<< B<much> slower|https://github.com/dbsrgits/dbix-class/commit/296248c3 >>.
98 As a result the very same version of L<DBIx::Class::Helpers>
99 L<mentioned above|/Performance considerations> takes a C<B<220%>> hit on its
100 test execution time (these numbers are observed with the speedups of
101 L<Class::C3::XS> available, without them the slowdown reaches the whopping
104 Therefore, on these versions of perl the sanity checks are B<not enabled> by
105 default. Instead a C<false> placeholder value is inserted into the
106 L<schema_sanity_checker attribute|DBIx::Class::Schema/schema_sanity_checker>,
107 urging the user to decide for themselves how to proceed.
109 It is the author's B<strongest> recommendation to find a way to run the
110 checks on your codebase continuously, even if it takes much longer. Refer to
111 the last paragraph of L</Performance considerations> above for an example how
112 to do this during CI builds only.
114 =head2 Validations provided by this module
116 =head3 no_indirect_method_overrides
118 There are many methods within DBIC which are
119 L<"strictly sugar"|DBIx::Class::MethodAttributes/DBIC_method_is_indirect_sugar>
120 and should never be overridden by your application (e.g. see warnings at the
121 end of L<DBIx::Class::ResultSet/create> and L<DBIx::Class::Schema/connect>).
122 Starting with C<v0.082900> DBIC is much more aggressive in calling the
123 underlying non-sugar methods directly, which in turn means that almost all
124 user-side overrides of sugar methods are never going to be invoked. These
125 situations are now reliably detected and reported individually (you may
126 end up with a lot of output on C<STDERR> due to this).
128 Note: B<ANY AND ALL ISSUES> reported by this check B<*MUST*> be resolved
129 before upgrading DBIC in production. Malfunctioning business logic and/or
130 B<SEVERE DATA LOSS> may result otherwise.
132 =head3 valid_c3_composition
134 Looks through everything returned by L</all_schema_related_classes>, and
135 for any class that B<does not> already utilize L<c3 MRO|mro/The C3 MRO> a
136 L<method shadowing map|App::Isa::Splain/SYNOPSIS> is calculated and then
137 compared to the shadowing map as if C<c3 MRO> was requested in the first place.
138 Any discrepancies are reported in order to clearly identify L<hard to explain
139 bugs|https://blog.afoolishmanifesto.com/posts/mros-and-you> especially when
140 encountered within complex inheritance hierarchies.
142 =head3 no_inheritance_crosscontamination
144 Checks that every individual L<Schema|DBIx::Class::Schema>,
145 L<Storage|DBIx::Class::Storage>, L<ResultSource|DBIx::Class::ResultSource>,
146 L<ResultSet|DBIx::Class::ResultSet>
147 and L<Result|DBIx::Class::Manual::ResultClass> class does not inherit from
148 an unexpected DBIC base class: e.g. an error will be raised if your
149 C<MyApp::Schema> inherits from both C<DBIx::Class::Schema> and
150 C<DBIx::Class::ResultSet>.
154 =head2 perform_schema_sanity_checks
158 =item Arguments: L<$schema|DBIx::Class::Schema>
160 =item Return Value: unspecified (ignored by caller)
164 The entry point expected by the
165 L<validation framework|DBIx::Class::Schema/schema_sanity_checker>. See
166 L</DESCRIPTION> for details.
170 sub perform_schema_sanity_checks {
171 my ($self, $schema) = @_;
173 local $DBIx::Class::_Util::describe_class_query_cache->{'!internal!'} = {}
175 # does not make a measurable difference on 5.10+
176 DBIx::Class::_ENV_::OLD_MRO
178 # the callstack shouldn't really be recursive, but for completeness...
179 ! $DBIx::Class::_Util::describe_class_query_cache->{'!internal!'}
182 my (@errors_found, $schema_desc);
183 for my $ch ( @{ $self->available_checks } ) {
185 my $err = $self->${\"check_$ch"} ( $schema );
187 push @errors_found, map
191 formatted_error => $_,
192 schema_desc => ( $schema_desc ||=
193 ( length ref $schema )
200 $self->${\"format_${ch}_errors"} ( $err )
207 $self->emit_errors(\@errors_found)
211 =head2 available_checks
215 =item Arguments: none
217 =item Return Value: \@list_of_check_names
221 The list of checks L</perform_schema_sanity_checks> will perform on the
222 provided L<$schema|DBIx::Class::Schema> object. For every entry returned
223 by this method, there must be a pair of I<C<check_$checkname()>> and
224 I<C<format_$checkname_errors()>> methods available.
226 Override this method to add checks to the
227 L<currently available set|/Validations provided by this module>.
231 sub available_checks { [qw(
233 no_inheritance_crosscontamination
234 no_indirect_method_overrides
241 =item Arguments: \@list_of_formatted_errors
243 =item Return Value: unspecified (ignored by caller)
247 Takes an array reference of individual errors returned by various
248 I<C<format_$checkname_errors()>> formatters, and outputs them on C<STDERR>.
250 This method is the most convenient integration point for a 3rd party logging
253 Each individual error is expected to be a hash reference with all values being
254 plain strings as follows:
257 schema_desc => $human_readable_description_of_the_passed_in_schema
258 check_name => $name_of_the_check_as_listed_in_available_checks()
259 formatted_error => $error_text_as_returned_by_format_$checkname_errors()
262 If the environment variable C<DBIC_ASSERT_NO_FAILING_SANITY_CHECKS> is set to
263 a true value this method will throw an exception with the same text. Those who
264 prefer to take no chances could set this variable permanently as part of their
269 # *NOT* using carp_unique and the warn framework - make
270 # it harder to accidentaly silence problems via $SIG{__WARN__}
272 #my ($self, $errs) = @_;
274 my @final_error_texts = map {
275 sprintf( "Schema %s failed the '%s' sanity check: %s\n",
276 @{$_}{qw( schema_desc check_name formatted_error )}
282 ) for @final_error_texts;
284 # Do not use the constant - but instead check the env every time
285 # This will allow people to start auditing their apps piecemeal
286 DBIx::Class::Exception->throw( join "\n", @final_error_texts, ' ' )
287 if $ENV{DBIC_ASSERT_NO_FAILING_SANITY_CHECKS};
290 =head2 all_schema_related_classes
294 =item Arguments: L<$schema|DBIx::Class::Schema>
296 =item Return Value: @sorted_list_of_unique_class_names
300 This is a convenience method providing a list (not an arrayref) of
301 "interesting classes" related to the supplied schema. The returned list
302 currently contains the following class names:
306 =item * The L<Schema|DBIx::Class::Schema> class itself
308 =item * The associated L<Storage|DBIx::Class::Schema/storage> class if any
310 =item * The classes of all L<registered ResultSource instances|DBIx::Class::Schema/sources> if any
312 =item * All L<Result|DBIx::Class::ResultSource/result_class> classes for all registered ResultSource instances
314 =item * All L<ResultSet|DBIx::Class::ResultSource/resultset_class> classes for all registered ResultSource instances
320 sub all_schema_related_classes {
321 my ($self, $schema) = @_;
324 ( not defined $_ ) ? ()
325 : ( defined blessed $_ ) ? ref $_
334 } map { $schema->source($_) } $schema->sources ),
339 sub format_no_indirect_method_overrides_errors {
340 # my ($self, $errors) = @_;
343 "Method(s) %s override the convenience shortcut %s::%s(): "
344 . 'it is almost certain these overrides *MAY BE COMPLETELY IGNORED* at '
345 . 'runtime. You MUST reimplement each override to hook a method from the '
346 . "chain of calls within the convenience shortcut as seen when running:\n "
347 . '~$ perl -M%2$s -MDevel::Dwarn -e "Ddie { %3$s => %2$s->can(q(%3$s)) }"',
348 join (', ', map { "$_()" } sort @{ $_->{by} } ),
349 $_->{overriden}{via_class},
350 $_->{overriden}{name},
354 sub check_no_indirect_method_overrides {
355 my ($self, $schema) = @_;
357 my( @err, $seen_shadowing_configurations );
360 for my $method_stack ( map {
361 values %{ describe_class_methods($_)->{methods_with_supers} || {} }
362 } $self->all_schema_related_classes($schema) ) {
364 my $nonsugar_methods;
366 for (@$method_stack) {
368 push @$nonsugar_methods, $_ and next
369 unless $_->{attributes}{DBIC_method_is_indirect_sugar};
375 # this way we report a much better Dwarn oneliner in the error
376 $_->{attributes}{DBIC_method_is_bypassable_resultsource_proxy}
377 ? 'DBIx::Class::ResultSource'
381 by => [ map { "$_->{via_class}::$_->{name}" } @$nonsugar_methods ],
385 ! $seen_shadowing_configurations->{
405 sub format_valid_c3_composition_errors {
406 # my ($self, $errors) = @_;
409 "Class '%s' %s using the '%s' MRO affecting the lookup order of the "
410 . "following method(s): %s. You MUST add the following line to '%1\$s' "
411 . "right after strict/warnings:\n use mro 'c3';",
413 ( ($_->{initial_mro} eq $_->{current_mro}) ? 'is' : 'was originally' ),
415 join (', ', map { "$_()" } sort keys %{$_->{affected_methods}} ),
421 map { $_ => 1 } @{mro::get_linear_isa("DBIx::Class")}
424 sub check_valid_c3_composition {
425 my ($self, $schema) = @_;
430 # A *very* involved check, to absolutely minimize false positives
431 # If this check returns an issue - it *better be* a real one
433 for my $class ( $self->all_schema_related_classes($schema) ) {
437 describe_class_methods({
439 ( ${"${class}::__INITIAL_MRO_UPON_DBIC_LOAD__"}
440 ? ( use_mro => ${"${class}::__INITIAL_MRO_UPON_DBIC_LOAD__"} )
446 # is there anything to check?
448 ! $desc->{mro}{is_c3}
450 $desc->{methods_with_supers}
452 my @potentially_problematic_method_stacks =
455 # at least 2 variants came via inheritance (not ours)
457 (grep { $_->{via_class} ne $class } @$_)
463 # last ditch effort to skip examining an alternative mro
464 # IFF the entire "foreign" stack is located in the "base isa"
466 # This allows for extra efficiency (as there are several
467 # with_supers methods that would always be there), but more
468 # importantly saves one from tripping on the nonsensical yet
469 # begrudgingly functional (as in - no adverse effects):
471 # use base 'DBIx::Class';
472 # use base 'DBIx::Class::Schema';
477 $_->{via_class} ne $class
479 # not from the base stack either
480 ! $base_ISA->{$_->{via_class}}
484 values %{ $desc->{methods_with_supers} }
487 my $affected_methods;
489 for my $stack (@potentially_problematic_method_stacks) {
491 # If we got so far - we need to see what the class would look
492 # like under c3 and compare, sigh
494 # Note that if the hierarchy is *really* fucked (like the above
495 # double-base e.g.) then recalc under 'c3' WILL FAIL, hence the
496 # extra eval: if we fail we report things as "jumbled up"
498 $affected_methods->{$stack->[0]{name}} = [
499 map { $_->{via_class} } @$stack
500 ] unless dbic_internal_try {
505 describe_class_methods({ class => $class, use_mro => 'c3' })
507 ->{$stack->[0]{name}}
515 initial_mro => $desc->{mro}{type},
516 current_mro => mro::get_mro($class),
517 affected_methods => $affected_methods,
518 } if $affected_methods;
525 sub format_no_inheritance_crosscontamination_errors {
526 # my ($self, $errors) = @_;
529 "Class '%s' registered in the role of '%s' unexpectedly inherits '%s': "
530 . 'you must resolve this by either removing an erroneous `use base` call '
531 . "or switching to Moo(se)-style delegation (i.e. the 'handles' keyword)",
534 $_->{unexpectedly_inherits},
538 sub check_no_inheritance_crosscontamination {
539 my ($self, $schema) = @_;
544 Schema => [ $schema ],
545 Storage => [ $schema->storage ],
546 ResultSource => [ map { $schema->source($_) } $schema->sources ],
549 $to_check->{ResultSet} = [
550 map { $_->resultset_class } @{$to_check->{ResultSource}}
553 $to_check->{Core} = [
554 map { $_->result_class } @{$to_check->{ResultSource}}
557 # Reduce everything to a unique sorted list of class names
558 $_ = [ sort( uniq( map {
559 ( not defined $_ ) ? ()
560 : ( defined blessed $_ ) ? ref $_
562 } @$_ ) ) ] for values %$to_check;
564 for my $group ( sort keys %$to_check ) {
565 for my $class ( @{ $to_check->{$group} } ) {
566 for my $foreign_base (
567 map { "DBIx::Class::$_" } sort grep { $_ ne $group } keys %$to_check
572 type => ( $group eq 'Core' ? 'ResultClass' : $group ),
573 unexpectedly_inherits => $foreign_base
574 } if $class->isa($foreign_base);
586 =head1 FURTHER QUESTIONS?
588 Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
590 =head1 COPYRIGHT AND LICENSE
592 This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
593 by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
594 redistribute it and/or modify it under the same terms as the
595 L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.