Bring back constants excised in 08a8d8f1. FML.
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / _Util.pm
1 package # hide from PAUSE
2   DBIx::Class::_Util;
3
4 use warnings;
5 use strict;
6
7 use constant SPURIOUS_VERSION_CHECK_WARNINGS => ( "$]" < 5.010 ? 1 : 0);
8
9 BEGIN {
10   package # hide from pause
11     DBIx::Class::_ENV_;
12
13   use Config;
14
15   use constant {
16
17     # but of course
18     BROKEN_FORK => ($^O eq 'MSWin32') ? 1 : 0,
19
20     BROKEN_GOTO => ( "$]" < 5.008003 ) ? 1 : 0,
21
22     HAS_ITHREADS => $Config{useithreads} ? 1 : 0,
23
24     UNSTABLE_DOLLARAT => ( "$]" < 5.013002 ) ? 1 : 0,
25
26     ( map
27       #
28       # the "DBIC_" prefix below is crucial - this is what makes CI pick up
29       # all envvars without further adjusting its scripts
30       # DO NOT CHANGE to the more logical { $_ => !!( $ENV{"DBIC_$_"} ) }
31       #
32       { substr($_, 5) => !!( $ENV{$_} ) }
33       qw(
34         DBIC_SHUFFLE_UNORDERED_RESULTSETS
35         DBIC_ASSERT_NO_INTERNAL_WANTARRAY
36         DBIC_ASSERT_NO_INTERNAL_INDIRECT_CALLS
37         DBIC_STRESSTEST_UTF8_UPGRADE_GENERATED_COLLAPSER_SOURCE
38         DBIC_STRESSTEST_COLUMN_INFO_UNAWARE_STORAGE
39       )
40     ),
41
42     IV_SIZE => $Config{ivsize},
43
44     OS_NAME => $^O,
45   };
46
47   if ( "$]" < 5.009_005) {
48     require MRO::Compat;
49     constant->import( OLD_MRO => 1 );
50   }
51   else {
52     require mro;
53     constant->import( OLD_MRO => 0 );
54   }
55
56   # Both of these are no longer used for anything. However bring
57   # them back after they were purged in 08a8d8f1, as there appear
58   # to be outfits with *COPY PASTED* pieces of lib/DBIx/Class/Storage/*
59   # in their production codebases. There is no point in breaking these
60   # if whatever they used actually continues to work
61   my $warned;
62   my $sigh = sub {
63
64     require Carp;
65     my $cluck = "The @{[ (caller(1))[3] ]} constant is no more - adjust your code" . Carp::longmess();
66
67     warn $cluck unless $warned->{$cluck}++;
68
69     0;
70   };
71   sub DBICTEST () { &$sigh }
72   sub PEEPEENESS () { &$sigh }
73 }
74
75 # FIXME - this is not supposed to be here
76 # Carp::Skip to the rescue soon
77 use DBIx::Class::Carp '^DBIx::Class|^DBICTest';
78
79 use B ();
80 use Carp 'croak';
81 use Storable 'nfreeze';
82 use Scalar::Util qw(weaken blessed reftype refaddr);
83 use List::Util qw(first);
84 use Sub::Quote qw(qsub quote_sub);
85
86 # Already correctly prototyped: perlbrew exec perl -MStorable -e 'warn prototype \&Storable::dclone'
87 BEGIN { *deep_clone = \&Storable::dclone }
88
89 use base 'Exporter';
90 our @EXPORT_OK = qw(
91   sigwarn_silencer modver_gt_or_eq modver_gt_or_eq_and_lt
92   fail_on_internal_wantarray fail_on_internal_call
93   refdesc refcount hrefaddr
94   scope_guard detected_reinvoked_destructor
95   is_exception dbic_internal_try
96   quote_sub qsub perlstring serialize deep_clone dump_value
97   parent_dir mkdir_p
98   UNRESOLVABLE_CONDITION
99 );
100
101 use constant UNRESOLVABLE_CONDITION => \ '1 = 0';
102
103 sub sigwarn_silencer ($) {
104   my $pattern = shift;
105
106   croak "Expecting a regexp" if ref $pattern ne 'Regexp';
107
108   my $orig_sig_warn = $SIG{__WARN__} || sub { CORE::warn(@_) };
109
110   return sub { &$orig_sig_warn unless $_[0] =~ $pattern };
111 }
112
113 sub perlstring ($) { q{"}. quotemeta( shift ). q{"} };
114
115 sub hrefaddr ($) { sprintf '0x%x', &refaddr||0 }
116
117 sub refdesc ($) {
118   croak "Expecting a reference" if ! length ref $_[0];
119
120   # be careful not to trigger stringification,
121   # reuse @_ as a scratch-pad
122   sprintf '%s%s(0x%x)',
123     ( defined( $_[1] = blessed $_[0]) ? "$_[1]=" : '' ),
124     reftype $_[0],
125     refaddr($_[0]),
126   ;
127 }
128
129 sub refcount ($) {
130   croak "Expecting a reference" if ! length ref $_[0];
131
132   # No tempvars - must operate on $_[0], otherwise the pad
133   # will count as an extra ref
134   B::svref_2object($_[0])->REFCNT;
135 }
136
137 sub serialize ($) {
138   local $Storable::canonical = 1;
139   nfreeze($_[0]);
140 }
141
142 my $dd_obj;
143 sub dump_value ($) {
144   local $Data::Dumper::Indent = 1
145     unless defined $Data::Dumper::Indent;
146
147   my $dump_str = (
148     $dd_obj
149       ||=
150     do {
151       require Data::Dumper;
152       my $d = Data::Dumper->new([])
153         ->Purity(0)
154         ->Pad('')
155         ->Useqq(1)
156         ->Terse(1)
157         ->Freezer('')
158         ->Quotekeys(0)
159         ->Bless('bless')
160         ->Pair(' => ')
161         ->Sortkeys(1)
162         ->Deparse(1)
163       ;
164
165       $d->Sparseseen(1) if modver_gt_or_eq (
166         'Data::Dumper', '2.136'
167       );
168
169       $d;
170     }
171   )->Values([$_[0]])->Dump;
172
173   $dd_obj->Reset->Values([]);
174
175   $dump_str;
176 }
177
178 sub scope_guard (&) {
179   croak 'Calling scope_guard() in void context makes no sense'
180     if ! defined wantarray;
181
182   # no direct blessing of coderefs - DESTROY is buggy on those
183   bless [ $_[0] ], 'DBIx::Class::_Util::ScopeGuard';
184 }
185 {
186   package #
187     DBIx::Class::_Util::ScopeGuard;
188
189   sub DESTROY {
190     &DBIx::Class::_Util::detected_reinvoked_destructor;
191
192     local $@ if DBIx::Class::_ENV_::UNSTABLE_DOLLARAT;
193
194     eval {
195       $_[0]->[0]->();
196       1;
197     }
198       or
199     Carp::cluck(
200       "Execution of scope guard $_[0] resulted in the non-trappable exception:\n\n$@"
201     );
202   }
203 }
204
205
206 sub is_exception ($) {
207   my $e = $_[0];
208
209   # FIXME
210   # this is not strictly correct - an eval setting $@ to undef
211   # is *not* the same as an eval setting $@ to ''
212   # but for the sake of simplicity assume the following for
213   # the time being
214   return 0 unless defined $e;
215
216   my ($not_blank, $suberror);
217   {
218     local $SIG{__DIE__} if $SIG{__DIE__};
219     local $@;
220     eval {
221       # The ne() here is deliberate - a plain length($e), or worse "$e" ne
222       # will entirely obviate the need for the encolsing eval{}, as the
223       # condition we guard against is a missing fallback overload
224       $not_blank = ( $e ne '' );
225       1;
226     } or $suberror = $@;
227   }
228
229   if (defined $suberror) {
230     if (length (my $class = blessed($e) )) {
231       carp_unique( sprintf(
232         'External exception class %s implements partial (broken) overloading '
233       . 'preventing its instances from being used in simple ($x eq $y) '
234       . 'comparisons. Given Perl\'s "globally cooperative" exception '
235       . 'handling this type of brokenness is extremely dangerous on '
236       . 'exception objects, as it may (and often does) result in silent '
237       . '"exception substitution". DBIx::Class tries to work around this '
238       . 'as much as possible, but other parts of your software stack may '
239       . 'not be even aware of this. Please submit a bugreport against the '
240       . 'distribution containing %s and in the meantime apply a fix similar '
241       . 'to the one shown at %s, in order to ensure your exception handling '
242       . 'is saner application-wide. What follows is the actual error text '
243       . "as generated by Perl itself:\n\n%s\n ",
244         $class,
245         $class,
246         'http://v.gd/DBIC_overload_tempfix/',
247         $suberror,
248       ));
249
250       # workaround, keeps spice flowing
251       $not_blank = !!( length $e );
252     }
253     else {
254       # not blessed yet failed the 'ne'... this makes 0 sense...
255       # just throw further
256       die $suberror
257     }
258   }
259   elsif (
260     # a ref evaluating to '' is definitively a "null object"
261     ( not $not_blank )
262       and
263     length( my $class = ref $e )
264   ) {
265     carp_unique( sprintf(
266       "Objects of external exception class '%s' stringify to '' (the "
267     . 'empty string), implementing the so called null-object-pattern. '
268     . 'Given Perl\'s "globally cooperative" exception handling using this '
269     . 'class of exceptions is extremely dangerous, as it may (and often '
270     . 'does) result in silent discarding of errors. DBIx::Class tries to '
271     . 'work around this as much as possible, but other parts of your '
272     . 'software stack may not be even aware of the problem. Please submit '
273     . 'a bugreport against the distribution containing %s',
274
275       ($class) x 2,
276     ));
277
278     $not_blank = 1;
279   }
280
281   return $not_blank;
282 }
283
284 {
285   my $callstack_state;
286
287   # Recreate the logic of try(), while reusing the catch()/finally() as-is
288   #
289   # FIXME: We need to move away from Try::Tiny entirely (way too heavy and
290   # yes, shows up ON TOP of profiles) but this is a batle for another maint
291   sub dbic_internal_try (&;@) {
292
293     my $try_cref = shift;
294     my $catch_cref = undef;  # apparently this is a thing... https://rt.perl.org/Public/Bug/Display.html?id=119311
295
296     for my $arg (@_) {
297
298       if( ref($arg) eq 'Try::Tiny::Catch' ) {
299
300         croak 'dbic_internal_try() may not be followed by multiple catch() blocks'
301           if $catch_cref;
302
303         $catch_cref = $$arg;
304       }
305       elsif ( ref($arg) eq 'Try::Tiny::Finally' ) {
306         croak 'dbic_internal_try() does not support finally{}';
307       }
308       else {
309         croak(
310           'dbic_internal_try() encountered an unexpected argument '
311         . "'@{[ defined $arg ? $arg : 'UNDEF' ]}' - perhaps "
312         . 'a missing semi-colon before or ' # trailing space important
313         );
314       }
315     }
316
317     my $wantarray = wantarray;
318     my $preexisting_exception = $@;
319
320     my @ret;
321     my $all_good = eval {
322       $@ = $preexisting_exception;
323
324       local $callstack_state->{in_internal_try} = 1
325         unless $callstack_state->{in_internal_try};
326
327       # always unset - someone may have snuck it in
328       local $SIG{__DIE__} if $SIG{__DIE__};
329
330       if( $wantarray ) {
331         @ret = $try_cref->();
332       }
333       elsif( defined $wantarray ) {
334         $ret[0] = $try_cref->();
335       }
336       else {
337         $try_cref->();
338       }
339
340       1;
341     };
342
343     my $exception = $@;
344     $@ = $preexisting_exception;
345
346     if ( $all_good ) {
347       return $wantarray ? @ret : $ret[0]
348     }
349     elsif ( $catch_cref ) {
350       for ( $exception ) {
351         return $catch_cref->($exception);
352       }
353     }
354
355     return;
356   }
357
358   sub in_internal_try { !! $callstack_state->{in_internal_try} }
359 }
360
361 {
362   my $destruction_registry = {};
363
364   sub CLONE {
365     $destruction_registry = { map
366       { defined $_ ? ( refaddr($_) => $_ ) : () }
367       values %$destruction_registry
368     };
369
370     # Dummy NEXTSTATE ensuring the all temporaries on the stack are garbage
371     # collected before leaving this scope. Depending on the code above, this
372     # may very well be just a preventive measure guarding future modifications
373     undef;
374   }
375
376   # This is almost invariably invoked from within DESTROY
377   # throwing exceptions won't work
378   sub detected_reinvoked_destructor {
379
380     # quick "garbage collection" pass - prevents the registry
381     # from slowly growing with a bunch of undef-valued keys
382     defined $destruction_registry->{$_} or delete $destruction_registry->{$_}
383       for keys %$destruction_registry;
384
385     if (! length ref $_[0]) {
386       printf STDERR '%s() expects a blessed reference %s',
387         (caller(0))[3],
388         Carp::longmess,
389       ;
390       return undef; # don't know wtf to do
391     }
392     elsif (! defined $destruction_registry->{ my $addr = refaddr($_[0]) } ) {
393       weaken( $destruction_registry->{$addr} = $_[0] );
394       return 0;
395     }
396     else {
397       carp_unique ( sprintf (
398         'Preventing *MULTIPLE* DESTROY() invocations on %s - an *EXTREMELY '
399       . 'DANGEROUS* condition which is *ALMOST CERTAINLY GLOBAL* within your '
400       . 'application, affecting *ALL* classes without active protection against '
401       . 'this. Diagnose and fix the root cause ASAP!!!%s',
402       refdesc $_[0],
403         ( ( $INC{'Devel/StackTrace.pm'} and ! do { local $@; eval { Devel::StackTrace->VERSION(2) } } )
404           ? " (likely culprit Devel::StackTrace\@@{[ Devel::StackTrace->VERSION ]} found in %INC, http://is.gd/D_ST_refcap)"
405           : ''
406         )
407       ));
408
409       return 1;
410     }
411   }
412 }
413
414 my $module_name_rx = qr/ \A [A-Z_a-z] [0-9A-Z_a-z]* (?: :: [0-9A-Z_a-z]+ )* \z /x;
415 my $ver_rx =         qr/ \A [0-9]+ (?: \. [0-9]+ )* (?: \_ [0-9]+ )*        \z /x;
416
417 sub modver_gt_or_eq ($$) {
418   my ($mod, $ver) = @_;
419
420   croak "Nonsensical module name supplied"
421     if ! defined $mod or $mod !~ $module_name_rx;
422
423   croak "Nonsensical minimum version supplied"
424     if ! defined $ver or $ver !~ $ver_rx;
425
426   no strict 'refs';
427   my $ver_cache = ${"${mod}::__DBIC_MODULE_VERSION_CHECKS__"} ||= ( $mod->VERSION
428     ? {}
429     : croak "$mod does not seem to provide a version (perhaps it never loaded)"
430   );
431
432   ! defined $ver_cache->{$ver}
433     and
434   $ver_cache->{$ver} = do {
435
436     local $SIG{__WARN__} = sigwarn_silencer( qr/\Qisn't numeric in subroutine entry/ )
437       if SPURIOUS_VERSION_CHECK_WARNINGS;
438
439     local $SIG{__DIE__} if $SIG{__DIE__};
440     local $@;
441     eval { $mod->VERSION($ver) } ? 1 : 0;
442   };
443
444   $ver_cache->{$ver};
445 }
446
447 sub modver_gt_or_eq_and_lt ($$$) {
448   my ($mod, $v_ge, $v_lt) = @_;
449
450   croak "Nonsensical maximum version supplied"
451     if ! defined $v_lt or $v_lt !~ $ver_rx;
452
453   return (
454     modver_gt_or_eq($mod, $v_ge)
455       and
456     ! modver_gt_or_eq($mod, $v_lt)
457   ) ? 1 : 0;
458 }
459
460
461 #
462 # Why not just use some higher-level module or at least File::Spec here?
463 # Because:
464 # 1)  This is a *very* rarely used function, and the deptree is large
465 #     enough already as it is
466 #
467 # 2)  (more importantly) Our tooling is utter shit in this area. There
468 #     is no comprehensive support for UNC paths in PathTools and there
469 #     are also various small bugs in representation across different
470 #     path-manipulation CPAN offerings.
471 #
472 # Since this routine is strictly used for logical path processing (it
473 # *must* be able to work with not-yet-existing paths), use this seemingly
474 # simple but I *think* complete implementation to feed to other consumers
475 #
476 # If bugs are ever uncovered in this routine, *YOU ARE URGED TO RESIST*
477 # the impulse to bring in an external dependency. During runtime there
478 # is exactly one spot that could potentially maybe once in a blue moon
479 # use this function. Keep it lean.
480 #
481 sub parent_dir ($) {
482   ( $_[0] =~ m{  [\/\\]  ( \.{0,2} ) ( [\/\\]* ) \z }x )
483     ? (
484       $_[0]
485         .
486       ( ( length($1) and ! length($2) ) ? '/' : '' )
487         .
488       '../'
489     )
490     : (
491       require File::Spec
492         and
493       File::Spec->catpath (
494         ( File::Spec->splitpath( "$_[0]" ) )[0,1],
495         '/',
496       )
497     )
498   ;
499 }
500
501 sub mkdir_p ($) {
502   require File::Path;
503   # do not ask for a recent version, use 1.x API calls
504   File::Path::mkpath([ "$_[0]" ]);  # File::Path does not like objects
505 }
506
507
508 {
509   my $list_ctx_ok_stack_marker;
510
511   sub fail_on_internal_wantarray () {
512     return if $list_ctx_ok_stack_marker;
513
514     if (! defined wantarray) {
515       croak('fail_on_internal_wantarray() needs a tempvar to save the stack marker guard');
516     }
517
518     my $cf = 1;
519     while ( ( (CORE::caller($cf+1))[3] || '' ) =~ / :: (?:
520
521       # these are public API parts that alter behavior on wantarray
522       search | search_related | slice | search_literal
523
524         |
525
526       # these are explicitly prefixed, since we only recognize them as valid
527       # escapes when they come from the guts of CDBICompat
528       CDBICompat .*? :: (?: search_where | retrieve_from_sql | retrieve_all )
529
530     ) $/x ) {
531       $cf++;
532     }
533
534     my ($fr, $want, $argdesc);
535     {
536       package DB;
537       $fr = [ CORE::caller($cf) ];
538       $want = ( CORE::caller($cf-1) )[5];
539       $argdesc = ref $DB::args[0]
540         ? DBIx::Class::_Util::refdesc($DB::args[0])
541         : 'non '
542       ;
543     };
544
545     if (
546       $want and $fr->[0] =~ /^(?:DBIx::Class|DBICx::)/
547     ) {
548       DBIx::Class::Exception->throw( sprintf (
549         "Improper use of %s instance in list context at %s line %d\n\n    Stacktrace starts",
550         $argdesc, @{$fr}[1,2]
551       ), 'with_stacktrace');
552     }
553
554     my $mark = [];
555     weaken ( $list_ctx_ok_stack_marker = $mark );
556     $mark;
557   }
558 }
559
560 sub fail_on_internal_call {
561   my ($fr, $argdesc);
562   {
563     package DB;
564     $fr = [ CORE::caller(1) ];
565     $argdesc = ref $DB::args[0]
566       ? DBIx::Class::_Util::refdesc($DB::args[0])
567       : undef
568     ;
569   };
570
571   if (
572     $argdesc
573       and
574     $fr->[0] =~ /^(?:DBIx::Class|DBICx::)/
575       and
576     $fr->[1] !~ /\b(?:CDBICompat|ResultSetProxy)\b/  # no point touching there
577   ) {
578     DBIx::Class::Exception->throw( sprintf (
579       "Illegal internal call of indirect proxy-method %s() with argument %s: examine the last lines of the proxy method deparse below to determine what to call directly instead at %s on line %d\n\n%s\n\n    Stacktrace starts",
580       $fr->[3], $argdesc, @{$fr}[1,2], ( $fr->[6] || do {
581         require B::Deparse;
582         no strict 'refs';
583         B::Deparse->new->coderef2text(\&{$fr->[3]})
584       }),
585     ), 'with_stacktrace');
586   }
587 }
588
589 1;