File Coverage

blib/lib/Memoize/Memcached.pm
Criterion Covered Total %
statement 36 141 25.5
branch 3 50 6.0
condition 2 7 28.5
subroutine 10 27 37.0
pod 2 2 100.0
total 53 227 23.3


line stmt bran cond sub pod time code
1             package Memoize::Memcached;
2              
3 2     2   213938 use strict;
  2         4  
  2         90  
4 2     2   13 use warnings;
  2         4  
  2         69  
5              
6 2     2   11 use Carp qw( carp croak );
  2         7  
  2         147  
7 2     2   2469 use Memoize qw( unmemoize );
  2         6186  
  2         180  
8 2     2   1477 use Cache::Memcached;
  2         202290  
  2         89  
9              
10             our $VERSION = '0.04';
11              
12 2     2   2360 use Data::Dumper;
  2         20040  
  2         186  
13             $Data::Dumper::Sortkeys = 1;
14              
15              
16 2     2   19 use base 'Exporter';
  2         4  
  2         323  
17              
18             our @EXPORT = qw( memoize_memcached );
19             our @EXPORT_OK = qw( unmemoize flush_cache );
20             our %EXPORT_TAGS = (
21             all => [ @EXPORT, @EXPORT_OK ],
22             );
23              
24              
25 2         22 use fields qw(
26             key_prefix
27             expire_time
28             memcached_obj
29             key_error
30             scalar_error
31 2     2   14 );
  2         4  
32              
33              
34              
35             my %memo_data;
36             my %memcached_config;
37              
38              
39             sub memoize_memcached {
40             # Be sure to leave @_ intact in case we need to redirect to
41             # 'Memoize::memoize'.
42 0     0 1 0 my ($function, %args) = @_;
43              
44 0 0 0     0 if (exists $args{LIST_CACHE} or exists $args{ARRAY_CACHE}) {
45 0         0 carp "Call to 'memoize_memcached' with a cache option passed to 'memoize'";
46 0         0 goto &Memoize::memoize;
47             }
48              
49 0   0     0 my $memcached_args = delete $args{memcached} || {};
50 0 0       0 croak "Invalid memcached argument (expected a hash)"
51             unless ref $memcached_args eq 'HASH';
52              
53 0         0 _memcached_setup(
54 0         0 %{$memcached_args},
55             memoized_function => $function,
56             );
57 0         0 $args{LIST_CACHE} = [ HASH => $memo_data{$function}{list_cache} ];
58 0         0 $args{SCALAR_CACHE} = [ HASH => $memo_data{$function}{scalar_cache} ];
59              
60             # If we are passed a normalizer, we need to keep a version of it
61             # around for flush_cache to use. This breaks encapsulation. And it
62             # is just plain ugly.
63 0 0       0 $memo_data{$function}{normalizer} = Memoize::_make_cref($args{NORMALIZER}, scalar caller)
64             if defined $args{NORMALIZER};
65              
66             # Rebuild @_ since there is a good probability we have removed some
67             # arguments meant for us and added the cache arguments.
68 0         0 @_ = ($function, %args);
69 0         0 goto &Memoize::memoize;
70             }
71              
72              
73             # Unfortunately, we need to do some magic to make flush_cache sorta
74             # work. I don't think this is enough magic yet.
75              
76             sub flush_cache {
77             # If we have exactly 1 argument then we are probably expected to
78             # clear the cache for a single function. Pass this along to
79             # Memoize, even though it cannot be handled correctly at this time
80             # (whatever we do will be wrong, anyway).
81              
82 0 0   0 1 0 goto &Memoize::flush_cache if @_ == 1;
83              
84              
85             # If we have more than 1 argument, we are probably expected to clear
86             # a single call signature for a function. This we can almost do
87             # properly.
88              
89             # Even though we can do this "properly", it is still very bad. This
90             # breaks encapsulation pretty disgustingly. With any luck Memoize
91             # will eventually be patched to do this for us...
92              
93 0 0       0 if (@_ > 1) {
94 0         0 my ($function, @args) = @_;
95 0         0 my $cur_memo = $memo_data{$function};
96 0         0 my $normalizer = $memo_data{$function}{normalizer};
97 0         0 my $array_argstr;
98             my $scalar_argstr;
99 0 0       0 if (defined $normalizer) {
100 0         0 ($array_argstr) = $normalizer->(@_);
101 0         0 $scalar_argstr = $normalizer->(@_);
102             }
103             else { # Default normalizer
104 0         0 local $^W = 0;
105 0         0 $array_argstr = $scalar_argstr = join chr(28), @args;
106             }
107 0         0 for my $cache (qw( list_cache scalar_cache )) {
108 0         0 for my $argstr ($scalar_argstr, $array_argstr) {
109 0         0 delete $cur_memo->{$cache}{$argstr};
110             }
111             }
112 0         0 return 1;
113             }
114              
115              
116             # Currently all memoized functions share memcached config, so just
117             # find the first valid object and flush cache.
118              
119 0         0 for my $function (keys %memo_data) {
120 0 0       0 next unless $memo_data{$function}{list_obj};
121 0         0 $memo_data{$function}{list_obj}{memcached_obj}->flush_all;
122 0         0 last;
123             }
124              
125 0         0 return 1;
126             }
127              
128              
129             sub import {
130 2     2   24 my ($class) = @_;
131              
132             # Search through the arg list for the 'memcached' arg, process it,
133             # and remove it (and its associated value) from the arg list in
134             # anticipation of passing off to Exporter.
135 2     2   2995 for my $idx ($[ + 1 .. $#_) {
  2         942  
  2         2076  
  2         17  
136 3   100     16 my $arg = $_[$idx] || q();
137 3 100       11 next unless $arg eq 'memcached';
138 1         4 (undef, my $memcached_config) = splice @_, $idx, 2;
139 1 50       6 croak "Invalid memcached config (expected a hash ref)"
140             unless ref $memcached_config eq 'HASH';
141 1         2 %memcached_config = %{$memcached_config};
  1         4  
142             }
143              
144 2         2958 return $class->export_to_level(1, @_);
145             }
146              
147              
148             sub _memcached_setup {
149 0     0     my %args = %memcached_config;
150 0           while (@_) {
151 0           my $key = shift;
152 0           my $value = shift;
153 0           $args{$key} = $value;
154             }
155              
156 0           my $function = delete $args{memoized_function};
157 0           my $list_key_prefix = delete $args{list_key_prefix};
158 0           my $scalar_key_prefix = delete $args{scalar_key_prefix};
159              
160 0 0         $args{key_prefix} = 'memoize-' unless defined $args{key_prefix};
161              
162 0 0         croak "Missing function name for memcached setup"
163             unless defined $function;
164 0           my $tie_data = $memo_data{$function} = {
165             list_obj => undef,
166             list_cache => {},
167             scalar_obj => undef,
168             scalar_cache => {},
169             };
170              
171 0           my %cur_args = %args;
172 0 0         $cur_args{key_prefix}
    0          
173             .= (defined $function ? "$function-" : '-')
174             . (defined $list_key_prefix ? $list_key_prefix : 'list-')
175             ;
176 0 0         $tie_data->{list_obj} = tie %{$tie_data->{list_cache}}, __PACKAGE__, %cur_args
  0            
177             or die "Error creating list cache";
178              
179 0           %cur_args = %args;
180 0 0         $cur_args{key_prefix}
    0          
181             .= (defined $function ? "$function-" : '-')
182             . (defined $scalar_key_prefix ? $scalar_key_prefix : 'scalar-')
183             ;
184 0 0         $tie_data->{scalar_obj} = tie %{$tie_data->{scalar_cache}}, __PACKAGE__, %cur_args
  0            
185             or die "Error creating scalar cache";
186              
187 0           return 1;
188             }
189              
190              
191             sub _new {
192 0     0     my $class = shift;
193 0 0         croak "Called new in object context" if ref $class;
194 0           my $self = fields::new($class);
195 0           $self->_init(@_);
196 0           return $self;
197             }
198              
199              
200             sub _init {
201 0     0     my $self = shift;
202 0           my %args = @_;
203 0           %{$self} = ();
  0            
204              
205 0           $self->{key_prefix} = delete $args{key_prefix};
206 0 0         $self->{key_prefix} = q() unless defined $self->{key_prefix};
207 0 0         $self->{expire_time} = exists $args{expire_time} ? delete $args{expire_time} : undef;
208              
209             # Default these to false so that we can use Data::Dumper on tied
210             # hashes by default. Yes, it will show them as empty, but I doubt
211             # someone running Dumper on this tied hash would really want to dump
212             # the contents of the memcached cache (and they can't anyway).
213              
214             $self->{$_} = exists $args{$_} ? delete $args{$_} : !1
215 0 0         for qw( key_error scalar_error );
216              
217 0           $self->{memcached_obj} = Cache::Memcached->new(\%args);
218              
219 0           return $self;
220             }
221              
222              
223             sub _get_key {
224 0     0     my $self = shift;
225 0           my $key = shift;
226 0           return $self->{key_prefix} . $key;
227             }
228              
229              
230             sub _key_lookup_error {
231 0     0     croak "Key lookup functionality is not implemented by memcached";
232             }
233              
234              
235             sub TIEHASH {
236 0     0     my $class = shift;
237 0           return $class->_new(@_);
238             }
239              
240              
241             sub STORE {
242 0     0     my $self = shift;
243 0           my $key = $self->_get_key(shift);
244 0           my $value = shift;
245 0           my @args = ($key, $value);
246 0 0         push @args, $self->{expire_time} if defined $self->{expire_time};
247 0           $self->{memcached_obj}->set(@args);
248 0           return $self;
249             }
250              
251              
252             sub FETCH {
253 0     0     my $self = shift;
254 0           my $key = $self->_get_key(shift);
255 0           return $self->{memcached_obj}->get($key);
256             }
257              
258              
259             sub EXISTS {
260 0     0     my $self = shift;
261 0           return defined $self->FETCH(@_);
262             }
263              
264              
265             sub DELETE {
266 0     0     my $self = shift;
267 0           my $key = $self->_get_key(shift);
268 0           $self->{memcached_obj}->delete($key);
269 0           return $self;
270             }
271              
272              
273             sub CLEAR {
274 0     0     my $self = shift;
275             # This is not safe because all object share memcached setup.
276 0           $self->{memcached_obj}->flush_all;
277 0           return $self;
278             }
279              
280              
281             sub FIRSTKEY {
282 0     0     my $self = shift;
283 0 0         return unless $self->{key_error};
284 0           $self->_key_lookup_error;
285             }
286              
287              
288             sub NEXTKEY {
289 0     0     my $self = shift;
290 0 0         return unless $self->{key_error};
291 0           $self->_key_lookup_error;
292             }
293              
294              
295             sub SCALAR {
296 0     0     my $self = shift;
297 0 0         return unless $self->{scalar_error};
298             # I think this error still makes sense, since to determine if the
299             # cache has content one would need to first determine if the cache
300             # contains keys.
301 0           $self->_key_lookup_error;
302             }
303              
304              
305             sub UNTIE {
306 0     0     my $self = shift;
307 0           $self->{memcached_obj}->disconnect_all;
308 0           return $self;
309             }
310              
311              
312              
313             1;
314              
315             __END__