Fixup several tests silently broken by 12e7015a
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Schema / SanityChecker.pm
1 package DBIx::Class::Schema::SanityChecker;
2
3 use strict;
4 use warnings;
5
6 use DBIx::Class::_Util qw(
7   dbic_internal_try refdesc uniq serialize
8   describe_class_methods emit_loud_diag
9 );
10 use DBIx::Class ();
11 use Scalar::Util qw( blessed refaddr );
12 use namespace::clean;
13
14 =head1 NAME
15
16 DBIx::Class::Schema::SanityChecker - Extensible "critic" for your Schema class hierarchy
17
18 =head1 SYNOPSIS
19
20   package MyApp::Schema;
21   use base 'DBIx::Class::Schema';
22
23   # this is the default setting
24   __PACKAGE__->schema_sanity_checker('DBIx::Class::Schema::SanityChecker');
25   ...
26
27 =head1 DESCRIPTION
28
29 This is the default implementation of the Schema and related classes
30 L<validation framework|DBIx::Class::Schema/schema_sanity_checker>.
31
32 The validator is B<enabled by default>. See L</Performance considerations>
33 for discussion of the runtime effects.
34
35 Use of this class begins by invoking L</perform_schema_sanity_checks>
36 (usually via L<DBIx::Class::Schema/connection>), which in turn starts
37 invoking validators I<C<check_$checkname()>> in the order listed in
38 L</available_checks>. For each set of returned errors (if any)
39 I<C<format_$checkname_errors()>> is called and the resulting strings are
40 passed to L</emit_errors>, where final headers are prepended and the entire
41 thing is printed on C<STDERR>.
42
43 The class does not provide a constructor, due to the lack of state to be
44 passed around: object orientation was chosen purely for the ease of
45 overriding parts of the chain of events as described above. The general
46 pattern of communicating errors between the individual methods (both
47 before and after formatting) is an arrayref of hash references.
48
49 =head2 WHY
50
51 DBIC existed for more than a decade without any such setup validation
52 fanciness, let alone something that is enabled by default (which in turn
53 L<isn't free|/Performance considerations>). The reason for this relatively
54 drastic change is a set of revamps within the metadata handling framework,
55 in order to resolve once and for all problems like
56 L<RT#107462|https://rt.cpan.org/Ticket/Display.html?id=107462>,
57 L<RT#114440|https://rt.cpan.org/Ticket/Display.html?id=114440>, etc. While
58 DBIC internals are now way more robust than they were before, this comes at
59 a price: some non-issues in code that has been working for a while, will
60 now become hard to explain, or if you are unlucky: B<silent breakages>.
61
62 Thus, in order to protect existing codebases to the fullest extent possible,
63 the executive decision (and substantial effort) was made to introduce this
64 on-by-default setup validation framework. A massive amount of work has been
65 invested ensuring that none of the builtin checks emit a false-positive:
66 each and every complaint made by these checks B<should be investigated>.
67
68 =head2 Performance considerations
69
70 First of all - after your connection has been established - there is B<no
71 runtime penalty> whenever the checks are enabled.
72
73 By default the checks are triggered every time
74 L<DBIx::Class::Schema/connection> is called. Thus there is a
75 noticeable startup slowdown, most notably during testing (each test is
76 effectively a standalone program connecting anew). As an example the test
77 execution phase of the L<DBIx::Class::Helpers> C<v2.032002> distribution
78 suffers a consistent slowdown of about C<16%>. This is considered a relatively
79 small price to pay for the benefits provided.
80
81 Nevertheless, there are valid cases for disabling the checks during
82 day-to-day development, and having them run only during CI builds. In fact
83 the test suite of DBIC does exactly this as can be seen in
84 F<t/lib/DBICTest/BaseSchema.pm>:
85
86  ~/dbic_repo$ git show 39636786 | perl -ne "print if 16..61"
87
88 Whatever you do, B<please do not disable the checks entirely>: it is not
89 worth the risk.
90
91 =head3 Perl5.8
92
93 The situation with perl interpreters before C<v5.10.0> is sadly more
94 complicated: due to lack of built-in L<pluggable mro support|mro>, the
95 mechanism used to interrogate various classes is
96 L<< B<much> slower|https://github.com/dbsrgits/dbix-class/commit/296248c3 >>.
97 As a result the very same version of L<DBIx::Class::Helpers>
98 L<mentioned above|/Performance considerations> takes a C<B<220%>> hit on its
99 test execution time (these numbers are observed with the speedups of
100 L<Class::C3::XS> available, without them the slowdown reaches the whopping
101 C<350%>).
102
103 It is the author's B<strongest> recommendation to find a way to run the
104 checks on your codebase continuously, even if it takes much longer. Refer to
105 the last paragraph of L</Performance considerations> above for an example how
106 to do this during CI builds only.
107
108 =head2 Validations provided by this module
109
110 =head3 no_indirect_method_overrides
111
112 There are many methods within DBIC which are
113 L<"strictly sugar"|DBIx::Class::MethodAttributes/DBIC_method_is_indirect_sugar>
114 and should never be overridden by your application (e.g. see warnings at the
115 end of L<DBIx::Class::ResultSet/create> and L<DBIx::Class::Schema/connect>).
116 Starting with C<v0.082900> DBIC is much more aggressive in calling the
117 underlying non-sugar methods directly, which in turn means that almost all
118 user-side overrides of sugar methods are never going to be invoked. These
119 situations are now reliably detected and reported individually (you may
120 end up with a lot of output on C<STDERR> due to this).
121
122 Note: B<ANY AND ALL ISSUES> reported by this check B<*MUST*> be resolved
123 before upgrading DBIC in production. Malfunctioning business logic and/or
124 B<SEVERE DATA LOSS> may result otherwise.
125
126 =head3 valid_c3_composition
127
128 Looks through everything returned by L</all_schema_related_classes>, and
129 for any class that B<does not> already utilize L<c3 MRO|mro/The C3 MRO> a
130 L<method shadowing map|App::Isa::Splain/SYNOPSIS> is calculated and then
131 compared to the shadowing map as if C<c3 MRO> was requested in the first place.
132 Any discrepancies are reported in order to clearly identify L<hard to explain
133 bugs|https://blog.afoolishmanifesto.com/posts/mros-and-you> especially when
134 encountered within complex inheritance hierarchies.
135
136 =head3 no_inheritance_crosscontamination
137
138 Checks that every individual L<Schema|DBIx::Class::Schema>,
139 L<Storage|DBIx::Class::Storage>, L<ResultSource|DBIx::Class::ResultSource>,
140 L<ResultSet|DBIx::Class::ResultSet>
141 and L<Result|DBIx::Class::Manual::ResultClass> class does not inherit from
142 an unexpected DBIC base class: e.g. an error will be raised if your
143 C<MyApp::Schema> inherits from both C<DBIx::Class::Schema> and
144 C<DBIx::Class::ResultSet>.
145
146 =head1 METHODS
147
148 =head2 perform_schema_sanity_checks
149
150 =over
151
152 =item Arguments: L<$schema|DBIx::Class::Schema>
153
154 =item Return Value: unspecified (ignored by caller)
155
156 =back
157
158 The entry point expected by the
159 L<validation framework|DBIx::Class::Schema/schema_sanity_checker>. See
160 L</DESCRIPTION> for details.
161
162 =cut
163
164 sub perform_schema_sanity_checks {
165   my ($self, $schema) = @_;
166
167   local $DBIx::Class::_Util::describe_class_query_cache->{'!internal!'} = {}
168     if
169       # does not make a measurable difference on 5.10+
170       DBIx::Class::_ENV_::OLD_MRO
171         and
172       # the callstack shouldn't really be recursive, but for completeness...
173       ! $DBIx::Class::_Util::describe_class_query_cache->{'!internal!'}
174   ;
175
176   my (@errors_found, $schema_desc);
177   for my $ch ( @{ $self->available_checks } ) {
178
179     my $err = $self->${\"check_$ch"} ( $schema );
180
181     push @errors_found, map
182       {
183         {
184           check_name => $ch,
185           formatted_error => $_,
186           schema_desc => ( $schema_desc ||=
187             ( length ref $schema )
188               ? refdesc $schema
189               : "'$schema'"
190           ),
191         }
192       }
193       @{
194         $self->${\"format_${ch}_errors"} ( $err )
195           ||
196         []
197       }
198     if @$err;
199   }
200
201   $self->emit_errors(\@errors_found)
202     if @errors_found;
203 }
204
205 =head2 available_checks
206
207 =over
208
209 =item Arguments: none
210
211 =item Return Value: \@list_of_check_names
212
213 =back
214
215 The list of checks L</perform_schema_sanity_checks> will perform on the
216 provided L<$schema|DBIx::Class::Schema> object. For every entry returned
217 by this method, there must be a pair of I<C<check_$checkname()>> and
218 I<C<format_$checkname_errors()>> methods available.
219
220 Override this method to add checks to the
221 L<currently available set|/Validations provided by this module>.
222
223 =cut
224
225 sub available_checks { [qw(
226   valid_c3_composition
227   no_inheritance_crosscontamination
228   no_indirect_method_overrides
229 )] }
230
231 =head2 emit_errors
232
233 =over
234
235 =item Arguments: \@list_of_formatted_errors
236
237 =item Return Value: unspecified (ignored by caller)
238
239 =back
240
241 Takes an array reference of individual errors returned by various
242 I<C<format_$checkname_errors()>> formatters, and outputs them on C<STDERR>.
243
244 This method is the most convenient integration point for a 3rd party logging
245 framework.
246
247 Each individual error is expected to be a hash reference with all values being
248 plain strings as follows:
249
250   {
251     schema_desc     => $human_readable_description_of_the_passed_in_schema
252     check_name      => $name_of_the_check_as_listed_in_available_checks()
253     formatted_error => $error_text_as_returned_by_format_$checkname_errors()
254   }
255
256 If the environment variable C<DBIC_ASSERT_NO_FAILING_SANITY_CHECKS> is set to
257 a true value this method will throw an exception with the same text. Those who
258 prefer to take no chances could set this variable permanently as part of their
259 deployment scripts.
260
261 =cut
262
263 # *NOT* using carp_unique and the warn framework - make
264 # it harder to accidentaly silence problems via $SIG{__WARN__}
265 sub emit_errors {
266   #my ($self, $errs) = @_;
267
268   my @final_error_texts = map {
269     sprintf( "Schema %s failed the '%s' sanity check: %s\n",
270       @{$_}{qw( schema_desc check_name formatted_error )}
271     );
272   } @{$_[1]};
273
274   emit_loud_diag(
275     msg => $_
276   ) for @final_error_texts;
277
278   # Do not use the constant - but instead check the env every time
279   # This will allow people to start auditing their apps piecemeal
280   DBIx::Class::Exception->throw( join "\n",  @final_error_texts, ' ' )
281     if $ENV{DBIC_ASSERT_NO_FAILING_SANITY_CHECKS};
282 }
283
284 =head2 all_schema_related_classes
285
286 =over
287
288 =item Arguments: L<$schema|DBIx::Class::Schema>
289
290 =item Return Value: @sorted_list_of_unique_class_names
291
292 =back
293
294 This is a convenience method providing a list (not an arrayref) of
295 "interesting classes" related to the supplied schema. The returned list
296 currently contains the following class names:
297
298 =over
299
300 =item * The L<Schema|DBIx::Class::Schema> class itself
301
302 =item * The associated L<Storage|DBIx::Class::Schema/storage> class if any
303
304 =item * The classes of all L<registered ResultSource instances|DBIx::Class::Schema/sources> if any
305
306 =item * All L<Result|DBIx::Class::ResultSource/result_class> classes for all registered ResultSource instances
307
308 =item * All L<ResultSet|DBIx::Class::ResultSource/resultset_class> classes for all registered ResultSource instances
309
310 =back
311
312 =cut
313
314 sub all_schema_related_classes {
315   my ($self, $schema) = @_;
316
317   sort( uniq( map {
318     ( not defined $_ )      ? ()
319   : ( defined blessed $_ )  ? ref $_
320                             : $_
321   } (
322     $schema,
323     $schema->storage,
324     ( map {
325       $_,
326       $_->result_class,
327       $_->resultset_class,
328     } map { $schema->source($_) } $schema->sources ),
329   )));
330 }
331
332
333 sub format_no_indirect_method_overrides_errors {
334   # my ($self, $errors) = @_;
335
336   [ map { sprintf(
337     "Method(s) %s override the convenience shortcut %s::%s(): "
338   . 'it is almost certain these overrides *MAY BE COMPLETELY IGNORED* at '
339   . 'runtime. You MUST reimplement each override to hook a method from the '
340   . "chain of calls within the convenience shortcut as seen when running:\n  "
341   . '~$ perl -M%2$s -MDevel::Dwarn -e "Ddie { %3$s => %2$s->can(q(%3$s)) }"',
342     join (', ', map { "$_()" } sort @{ $_->{by} } ),
343     $_->{overridden}{via_class},
344     $_->{overridden}{name},
345   )} @{ $_[1] } ]
346 }
347
348 sub check_no_indirect_method_overrides {
349   my ($self, $schema) = @_;
350
351   my( @err, $seen_shadowing_configurations );
352
353   METHOD_STACK:
354   for my $method_stack ( map {
355     values %{ describe_class_methods($_)->{methods_with_supers} || {} }
356   } $self->all_schema_related_classes($schema) ) {
357
358     my $nonsugar_methods;
359
360     for (@$method_stack) {
361
362       push @$nonsugar_methods, $_ and next
363         unless $_->{attributes}{DBIC_method_is_indirect_sugar};
364
365       push @err, {
366         overridden => {
367           name => $_->{name},
368           via_class => (
369             # this way we report a much better Dwarn oneliner in the error
370             $_->{attributes}{DBIC_method_is_bypassable_resultsource_proxy}
371               ? 'DBIx::Class::ResultSource'
372               : $_->{via_class}
373           ),
374         },
375         by => [ map { "$_->{via_class}::$_->{name}" } @$nonsugar_methods ],
376       } if (
377           $nonsugar_methods
378             and
379           ! $seen_shadowing_configurations->{
380             join "\0",
381               map
382                 { refaddr $_ }
383                 (
384                   $_,
385                   @$nonsugar_methods,
386                 )
387           }++
388         )
389       ;
390
391       next METHOD_STACK;
392     }
393   }
394
395   \@err
396 }
397
398
399 sub format_valid_c3_composition_errors {
400   # my ($self, $errors) = @_;
401
402   [ map { sprintf(
403     "Class '%s' %s using the '%s' MRO affecting the lookup order of the "
404   . "following method(s): %s. You MUST add the following line to '%1\$s' "
405   . "right after strict/warnings:\n  use mro 'c3';",
406     $_->{class},
407     ( ($_->{initial_mro} eq $_->{current_mro}) ? 'is' : 'was originally' ),
408     $_->{initial_mro},
409     join (', ', map { "$_()" } sort keys %{$_->{affected_methods}} ),
410   )} @{ $_[1] } ]
411 }
412
413
414 my $base_ISA = {
415   map { $_ => 1 } @{mro::get_linear_isa("DBIx::Class")}
416 };
417
418 sub check_valid_c3_composition {
419   my ($self, $schema) = @_;
420
421   my @err;
422
423   #
424   # A *very* involved check, to absolutely minimize false positives
425   # If this check returns an issue - it *better be* a real one
426   #
427   for my $class ( $self->all_schema_related_classes($schema) ) {
428
429     my $desc = do {
430       no strict 'refs';
431       describe_class_methods({
432         class => $class,
433         ( ${"${class}::__INITIAL_MRO_UPON_DBIC_LOAD__"}
434           ? ( use_mro => ${"${class}::__INITIAL_MRO_UPON_DBIC_LOAD__"} )
435           : ()
436         ),
437       })
438     };
439
440     # is there anything to check?
441     next unless (
442       ! $desc->{mro}{is_c3}
443         and
444       $desc->{methods_with_supers}
445         and
446       my @potentially_problematic_method_stacks =
447         grep
448           {
449             # at least 2 variants came via inheritance (not ours)
450             (
451               (grep { $_->{via_class} ne $class } @$_)
452                 >
453               1
454             )
455               and
456             #
457             # last ditch effort to skip examining an alternative mro
458             # IFF the entire "foreign" stack is located in the "base isa"
459             #
460             # This allows for extra efficiency (as there are several
461             # with_supers methods that would always be there), but more
462             # importantly saves one from tripping on the nonsensical yet
463             # begrudgingly functional (as in - no adverse effects):
464             #
465             #  use base 'DBIx::Class';
466             #  use base 'DBIx::Class::Schema';
467             #
468             (
469               grep {
470                 # not ours
471                 $_->{via_class} ne $class
472                   and
473                 # not from the base stack either
474                 ! $base_ISA->{$_->{via_class}}
475               } @$_
476             )
477           }
478           values %{ $desc->{methods_with_supers} }
479     );
480
481     my $affected_methods;
482
483     for my $stack (@potentially_problematic_method_stacks) {
484
485       # If we got so far - we need to see what the class would look
486       # like under c3 and compare, sigh
487       #
488       # Note that if the hierarchy is *really* fucked (like the above
489       # double-base e.g.) then recalc under 'c3' WILL FAIL, hence the
490       # extra eval: if we fail we report things as "jumbled up"
491       #
492       $affected_methods->{$stack->[0]{name}} = [
493         map { $_->{via_class} } @$stack
494       ] unless dbic_internal_try {
495
496         serialize($stack)
497           eq
498         serialize(
499           describe_class_methods({ class => $class, use_mro => 'c3' })
500                                ->{methods}
501                                 ->{$stack->[0]{name}}
502         )
503       };
504     }
505
506     push @err, {
507       class => $class,
508       initial_linear_isa => $desc->{linear_isa},
509       current_linear_isa => do { (undef, my @isa) = @{ mro::get_linear_isa($class) }; \@isa },
510       initial_mro => $desc->{mro}{type},
511       current_mro => mro::get_mro($class),
512       affected_methods => $affected_methods,
513     } if $affected_methods;
514   }
515
516   \@err;
517 }
518
519
520 sub format_no_inheritance_crosscontamination_errors {
521   # my ($self, $errors) = @_;
522
523   [ map { sprintf(
524     "Class '%s' registered in the role of '%s' unexpectedly inherits '%s': "
525   . 'you must resolve this by either removing an erroneous `use base` call '
526   . "or switching to Moo(se)-style delegation (i.e. the 'handles' keyword)",
527     $_->{class},
528     $_->{type},
529     $_->{unexpectedly_inherits},
530   )} @{ $_[1] } ]
531 }
532
533 sub check_no_inheritance_crosscontamination {
534   my ($self, $schema) = @_;
535
536   my @err;
537
538   my $to_check = {
539     Schema => [ $schema ],
540     Storage => [ $schema->storage ],
541     ResultSource => [ map { $schema->source($_) } $schema->sources ],
542   };
543
544   $to_check->{ResultSet} = [
545     map { $_->resultset_class } @{$to_check->{ResultSource}}
546   ];
547
548   $to_check->{Core} = [
549     map { $_->result_class } @{$to_check->{ResultSource}}
550   ];
551
552   # Reduce everything to a unique sorted list of class names
553   $_ = [ sort( uniq( map {
554     ( not defined $_ )      ? ()
555   : ( defined blessed $_ )  ? ref $_
556                             : $_
557   } @$_ ) ) ] for values %$to_check;
558
559   for my $group ( sort keys %$to_check ) {
560     for my $class ( @{ $to_check->{$group} } ) {
561       for my $foreign_base (
562         map { "DBIx::Class::$_" } sort grep { $_ ne $group } keys %$to_check
563       ) {
564
565         push @err, {
566           class => $class,
567           type => ( $group eq 'Core' ? 'ResultClass' : $group ),
568           unexpectedly_inherits => $foreign_base
569         } if $class->isa($foreign_base);
570       }
571     }
572   }
573
574   \@err;
575 }
576
577 1;
578
579 __END__
580
581 =head1 FURTHER QUESTIONS?
582
583 Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
584
585 =head1 COPYRIGHT AND LICENSE
586
587 This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
588 by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
589 redistribute it and/or modify it under the same terms as the
590 L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.