b048edd9d09c0abee97254c1cd33aca7c0bff64b
[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 DBIx::Class::Exception ();
12 use Scalar::Util qw( blessed refaddr );
13 use namespace::clean;
14
15 =head1 NAME
16
17 DBIx::Class::Schema::SanityChecker - Extensible "critic" for your Schema class hierarchy
18
19 =head1 SYNOPSIS
20
21   package MyApp::Schema;
22   use base 'DBIx::Class::Schema';
23
24   # this is the default setting
25   __PACKAGE__->schema_sanity_checker('DBIx::Class::Schema::SanityChecker');
26   ...
27
28 =head1 DESCRIPTION
29
30 This is the default implementation of the Schema and related classes
31 L<validation framework|DBIx::Class::Schema/schema_sanity_checker>.
32
33 The validator is B<enabled by default>. See L</Performance considerations>
34 for discussion of the runtime effects.
35
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>.
43
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.
49
50 =head2 WHY
51
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>.
62
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>.
68
69 =head2 Performance considerations
70
71 First of all - after your connection has been established - there is B<no
72 runtime penalty> whenever the checks are enabled.
73
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.
81
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>:
86
87  ~/dbic_repo$ git show 39636786 | perl -ne "print if 16..61"
88
89 Whatever you do, B<please do not disable the checks entirely>: it is not
90 worth the risk.
91
92 =head3 Perl5.8
93
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
102 C<350%>).
103
104 It is the author's B<strongest> recommendation to find a way to run the
105 checks on your codebase continuously, even if it takes much longer. Refer to
106 the last paragraph of L</Performance considerations> above for an example how
107 to do this during CI builds only.
108
109 =head2 Validations provided by this module
110
111 =head3 no_indirect_method_overrides
112
113 There are many methods within DBIC which are
114 L<"strictly sugar"|DBIx::Class::MethodAttributes/DBIC_method_is_indirect_sugar>
115 and should never be overridden by your application (e.g. see warnings at the
116 end of L<DBIx::Class::ResultSet/create> and L<DBIx::Class::Schema/connect>).
117 Starting with C<v0.082900> DBIC is much more aggressive in calling the
118 underlying non-sugar methods directly, which in turn means that almost all
119 user-side overrides of sugar methods are never going to be invoked. These
120 situations are now reliably detected and reported individually (you may
121 end up with a lot of output on C<STDERR> due to this).
122
123 Note: B<ANY AND ALL ISSUES> reported by this check B<*MUST*> be resolved
124 before upgrading DBIC in production. Malfunctioning business logic and/or
125 B<SEVERE DATA LOSS> may result otherwise.
126
127 =head3 valid_c3_composition
128
129 Looks through everything returned by L</all_schema_related_classes>, and
130 for any class that B<does not> already utilize L<c3 MRO|mro/The C3 MRO> a
131 L<method shadowing map|App::Isa::Splain/SYNOPSIS> is calculated and then
132 compared to the shadowing map as if C<c3 MRO> was requested in the first place.
133 Any discrepancies are reported in order to clearly identify L<hard to explain
134 bugs|https://blog.afoolishmanifesto.com/posts/mros-and-you> especially when
135 encountered within complex inheritance hierarchies.
136
137 =head3 no_inheritance_crosscontamination
138
139 Checks that every individual L<Schema|DBIx::Class::Schema>,
140 L<Storage|DBIx::Class::Storage>, L<ResultSource|DBIx::Class::ResultSource>,
141 L<ResultSet|DBIx::Class::ResultSet>
142 and L<Result|DBIx::Class::Manual::ResultClass> class does not inherit from
143 an unexpected DBIC base class: e.g. an error will be raised if your
144 C<MyApp::Schema> inherits from both C<DBIx::Class::Schema> and
145 C<DBIx::Class::ResultSet>.
146
147 =head1 METHODS
148
149 =head2 perform_schema_sanity_checks
150
151 =over
152
153 =item Arguments: L<$schema|DBIx::Class::Schema>
154
155 =item Return Value: unspecified (ignored by caller)
156
157 =back
158
159 The entry point expected by the
160 L<validation framework|DBIx::Class::Schema/schema_sanity_checker>. See
161 L</DESCRIPTION> for details.
162
163 =cut
164
165 sub perform_schema_sanity_checks {
166   my ($self, $schema) = @_;
167
168   local $DBIx::Class::_Util::describe_class_query_cache->{'!internal!'} = {}
169     if
170       # does not make a measurable difference on 5.10+
171       DBIx::Class::_ENV_::OLD_MRO
172         and
173       # the callstack shouldn't really be recursive, but for completeness...
174       ! $DBIx::Class::_Util::describe_class_query_cache->{'!internal!'}
175   ;
176
177   my (@errors_found, $schema_desc);
178   for my $ch ( @{ $self->available_checks } ) {
179
180     my $err = $self->${\"check_$ch"} ( $schema );
181
182     push @errors_found, map
183       {
184         {
185           check_name => $ch,
186           formatted_error => $_,
187           schema_desc => ( $schema_desc ||=
188             ( length ref $schema )
189               ? refdesc $schema
190               : "'$schema'"
191           ),
192         }
193       }
194       @{
195         $self->${\"format_${ch}_errors"} ( $err )
196           ||
197         []
198       }
199     if @$err;
200   }
201
202   $self->emit_errors(\@errors_found)
203     if @errors_found;
204 }
205
206 =head2 available_checks
207
208 =over
209
210 =item Arguments: none
211
212 =item Return Value: \@list_of_check_names
213
214 =back
215
216 The list of checks L</perform_schema_sanity_checks> will perform on the
217 provided L<$schema|DBIx::Class::Schema> object. For every entry returned
218 by this method, there must be a pair of I<C<check_$checkname()>> and
219 I<C<format_$checkname_errors()>> methods available.
220
221 Override this method to add checks to the
222 L<currently available set|/Validations provided by this module>.
223
224 =cut
225
226 sub available_checks { [qw(
227   valid_c3_composition
228   no_inheritance_crosscontamination
229   no_indirect_method_overrides
230 )] }
231
232 =head2 emit_errors
233
234 =over
235
236 =item Arguments: \@list_of_formatted_errors
237
238 =item Return Value: unspecified (ignored by caller)
239
240 =back
241
242 Takes an array reference of individual errors returned by various
243 I<C<format_$checkname_errors()>> formatters, and outputs them on C<STDERR>.
244
245 This method is the most convenient integration point for a 3rd party logging
246 framework.
247
248 Each individual error is expected to be a hash reference with all values being
249 plain strings as follows:
250
251   {
252     schema_desc     => $human_readable_description_of_the_passed_in_schema
253     check_name      => $name_of_the_check_as_listed_in_available_checks()
254     formatted_error => $error_text_as_returned_by_format_$checkname_errors()
255   }
256
257 If the environment variable C<DBIC_ASSERT_NO_FAILING_SANITY_CHECKS> is set to
258 a true value this method will throw an exception with the same text. Those who
259 prefer to take no chances could set this variable permanently as part of their
260 deployment scripts.
261
262 =cut
263
264 # *NOT* using carp_unique and the warn framework - make
265 # it harder to accidentaly silence problems via $SIG{__WARN__}
266 sub emit_errors {
267   #my ($self, $errs) = @_;
268
269   my @final_error_texts = map {
270     sprintf( "Schema %s failed the '%s' sanity check: %s\n",
271       @{$_}{qw( schema_desc check_name formatted_error )}
272     );
273   } @{$_[1]};
274
275   emit_loud_diag(
276     msg => $_
277   ) for @final_error_texts;
278
279   # Do not use the constant - but instead check the env every time
280   # This will allow people to start auditing their apps piecemeal
281   DBIx::Class::Exception->throw( join "\n",  @final_error_texts, ' ' )
282     if $ENV{DBIC_ASSERT_NO_FAILING_SANITY_CHECKS};
283 }
284
285 =head2 all_schema_related_classes
286
287 =over
288
289 =item Arguments: L<$schema|DBIx::Class::Schema>
290
291 =item Return Value: @sorted_list_of_unique_class_names
292
293 =back
294
295 This is a convenience method providing a list (not an arrayref) of
296 "interesting classes" related to the supplied schema. The returned list
297 currently contains the following class names:
298
299 =over
300
301 =item * The L<Schema|DBIx::Class::Schema> class itself
302
303 =item * The associated L<Storage|DBIx::Class::Schema/storage> class if any
304
305 =item * The classes of all L<registered ResultSource instances|DBIx::Class::Schema/sources> if any
306
307 =item * All L<Result|DBIx::Class::ResultSource/result_class> classes for all registered ResultSource instances
308
309 =item * All L<ResultSet|DBIx::Class::ResultSource/resultset_class> classes for all registered ResultSource instances
310
311 =back
312
313 =cut
314
315 sub all_schema_related_classes {
316   my ($self, $schema) = @_;
317
318   sort( uniq( map {
319     ( not defined $_ )      ? ()
320   : ( defined blessed $_ )  ? ref $_
321                             : $_
322   } (
323     $schema,
324     $schema->storage,
325     ( map {
326       $_,
327       $_->result_class,
328       $_->resultset_class,
329     } map { $schema->source($_) } $schema->sources ),
330   )));
331 }
332
333
334 sub format_no_indirect_method_overrides_errors {
335   # my ($self, $errors) = @_;
336
337   [ map { sprintf(
338     "Method(s) %s override the convenience shortcut %s::%s(): "
339   . 'it is almost certain these overrides *MAY BE COMPLETELY IGNORED* at '
340   . 'runtime. You MUST reimplement each override to hook a method from the '
341   . "chain of calls within the convenience shortcut as seen when running:\n  "
342   . '~$ perl -M%2$s -MDevel::Dwarn -e "Ddie { %3$s => %2$s->can(q(%3$s)) }"',
343     join (', ', map { "$_()" } sort @{ $_->{by} } ),
344     $_->{overridden}{via_class},
345     $_->{overridden}{name},
346   )} @{ $_[1] } ]
347 }
348
349 sub check_no_indirect_method_overrides {
350   my ($self, $schema) = @_;
351
352   my( @err, $seen_shadowing_configurations );
353
354   METHOD_STACK:
355   for my $method_stack ( map {
356     values %{ describe_class_methods($_)->{methods_with_supers} || {} }
357   } $self->all_schema_related_classes($schema) ) {
358
359     my $nonsugar_methods;
360
361     for (@$method_stack) {
362
363       push @$nonsugar_methods, $_ and next
364         unless $_->{attributes}{DBIC_method_is_indirect_sugar};
365
366       push @err, {
367         overridden => {
368           name => $_->{name},
369           via_class => (
370             # this way we report a much better Dwarn oneliner in the error
371             $_->{attributes}{DBIC_method_is_bypassable_resultsource_proxy}
372               ? 'DBIx::Class::ResultSource'
373               : $_->{via_class}
374           ),
375         },
376         by => [ map { "$_->{via_class}::$_->{name}" } @$nonsugar_methods ],
377       } if (
378           $nonsugar_methods
379             and
380           ! $seen_shadowing_configurations->{
381             join "\0",
382               map
383                 { refaddr $_ }
384                 (
385                   $_,
386                   @$nonsugar_methods,
387                 )
388           }++
389         )
390       ;
391
392       next METHOD_STACK;
393     }
394   }
395
396   \@err
397 }
398
399
400 sub format_valid_c3_composition_errors {
401   # my ($self, $errors) = @_;
402
403   [ map { sprintf(
404     "Class '%s' %s using the '%s' MRO affecting the lookup order of the "
405   . "following method(s): %s. You MUST add the following line to '%1\$s' "
406   . "right after strict/warnings:\n  use mro 'c3';",
407     $_->{class},
408     ( ($_->{initial_mro} eq $_->{current_mro}) ? 'is' : 'was originally' ),
409     $_->{initial_mro},
410     join (', ', map { "$_()" } sort keys %{$_->{affected_methods}} ),
411   )} @{ $_[1] } ]
412 }
413
414
415 my $base_ISA = {
416   map { $_ => 1 } @{mro::get_linear_isa("DBIx::Class")}
417 };
418
419 sub check_valid_c3_composition {
420   my ($self, $schema) = @_;
421
422   my @err;
423
424   #
425   # A *very* involved check, to absolutely minimize false positives
426   # If this check returns an issue - it *better be* a real one
427   #
428   for my $class ( $self->all_schema_related_classes($schema) ) {
429
430     my $desc = do {
431       no strict 'refs';
432       describe_class_methods({
433         class => $class,
434         ( ${"${class}::__INITIAL_MRO_UPON_DBIC_LOAD__"}
435           ? ( use_mro => ${"${class}::__INITIAL_MRO_UPON_DBIC_LOAD__"} )
436           : ()
437         ),
438       })
439     };
440
441     # is there anything to check?
442     next unless (
443       ! $desc->{mro}{is_c3}
444         and
445       $desc->{methods_with_supers}
446         and
447       my @potentially_problematic_method_stacks =
448         grep
449           {
450             # at least 2 variants came via inheritance (not ours)
451             (
452               (grep { $_->{via_class} ne $class } @$_)
453                 >
454               1
455             )
456               and
457             #
458             # last ditch effort to skip examining an alternative mro
459             # IFF the entire "foreign" stack is located in the "base isa"
460             #
461             # This allows for extra efficiency (as there are several
462             # with_supers methods that would always be there), but more
463             # importantly saves one from tripping on the nonsensical yet
464             # begrudgingly functional (as in - no adverse effects):
465             #
466             #  use base 'DBIx::Class';
467             #  use base 'DBIx::Class::Schema';
468             #
469             (
470               grep {
471                 # not ours
472                 $_->{via_class} ne $class
473                   and
474                 # not from the base stack either
475                 ! $base_ISA->{$_->{via_class}}
476               } @$_
477             )
478           }
479           values %{ $desc->{methods_with_supers} }
480     );
481
482     my $affected_methods;
483
484     for my $stack (@potentially_problematic_method_stacks) {
485
486       # If we got so far - we need to see what the class would look
487       # like under c3 and compare, sigh
488       #
489       # Note that if the hierarchy is *really* fucked (like the above
490       # double-base e.g.) then recalc under 'c3' WILL FAIL, hence the
491       # extra eval: if we fail we report things as "jumbled up"
492       #
493       $affected_methods->{$stack->[0]{name}} = [
494         map { $_->{via_class} } @$stack
495       ] unless dbic_internal_try {
496
497         serialize($stack)
498           eq
499         serialize(
500           describe_class_methods({ class => $class, use_mro => 'c3' })
501                                ->{methods}
502                                 ->{$stack->[0]{name}}
503         )
504       };
505     }
506
507     push @err, {
508       class => $class,
509       isa => $desc->{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>.