Expand annotations to cover all generated methods
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Schema / SanityChecker.pm
CommitLineData
12e7015a 1package DBIx::Class::Schema::SanityChecker;
2
3use strict;
4use warnings;
5
6use DBIx::Class::_Util qw(
7 dbic_internal_try refdesc uniq serialize
8 describe_class_methods emit_loud_diag
9);
10use DBIx::Class ();
11use DBIx::Class::Exception ();
12use Scalar::Util qw( blessed refaddr );
13use namespace::clean;
14
15=head1 NAME
16
17DBIx::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 on Perl v5.10 and later
25 __PACKAGE__->schema_sanity_checker('DBIx::Class::Schema::SanityChecker');
26 ...
27
28=head1 DESCRIPTION
29
30This is the default implementation of the Schema and related classes
31L<validation framework|DBIx::Class::Schema/schema_sanity_checker>.
32
33The validator is B<enabled by default> on perls C<v5.10> and above. See
34L</Performance considerations> for discussion of the runtime effects.
35
36Use of this class begins by invoking L</perform_schema_sanity_checks>
37(usually via L<DBIx::Class::Schema/connection>), which in turn starts
38invoking validators I<C<check_$checkname()>> in the order listed in
39L</available_checks>. For each set of returned errors (if any)
40I<C<format_$checkname_errors()>> is called and the resulting strings are
41passed to L</emit_errors>, where final headers are prepended and the entire
42thing is printed on C<STDERR>.
43
44The class does not provide a constructor, due to the lack of state to be
45passed around: object orientation was chosen purely for the ease of
46overriding parts of the chain of events as described above. The general
47pattern of communicating errors between the individual methods (both
48before and after formatting) is an arrayref of hash references.
49
50=head2 WHY
51
52DBIC existed for more than a decade without any such setup validation
53fanciness, let alone something that is enabled by default (which in turn
54L<isn't free|/Performance considerations>). The reason for this relatively
55drastic change is a set of revamps within the metadata handling framework,
56in order to resolve once and for all problems like
57L<RT#107462|https://rt.cpan.org/Ticket/Display.html?id=107462>,
58L<RT#114440|https://rt.cpan.org/Ticket/Display.html?id=114440>, etc. While
59DBIC internals are now way more robust than they were before, this comes at
60a price: some non-issues in code that has been working for a while, will
61now become hard to explain, or if you are unlucky: B<silent breakages>.
62
63Thus, in order to protect existing codebases to the fullest extent possible,
64the executive decision (and substantial effort) was made to introduce this
65on-by-default setup validation framework. A massive amount of work has been
66invested ensuring that none of the builtin checks emit a false-positive:
67each and every complaint made by these checks B<should be investigated>.
68
69=head2 Performance considerations
70
71First of all - after your connection has been established - there is B<no
72runtime penalty> whenever the checks are enabled.
73
74By default the checks are triggered every time
75L<DBIx::Class::Schema/connection> is called. Thus there is a
76noticeable startup slowdown, most notably during testing (each test is
77effectively a standalone program connecting anew). As an example the test
78execution phase of the L<DBIx::Class::Helpers> C<v2.032002> distribution
79suffers a consistent slowdown of about C<16%>. This is considered a relatively
80small price to pay for the benefits provided.
81
82Nevertheless, there are valid cases for disabling the checks during
83day-to-day development, and having them run only during CI builds. In fact
84the test suite of DBIC does exactly this as can be seen in
85F<t/lib/DBICTest/BaseSchema.pm>:
86
87 ~/dbic_repo$ git show 39636786 | perl -ne "print if 16..61"
88
89Whatever you do, B<please do not disable the checks entirely>: it is not
90worth the risk.
91
92=head3 Perl5.8
93
94The situation with perl interpreters before C<v5.10.0> is sadly more
95complicated: due to lack of built-in L<pluggable mro support|mro>, the
96mechanism used to interrogate various classes is
97L<< B<much> slower|https://github.com/dbsrgits/dbix-class/commit/296248c3 >>.
98As a result the very same version of L<DBIx::Class::Helpers>
99L<mentioned above|/Performance considerations> takes a C<B<220%>> hit on its
100test execution time (these numbers are observed with the speedups of
101L<Class::C3::XS> available, without them the slowdown reaches the whopping
102C<350%>).
103
104Therefore, on these versions of perl the sanity checks are B<not enabled> by
105default. Instead a C<false> placeholder value is inserted into the
106L<schema_sanity_checker attribute|DBIx::Class::Schema/schema_sanity_checker>,
107urging the user to decide for themselves how to proceed.
108
109It is the author's B<strongest> recommendation to find a way to run the
110checks on your codebase continuously, even if it takes much longer. Refer to
111the last paragraph of L</Performance considerations> above for an example how
112to do this during CI builds only.
113
114=head2 Validations provided by this module
115
116=head3 no_indirect_method_overrides
117
118There are many methods within DBIC which are
119L<"strictly sugar"|DBIx::Class::MethodAttributes/DBIC_method_is_indirect_sugar>
120and should never be overridden by your application (e.g. see warnings at the
121end of L<DBIx::Class::ResultSet/create> and L<DBIx::Class::Schema/connect>).
122Starting with C<v0.082900> DBIC is much more aggressive in calling the
123underlying non-sugar methods directly, which in turn means that almost all
124user-side overrides of sugar methods are never going to be invoked. These
125situations are now reliably detected and reported individually (you may
126end up with a lot of output on C<STDERR> due to this).
127
128Note: B<ANY AND ALL ISSUES> reported by this check B<*MUST*> be resolved
129before upgrading DBIC in production. Malfunctioning business logic and/or
130B<SEVERE DATA LOSS> may result otherwise.
131
132=head3 valid_c3_composition
133
134Looks through everything returned by L</all_schema_related_classes>, and
135for any class that B<does not> already utilize L<c3 MRO|mro/The C3 MRO> a
136L<method shadowing map|App::Isa::Splain/SYNOPSIS> is calculated and then
137compared to the shadowing map as if C<c3 MRO> was requested in the first place.
138Any discrepancies are reported in order to clearly identify L<hard to explain
139bugs|https://blog.afoolishmanifesto.com/posts/mros-and-you> especially when
140encountered within complex inheritance hierarchies.
141
142=head3 no_inheritance_crosscontamination
143
144Checks that every individual L<Schema|DBIx::Class::Schema>,
145L<Storage|DBIx::Class::Storage>, L<ResultSource|DBIx::Class::ResultSource>,
146L<ResultSet|DBIx::Class::ResultSet>
147and L<Result|DBIx::Class::Manual::ResultClass> class does not inherit from
148an unexpected DBIC base class: e.g. an error will be raised if your
149C<MyApp::Schema> inherits from both C<DBIx::Class::Schema> and
150C<DBIx::Class::ResultSet>.
151
152=head1 METHODS
153
154=head2 perform_schema_sanity_checks
155
156=over
157
158=item Arguments: L<$schema|DBIx::Class::Schema>
159
160=item Return Value: unspecified (ignored by caller)
161
162=back
163
164The entry point expected by the
165L<validation framework|DBIx::Class::Schema/schema_sanity_checker>. See
166L</DESCRIPTION> for details.
167
168=cut
169
170sub perform_schema_sanity_checks {
171 my ($self, $schema) = @_;
172
173 local $DBIx::Class::_Util::describe_class_query_cache->{'!internal!'} = {}
174 if
175 # does not make a measurable difference on 5.10+
176 DBIx::Class::_ENV_::OLD_MRO
177 and
178 # the callstack shouldn't really be recursive, but for completeness...
179 ! $DBIx::Class::_Util::describe_class_query_cache->{'!internal!'}
180 ;
181
182 my (@errors_found, $schema_desc);
183 for my $ch ( @{ $self->available_checks } ) {
184
185 my $err = $self->${\"check_$ch"} ( $schema );
186
187 push @errors_found, map
188 {
189 {
190 check_name => $ch,
191 formatted_error => $_,
192 schema_desc => ( $schema_desc ||=
193 ( length ref $schema )
194 ? refdesc $schema
195 : "'$schema'"
196 ),
197 }
198 }
199 @{
200 $self->${\"format_${ch}_errors"} ( $err )
201 ||
202 []
203 }
204 if @$err;
205 }
206
207 $self->emit_errors(\@errors_found)
208 if @errors_found;
209}
210
211=head2 available_checks
212
213=over
214
215=item Arguments: none
216
217=item Return Value: \@list_of_check_names
218
219=back
220
221The list of checks L</perform_schema_sanity_checks> will perform on the
222provided L<$schema|DBIx::Class::Schema> object. For every entry returned
223by this method, there must be a pair of I<C<check_$checkname()>> and
224I<C<format_$checkname_errors()>> methods available.
225
226Override this method to add checks to the
227L<currently available set|/Validations provided by this module>.
228
229=cut
230
231sub available_checks { [qw(
232 valid_c3_composition
233 no_inheritance_crosscontamination
234 no_indirect_method_overrides
235)] }
236
237=head2 emit_errors
238
239=over
240
241=item Arguments: \@list_of_formatted_errors
242
243=item Return Value: unspecified (ignored by caller)
244
245=back
246
247Takes an array reference of individual errors returned by various
248I<C<format_$checkname_errors()>> formatters, and outputs them on C<STDERR>.
249
250This method is the most convenient integration point for a 3rd party logging
251framework.
252
253Each individual error is expected to be a hash reference with all values being
254plain strings as follows:
255
256 {
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()
260 }
261
262If the environment variable C<DBIC_ASSERT_NO_FAILING_SANITY_CHECKS> is set to
263a true value this method will throw an exception with the same text. Those who
264prefer to take no chances could set this variable permanently as part of their
265deployment scripts.
266
267=cut
268
269# *NOT* using carp_unique and the warn framework - make
270# it harder to accidentaly silence problems via $SIG{__WARN__}
271sub emit_errors {
272 #my ($self, $errs) = @_;
273
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 )}
277 );
278 } @{$_[1]};
279
280 emit_loud_diag(
281 msg => $_
282 ) for @final_error_texts;
283
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};
288}
289
290=head2 all_schema_related_classes
291
292=over
293
294=item Arguments: L<$schema|DBIx::Class::Schema>
295
296=item Return Value: @sorted_list_of_unique_class_names
297
298=back
299
300This is a convenience method providing a list (not an arrayref) of
301"interesting classes" related to the supplied schema. The returned list
302currently contains the following class names:
303
304=over
305
306=item * The L<Schema|DBIx::Class::Schema> class itself
307
308=item * The associated L<Storage|DBIx::Class::Schema/storage> class if any
309
310=item * The classes of all L<registered ResultSource instances|DBIx::Class::Schema/sources> if any
311
312=item * All L<Result|DBIx::Class::ResultSource/result_class> classes for all registered ResultSource instances
313
314=item * All L<ResultSet|DBIx::Class::ResultSource/resultset_class> classes for all registered ResultSource instances
315
316=back
317
318=cut
319
320sub all_schema_related_classes {
321 my ($self, $schema) = @_;
322
323 sort( uniq( map {
324 ( not defined $_ ) ? ()
325 : ( defined blessed $_ ) ? ref $_
326 : $_
327 } (
328 $schema,
329 $schema->storage,
330 ( map {
331 $_,
332 $_->result_class,
333 $_->resultset_class,
334 } map { $schema->source($_) } $schema->sources ),
335 )));
336}
337
338
339sub format_no_indirect_method_overrides_errors {
340 # my ($self, $errors) = @_;
341
342 [ map { sprintf(
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},
351 )} @{ $_[1] } ]
352}
353
354sub check_no_indirect_method_overrides {
355 my ($self, $schema) = @_;
356
357 my( @err, $seen_shadowing_configurations );
358
359 METHOD_STACK:
360 for my $method_stack ( map {
361 values %{ describe_class_methods($_)->{methods_with_supers} || {} }
362 } $self->all_schema_related_classes($schema) ) {
363
364 my $nonsugar_methods;
365
366 for (@$method_stack) {
367
368 push @$nonsugar_methods, $_ and next
369 unless $_->{attributes}{DBIC_method_is_indirect_sugar};
370
371 push @err, {
372 overriden => {
373 name => $_->{name},
374 via_class => $_->{via_class}
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
400sub 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
415my $base_ISA = {
416 map { $_ => 1 } @{mro::get_linear_isa("DBIx::Class")}
417};
418
419sub 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
520sub 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
533sub 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
5771;
578
579__END__
580
581=head1 FURTHER QUESTIONS?
582
583Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
584
585=head1 COPYRIGHT AND LICENSE
586
587This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
588by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
589redistribute it and/or modify it under the same terms as the
590L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.