File Coverage

blib/lib/BerkeleyDB/Easy/Common.pm
Criterion Covered Total %
statement 93 174 53.4
branch 4 28 14.2
condition 3 64 4.6
subroutine 15 24 62.5
pod n/a
total 115 290 39.6


line stmt bran cond sub pod time code
1             package BerkeleyDB::Easy::Common;
2            
3 1     1   20156 use strict;
  1         2  
  1         37  
4 1     1   5 use warnings;
  1         2  
  1         28  
5 1     1   4 no warnings 'uninitialized';
  1         2  
  1         27  
6            
7 1     1   5 use Exporter ();
  1         2  
  1         29  
8 1     1   4 use Scalar::Util ();
  1         2  
  1         37  
9            
10             # The following are "switchboard" methods for class dispatching.
11             # The idea is that sometimes classes need to call each other laterally
12             # and not through the inheritance chain. For example, Handle::cursor()
13             # creates a new cursor, so it finds the right class via $self->_Cursor.
14             # That way, if you wanted to extend that class with your own, you
15             # could just override Common::_Cursor here instead of tracking down
16             # and overriding all the various call sites.
17            
18             use constant {
19 1         122 _Base => 'BerkeleyDB::Easy',
20             _Handle => 'BerkeleyDB::Easy::Handle',
21             _Cursor => 'BerkeleyDB::Easy::Cursor',
22             _Error => 'BerkeleyDB::Easy::Error',
23             _Common => 'BerkeleyDB::Easy::Common',
24 1     1   5 };
  1         2  
25            
26 1     1   5 sub _unstrict { no strict 'refs'; no warnings 'once'; ${+shift} }
  1     1   2  
  1     8   35  
  1         8  
  1         2  
  1         550  
  8         9  
  8         60  
27            
28             our (@ISA, @EXPORT, %EXPORT_TAGS, %Levels);
29            
30             #
31             # Set up constant functions and exports used by the other packages
32             # TODO: Process compile/construction-time options;
33             # integrate with handle constructor
34             #
35             BEGIN {
36 1     1   30 @ISA = qw(Exporter);
37 1         2 @EXPORT = ();
38 1         7 %EXPORT_TAGS = (
39             subs => [qw(_generate _accessor _compile _install _wrap _try
40             _lines _log)],
41             class => [], # Class dispatching ex: _Base, _Btree
42             flag => [], # Error level flags, etc. ex: BDB_TRACE, BDB_IGNORE
43             spec => [], # Specification constants ex: FUNC, RECV, K, V, F
44             bool => [], # Compilation guard bools ex: TRACE, INFO, NOTICE
45             );
46            
47             # Class dispatching (export under :class) ---------------------------
48            
49 1         4 my @classes = qw(
50             Handle Cursor Error Common
51             Btree Hash Queue Recno Heap Unknown
52             );
53            
54 1         1 my $base = q(BerkeleyDB::Easy);
55 1         46 constant->import(_Base => $base);
56 1         2 push @{$EXPORT_TAGS{class}}, q(_Base);
  1         3  
57            
58 1         3 for my $name (@classes) {
59 10         124 my $const = qq(_$name);
60 10         19 my $class = qq($base\::$name);
61 10         272 constant->import($const => $class);
62 10         11 push @{$EXPORT_TAGS{class}}, $const;
  10         33  
63             }
64            
65             # Error severity / log levels ---------------------------------------
66            
67 1         4 my @levels = (
68             'IGNORE', # 0
69             'FATAL', # 1
70             'ERROR', # 2
71             'WARN', # 3
72             'NOTICE', # 4
73             'INFO', # 5
74             'DEBUG', # 6
75             'TRACE', # 7
76             );
77            
78 1         2 my $log_level = 0;
79 1         3 for my $level (reverse 0 .. $#levels) {
80 8         12 my $level_name = $levels[$level];
81            
82             # User flags, ie: BDB_DEBUG (export under :flag)
83 8         17 my $flag_name = qq(BDB_$level_name);
84 8         32 my $flag_dual = Scalar::Util::dualvar($level, $flag_name);
85 8         202 constant->import($flag_name, $flag_dual);
86 8         19 $Levels{$flag_dual} = $flag_dual;
87 8         11 push @{$EXPORT_TAGS{flag}}, $flag_name;
  8         17  
88            
89             # We don't need guards or handlers for IGNORE
90 8 100       22 next if $level == 0;
91            
92             # Guard booleans, ie: DEBUG (export under :bool)
93 7 50 0     38 $log_level ||= $flag_dual if $ENV{$flag_name}
      33        
94             or _unstrict(_Base . q(::) . ucfirst lc $level_name);
95 7         173 constant->import($level_name, $level <= $log_level);
96 7         10 push @{$EXPORT_TAGS{bool}}, $level_name;
  7         19  
97            
98             # Handler aliases, ie: _debug (export under :sub)
99 7         14 my $handler_name = q(_) . lc $level_name;
100             my $handler_sub = sub {
101 0     0   0 my $self = shift;
102 0         0 unshift @_, $flag_dual;
103 0         0 $self->_log(@_);
104 7         33 };
105 1     1   6 no strict 'refs';
  1         2  
  1         405  
106 7         51 *$handler_name = $handler_sub;
107 7         16 push @{$EXPORT_TAGS{sub}}, $handler_name;
  7         36  
108             }
109            
110             # BDB_LEVEL (export under :flag)
111 1   33     30 constant->import(BDB_LEVEL => $log_level || BDB_IGNORE());
112 1         2 push @{$EXPORT_TAGS{flag}}, q(BDB_LEVEL);
  1         4  
113            
114             # BDB_VERBOSE (export under :flag)
115 1   33     15 my $verbose = $ENV{BDB_VERBOSE} || _unstrict(_Base . q(::Verbose));
116 1 50       6 $verbose = $log_level >= BDB_DEBUG() if not defined $verbose;
117 1         33 constant->import(BDB_VERBOSE => !!$verbose);
118 1         2 push @{$EXPORT_TAGS{flag}}, q(BDB_VERBOSE);
  1         3  
119            
120             # Subroutine generator specification (export under :spec) -----------
121            
122 1         19 my %spec = (
123             K => q($key), FUNC => 0,
124             V => q($value), RECV => 1,
125             F => q($flags), SEND => 2,
126             S => q($status), SUCC => 3,
127             R => q($return), FAIL => 4,
128             A => q(@_), OPTI => 5,
129             X => q($x), FLAG => 6,
130             Y => q($y),
131             Z => q($z),
132             T => q(1),
133             N => q(''),
134             U => q(undef),
135             );
136 1         6 while (my ($key, $val) = each %spec) {
137 19         416 constant->import($key, $val);
138 19         28 push @{$EXPORT_TAGS{spec}}, $key;
  19         109  
139             }
140            
141             # Export all tag groups by default
142 1         4 push @EXPORT, map @{$EXPORT_TAGS{$_}}, keys %EXPORT_TAGS;
  6         298  
143             }
144            
145             #
146             # Install a stub closure into the calling package. When called for the
147             # first time, it will compile and magic goto itself. If we get passed a
148             # specification, generate a BerkeleyDB.pm wrapper function. Otherwise, make
149             # a simple object accessor.
150             #
151             sub _install {
152 11     11   23 my ($self, $name, $spec) = @_;
153 11         43 my ($pack, $file, $line) = (caller)[0..2];
154            
155 11         14 DEBUG and $self->_debug(qq(Installing method stub: $name));
156            
157             my $stub = sub {
158 0 0   0   0 my $code = $spec
159             ? $self->_generate($spec, $name, $pack)
160             : $self->_accessor($name);
161            
162 0         0 TRACE and $self->_trace(qq(Generated code: $code));
163 0         0 $self->_compile($code, $name, $pack);
164            
165 0         0 goto &{"$pack\::$name"};
  0         0  
166 11         48 };
167            
168 1     1   7 no strict 'refs';
  1         3  
  1         276  
169 11         15 *{"$pack\::$name"} = $stub;
  11         83  
170             }
171            
172             #
173             # Expand function specification into code via a dynamic template.
174             # (Internal method, used by _install)
175             #
176             sub _generate {
177 0     0     my ($self, $spec, $name, $pack) = @_;
178            
179             # Optimization level. The higher this is, the less we do.
180 0   0       my $opt = $spec->[OPTI] || 0;
181            
182             # The parameters to our function and the vars we will unroll @_ into.
183             # Generally some combination of K ($key), V ($value), and F ($flags).
184 0           my $recv = join q(, ), q($self), @{$spec->[RECV]};
  0            
185            
186             # Need to declare any other variables we're going to need that didn't
187             # get declared when we unrolled @_.
188 0           my $decl = do {
189 0           my %r = map { $_ => 1 } @{$spec->[RECV]};
  0            
  0            
190 0 0         join q(, ), grep { $_ ne A and !$r{$_} } @{$spec->[SEND]};
  0            
  0            
191             };
192            
193             # What BerkeleyDB.pm class are we wrapping?
194             # Either ::Common (for all handle types) or ::Cursor.
195 1     1   6 my $isa = do { no strict 'refs'; ${qq($pack\::ISA)}[0] };
  1         1  
  1         1087  
  0            
  0            
  0            
196            
197             # Does the function return something we need to keep? (db_cursor)
198             # Yes (R): keep it and get $status from SUPER::status.
199             # No (S): return value is $status.
200 0 0         my $keep = ( grep { $_ eq R } @{$spec->[SUCC]} ) ? R : S;
  0            
  0            
201            
202             # What function are we wrapping?
203 0           my $func = $spec->[FUNC];
204            
205             # Does it require a default flag?
206 0           my $flag = $spec->[FLAG];
207            
208             # Arguments that we send to the function. If the function has a default
209             # flag, we need to OR it together with any flags provided by the user.
210 0 0         my $send = join q(, ), $flag
211 0           ? map { $_ eq F ? qq($flag | ${\F}) : $_ } @{$spec->[SEND]}
  0            
  0            
212 0 0         : @{$spec->[SEND]};
213            
214             # What to return on failure ($status is set) or success.
215 0           my $fail = join q(, ), @{$spec->[FAIL]};
  0            
216 0           my $succ = join q(, ), @{$spec->[SUCC]};
  0            
217            
218             # Use specification to generate code from the following template.
219             # Right now, the only use of $opt is to determine if we localize
220             # error variables and signal handlers, which is expensive.
221             # Various other logic is done is to create the trimmest possible
222             # wrapper depending on the needs of the function.
223            
224             # $opt = 1;
225 0           my ($D, $W) = (BDB_FATAL, BDB_WARN);
226            
227 0   0       $self->_lines(
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
228             ( qq|sub $name { |),
229             (!$opt && qq| my \@err; |),
230             (!$opt && qq| local (\$!, \$^E); |),
231             (!$opt && qq| local \$SIG{__DIE__} = |),
232             (!$opt && qq| sub { \@err = ($D, \$_) }; |),
233             (!$opt && qq| local \$SIG{__WARN__} = |),
234             (!$opt && qq| sub { \@err = ($W, \$_) }; |),
235             ( $opt <= 1 && qq| undef \$BerkeleyDB::Error; |),
236             ( qq| my ($recv) = \@_; |),
237             ($decl && qq| my ($decl); |),
238             (TRACE && qq| \$self->_trace('$name', \@_); |),
239             ($send ne A && qq| my $keep = $isa\::$func(\$self, $send); |),
240             ($send eq A && qq| my $keep = &$isa\::$func; |),
241             ($keep eq R && qq| my ${\S} = $isa\::status(\$self); |),
242             (!$opt && qq| \$self->_log(\@err) if \@err; |),
243 0           ( qq| if (${\S}) { |),
244             (!$opt && qq| \$self->_throw(${\S}); |),
245             ( $opt && qq| \$self->_throw(${\S}, undef, $opt); |),
246             ($fail ne $succ && qq| return($fail); |),
247             ( qq| } |),
248             ( qq| return($succ); |),
249             ( qq|} |),
250             );
251             }
252            
253             #
254             # Make a getter-setter for managing our own state.
255             # (Internal method, used by _install)
256             #
257             sub _accessor {
258 0     0     my ($self, $name) = @_;
259            
260 0           $self->_lines(
261             (qq|sub $name { |),
262             (qq| my \$self = shift; |),
263             (qq| if (\@_) { |),
264             (qq| \$self->{$name} = shift; |),
265             (qq| return(\$self); |),
266             (qq| } |),
267             (qq| return(\$self->{$name}); |),
268             (qq|} |),
269             );
270             }
271            
272             #
273             # Prior to compilation, interleave code with line directives so that
274             # stack traces will be still be somewhat useful. They'll point to the
275             # file and line number of our caller, the site of the template definition.
276             # (Internal method, used by _generate and _accessor)
277             #
278             sub _lines {
279 0     0     my $self = shift;
280 0           my ($file, $line) = (caller)[1..2];
281            
282 0           join qq(# line $line $file(EVAL)\n),
283 0           map { (my $ln = $_) =~ s/\s*$/\n/; $ln }
  0            
284             grep $_, @_;
285             }
286            
287             #
288             # Compile some code into the requested package (or caller).
289             # (Internal method, used by _install)
290             #
291             sub _compile {
292 0     0     my ($self, $code, $name, $pack) = @_;
293 0   0       $name ||= q(__ANON__);
294 0   0       $pack ||= caller;
295            
296 0           INFO and $self->_info(qq(Compiling method: $name));
297            
298 0           my ($sub, $err);
299             {
300 0           local $@;
  0            
301 1     1   7 no warnings 'redefine';
  1         7  
  1         428  
302 0           $sub = eval(qq(package $pack; $code));
303 0           ($err = $@) =~ s/, at EOF\n$//;
304             };
305            
306 0 0         $self->_fatal(qq(Error compiling method "$name": $err)) if $err;
307            
308 0           $sub;
309             }
310            
311             #
312             # BerkeleyDB.pm doesn't throw many exceptions -- only during initialization,
313             # really -- but whenever it might, we wrap it and localize its global error
314             # variable as well as the operating system's, so we can return everything
315             # back to the user in a pristine state.
316             #
317             sub _wrap {
318 0     0     my ($self, $func) = (shift, shift);
319 0           local ($BerkeleyDB::Error, $!, $^E);
320 0           $func->(@_);
321             }
322            
323             #
324             # An even tinier Try::Tiny because lol no dependencies.
325             #
326             sub _try {
327 0     0     my ($self, $try, $catch) = @_;
328 0           my ($ok, $ret, $err);
329            
330 0           my $prev = $@;
331             {
332 0           local $@;
  0            
333 0           $ok = eval {
334 0           local $@ = $prev;
335 0           $ret = $try->();
336 0           1;
337             };
338 0           $err = $@;
339             };
340            
341 0 0 0       if ($catch and not $ok) {
342 0           local $_ = $err;
343 0           $catch->();
344             }
345            
346 0           $ret;
347             }
348            
349             #
350             # Log a message and then warn or die depending on the severity level.
351             # Just prints to STDOUT for now.
352             # Used by _throw, and thinly wrapped by:
353             # _trace _debug _info _notice _warn _error _fatal
354             #
355             sub _log {
356 0     0     my ($self, $level, @args) = @_;
357            
358 0 0 0       if (my $exc = ref $args[0] && $args[0]) {
359             # TODO: Unpack error object.
360             }
361            
362 0 0         my $msg = @args ? join q(, ), @args : '';
363 0           print STDERR qq(<< $level : $msg >>\n);
364            
365 0 0         if ($level <= BDB_ERROR) {
    0          
366 0           die @args;
367             }
368             elsif ($level == BDB_WARN) {
369 0           warn @args;
370             }
371             }
372            
373             INFO and __PACKAGE__->_info(q(Common.pm finished loading));
374            
375             1;