File Coverage

blib/lib/Data/Model/Driver/Memcached.pm
Criterion Covered Total %
statement 144 276 52.1
branch 69 168 41.0
condition 16 22 72.7
subroutine 15 25 60.0
pod 1 15 6.6
total 245 506 48.4


line stmt bran cond sub pod time code
1             # storaged to memcache protocol (not for cache)
2             package Data::Model::Driver::Memcached;
3 42     42   6442110 use strict;
  42         118  
  42         1553  
4 42     42   278 use warnings;
  42         79  
  42         1231  
5 42     42   228 use base 'Data::Model::Driver';
  42         170  
  42         24449  
6              
7 42     42   325 use Carp ();
  42         81  
  42         83673  
8             $Carp::Internal{(__PACKAGE__)}++;
9              
10 0     0 0 0 sub memcached { shift->{memcached} }
11              
12 0     0 0 0 sub update_direct { Carp::croak("update_direct is NOT IMPLEMENTED") }
13              
14             sub init {
15 4     4 0 7 my $self = shift;
16 4 50       27 if (my $serializer = $self->{serializer}) {
17 0 0       0 $serializer = 'Data::Model::Driver::Memcached::Serializer::' . $serializer
18             unless $serializer =~ s/^\+//;
19 0 0       0 unless ($serializer eq 'Data::Model::Driver::Memcached::Serializer::Default') {
20 0         0 eval "use $serializer"; ## no critic
21 0         0 Carp::croak $@;
22             }
23 0         0 $self->{serializer} = $serializer;
24             }
25             }
26              
27             sub lookup {
28 0     0 0 0 my($self, $schema, $key) = @_;
29 0         0 my $cache_key = $self->cache_key($schema, $key);
30 0         0 my $ret = $self->{memcached}->get( $cache_key );
31 0 0       0 return unless $ret;
32 0 0       0 $ret = $self->{serializer}->deserialize($self, $ret) if $self->{serializer};
33 0         0 my $map = $schema->options->{column_name_rename};
34 0 0       0 $ret = $self->column_name_rename($map, $ret, 1) if $map;
35 0 0       0 $ret = $self->revert_undefvalue($schema, $ret) if $self->{ignore_undef_value};
36 0 0       0 $ret = $self->revert_keyvalue($schema, $key, $ret) if $self->{strip_keys};
37 0         0 return $ret;
38             }
39              
40             sub lookup_multi {
41 0     0 0 0 my($self, $schema, $keys) = @_;
42 0         0 my $keys_map = {};
43 0         0 my @cache_keys = map { my $k = $self->cache_key($schema, $_); $keys_map->{$k} = $_ ; $k } @{ $keys };
  0         0  
  0         0  
  0         0  
  0         0  
44 0         0 my $ret = $self->{memcached}->get_multi( @cache_keys );
45 0 0       0 return unless $ret;
46              
47 0         0 my %resultlist;
48 0         0 while (my($id, $data) = each %{ $ret }) {
  0         0  
49 0 0       0 $data = $self->{serializer}->deserialize($self, $data) if $self->{serializer};
50 0         0 my $map = $schema->options->{column_name_rename};
51 0 0       0 $data = $self->column_name_rename($map, $data, 1) if $map;
52 0 0       0 $data = $self->revert_undefvalue($schema, $data) if $self->{ignore_undef_value};
53 0 0       0 $data = $self->revert_keyvalue($schema, $keys_map->{$id}, $data) if $self->{strip_keys};
54 0         0 my $key = $schema->get_key_array_by_hash($data);
55 0         0 $resultlist{join "\0", @{ $key }} = +{ %{ $data } };
  0         0  
  0         0  
56             }
57 0         0 return \%resultlist;
58             }
59              
60             sub get {
61 0     0 0 0 my($self, $schema, $key, $columns, %args) = @_;
62              
63 0         0 my $cache_key = $self->cache_key($schema, $key);
64 0         0 my $ret = $self->{memcached}->get( $cache_key );
65 0 0       0 return unless $ret;
66 0 0       0 $ret = $self->{serializer}->deserialize($self, $ret) if $self->{serializer};
67 0         0 my $map = $schema->options->{column_name_rename};
68 0 0       0 $ret = $self->column_name_rename($map, $ret, 1) if $map;
69 0 0       0 $ret = $self->revert_undefvalue($schema, $ret) if $self->{ignore_undef_value};
70 0 0       0 $ret = $self->revert_keyvalue($schema, $key, $ret) if $self->{strip_keys};
71 0         0 return $self->_generate_result_iterator([ $ret ]), +{};
72             }
73              
74             sub set {
75 0     0 0 0 my($self, $schema, $key, $columns, %args) = @_;
76              
77 0         0 my $cache_key = $self->cache_key($schema, $key);
78 0         0 my $data = $columns;
79 0 0       0 $data = $self->strip_keyvalue($schema, $key, $data) if $self->{strip_keys};
80 0 0       0 $data = $self->strip_undefvalue($schema, $data) if $self->{ignore_undef_value};
81 0         0 my $map = $schema->options->{column_name_rename};
82 0 0       0 $data = $self->column_name_rename($map, $data) if $map;
83 0 0       0 $data = $self->{serializer}->serialize($self, $data) if $self->{serializer};
84 0 0       0 my $ret = $self->{always_overwrite} ?
85             $self->{memcached}->set( $cache_key, $data ) :
86             $self->{memcached}->add( $cache_key, $data );
87 0 0       0 return unless $ret;
88              
89 0         0 $columns;
90             }
91              
92             sub replace {
93 0     0 0 0 my($self, $schema, $key, $columns, %args) = @_;
94              
95 0         0 my $cache_key = $self->cache_key($schema, $key);
96 0         0 my $data = $columns;
97 0 0       0 $data = $self->strip_keyvalue($schema, $key, $data) if $self->{strip_keys};
98 0 0       0 $data = $self->strip_undefvalue($schema, $data) if $self->{ignore_undef_value};
99 0         0 my $map = $schema->options->{column_name_rename};
100 0 0       0 $data = $self->column_name_rename($map, $data) if $map;
101 0 0       0 $data = $self->{serializer}->serialize($self, $data) if $self->{serializer};
102 0         0 my $ret = $self->{memcached}->set( $cache_key, $data );
103 0 0       0 return unless $ret;
104              
105 0         0 $columns;
106             }
107              
108             sub update {
109 0     0 0 0 my($self, $schema, $old_key, $key, $old_columns, $columns, $changed_columns, %args) = @_;
110              
111 0         0 my $old_cache_key = $self->cache_key($schema, $old_key);
112 0         0 my $new_cache_key = $self->cache_key($schema, $key);
113 0 0       0 unless ($old_cache_key eq $new_cache_key) {
114 0         0 my $ret = $self->delete($schema, $old_key);
115 0 0       0 return unless $ret;
116             }
117              
118 0         0 my $data = $columns;
119 0 0       0 $data = $self->strip_keyvalue($schema, $key, $data) if $self->{strip_keys};
120 0 0       0 $data = $self->strip_undefvalue($schema, $data) if $self->{ignore_undef_value};
121 0         0 my $map = $schema->options->{column_name_rename};
122 0 0       0 $data = $self->column_name_rename($map, $data) if $map;
123 0 0       0 $data = $self->{serializer}->serialize($self, $data) if $self->{serializer};
124 0         0 my $ret = $self->{memcached}->set( $new_cache_key, $data );
125 0 0       0 return unless $ret;
126              
127 0         0 $columns;
128             }
129              
130             sub delete {
131 0     0 0 0 my($self, $schema, $key, $columns, %args) = @_;
132 0         0 my $cache_key = $self->cache_key($schema, $key);
133 0         0 my $data = $self->{memcached}->get( $cache_key );
134 0 0       0 return unless $data;
135 0         0 my $ret = $self->{memcached}->delete( $cache_key );
136 0 0       0 return unless $ret;
137 0         0 $data;
138             }
139              
140             sub strip_keyvalue {
141 3     3 0 27 my($self, $schema, $keys, $columns) = @_;
142 3         3 my $data = { %{ $columns } };
  3         11  
143 3         5 for my $key (@{ $schema->key }) {
  3         9  
144 6         11 delete $data->{$key};
145             }
146 3         10 $data;
147             }
148              
149             sub revert_keyvalue {
150 3     3 0 1510 my($self, $schema, $keys, $columns) = @_;
151 3         5 my $i = 0;
152 3         5 my $data = { %{ $columns } };
  3         9  
153 3         4 for my $key (@{ $schema->key }) {
  3         11  
154 6         27 $data->{$key} = $keys->[$i++].''; # copy
155             }
156 3         8 $data;
157             }
158              
159             sub strip_undefvalue {
160 1     1 0 12 my($self, $schema, $columns) = @_;
161 1         2 my $data = { %{ $columns } };
  1         4  
162 1         3 for my $key (@{ $schema->columns }) {
  1         6  
163 3 100 66     19 delete $data->{$key} unless exists $data->{$key} && defined $data->{$key};
164             }
165 1         3 $data;
166             }
167              
168             sub revert_undefvalue {
169 1     1 0 1668 my($self, $schema, $columns) = @_;
170 1         2 my $data = { %{ $columns } };
  1         4  
171 1         2 for my $key (@{ $schema->columns }) {
  1         6  
172 3 100 66     20 $data->{$key} = undef unless exists $data->{$key} && defined $data->{$key};
173             }
174 1         3 $data;
175             }
176              
177             sub column_name_rename {
178 0     0 1 0 my($self, $map, $columns, $is_reverse) = @_;
179 0 0       0 if ($is_reverse) {
180 0         0 my $tmp = {};
181 0         0 while (my($k, $v) = each %{ $map }) {
  0         0  
182 0         0 $tmp->{$v} = $k;
183             }
184 0         0 $map = $tmp;
185             }
186              
187 0         0 my $data = {};
188 0         0 while (my($k, $v) = each %{ $columns }) {
  0         0  
189 0 0       0 if (my $n = $map->{$k}) {
190 0         0 $data->{$n} = $v;
191             } else {
192 0         0 $data->{$k} = $v;
193             }
194             }
195 0         0 $data;
196             }
197              
198             package
199             Data::Model::Driver::Memcached::Serializer::Default;
200             # serializer use messagepack format
201             # implement format is map16, map32, fixmap and nil, raw16, rwa32, fixraw and Positive FixNum, uint
202             # see http://msgpack.sourceforge.jp/spec
203 42     42   331 use strict;
  42         112  
  42         1530  
204 42     42   706 use warnings;
  42         103  
  42         1231  
205 42     42   229 use Carp ();
  42         74  
  42         83687  
206             $Carp::Internal{(__PACKAGE__)}++;
207              
208             my $MAGIC = 'd'^'e'^'f'^'a'^'u'^'l'^'t';
209             my $MAP16 = pack 'C', 0xde;
210             my $MAP32 = pack 'C', 0xdf;
211             my $RAW16 = pack 'C', 0xda;
212             my $RAW32 = pack 'C', 0xdb;
213             my $NIL = pack 'C', 0xc0;
214              
215             my $UINT8 = pack 'C', 0xcc;
216             my $UINT16 = pack 'C', 0xcd;
217             my $UINT32 = pack 'C', 0xce;
218             my $UINT64 = pack 'C', 0xcf;
219              
220 42     42   17850 our $HAS_DATA_MESSAGEPACK = eval "use Data::MessagePack; if (\$Data::MessagePack::VERSION >= 0.05) { 1 } else { 0 };" or 0; ## no critic
  0         0  
  0         0  
221              
222             sub serialize {
223 20     20   26325 my($class, $c, $hash) = @_;
224 20 50       62 Carp::croak "usage: $class->serialize(\$self, \$hashref)" unless ref($hash) eq 'HASH';
225 20 50       47 if ($HAS_DATA_MESSAGEPACK) {
226 0         0 local $Data::MessagePack::PreferInteger = 1;
227 0         0 my $ret = eval { Data::MessagePack->pack( $hash ) };
  0         0  
228 0 0       0 if ($@) {
229 0         0 require Data::Dumper;
230 0         0 warn Data::Dumper::Dumper($hash);
231             {
232 0         0 local $@;
  0         0  
233 0         0 eval { require Devel::Peek };
  0         0  
234 0 0       0 unless ($@) {
235 0         0 Devel::Peek::Dump($hash);
236             }
237             }
238 0         0 die $@;
239             }
240 0         0 return $MAGIC.$ret;
241             }
242 20         28 my $num = scalar(keys(%{ $hash }));
  20         48  
243 20 50       48 Carp::croak "this serializer work is under 2^32 columns" if $num > 0xffffffff;
244              
245 20         33 my $pack = $MAGIC;
246 20 100       41 if ($num < 16) {
    50          
247             # FixMap
248 19         55 $pack .= pack 'C', (0x80 + $num);
249             } elsif ($num < 0xffff) {
250             # map16
251 1         5 $pack .= $MAP16 . pack('n', $num);
252             } else {
253             # map32
254 0         0 $pack .= $MAP32 . pack('N', $num);
255             }
256              
257 20         27 for my $k (sort keys %{ $hash }) {
  20         103  
258 77         149 my $v = $hash->{$k};
259 77 50       128 if (defined $k) {
260 77 100 66     636 if ($k =~ /\A[0-9]+\z/ && $k <= 0xffffffff) {
261             # Positive FixNum, uint
262 20 100       54 if ($k <= 0x7f) {
    100          
    100          
    50          
263             # Positive FixNum
264 8         14 $pack .= pack('C', $k);
265             } elsif ($k <= 0xff) {
266             # uint 8
267 3         7 $pack .= $UINT8 . pack('C', $k);
268             } elsif ($k <= 0xffff) {
269             # uint 16
270 3         9 $pack .= $UINT16 . pack('n', $k);
271             } elsif ($k <= 0xffffffff) {
272             # uint 32
273 6         17 $pack .= $UINT32 . pack('N', $k);
274             } else {
275 0         0 Carp::croak "oops? ($k => $v)";
276             }
277             } else {
278 57         75 my $l = length($k);
279 57 100       96 if ($l < 32) {
    100          
    50          
280 54         99 $pack .= pack 'C', 0xa0 + $l;
281             } elsif ($l <= 0xffff) {
282 2         5 $pack .= $RAW16 . pack('n', $l);
283             } elsif ($l <= 0xffffffff) {
284 1         4 $pack .= $RAW32 . pack('N', $l);
285             } else {
286 0         0 Carp::croak "this serializer work is under 2^32 length ($k => $v)";
287             }
288 57         409 $pack .= $k;
289             }
290             } else {
291             # undef
292 0         0 $pack .= $NIL;
293             }
294              
295 77 100       131 if (defined $v) {
296 73 100 66     633 if ($v =~ /\A[0-9]+\z/ && $v <= 0xffffffff) {
297             # Positive FixNum, uint
298 42 100       120 if ($v <= 0x7f) {
    100          
    100          
    50          
299             # Positive FixNum
300 18         44 $pack .= pack('C', $v);
301             } elsif ($v <= 0xff) {
302             # uint 8
303 6         21 $pack .= $UINT8 . pack('C', $v);
304             } elsif ($v <= 0xffff) {
305             # uint 16
306 6         18 $pack .= $UINT16 . pack('n', $v);
307             } elsif ($v <= 0xffffffff) {
308             # uint 32
309 12         43 $pack .= $UINT32 . pack('N', $v);
310             } else {
311 0         0 Carp::croak "oops? ($k => $v)";
312             }
313             } else {
314 31         41 my $l = length($v);
315 31 100       55 if ($l < 32) {
    100          
    50          
316 28         51 $pack .= pack 'C', 0xa0 + $l;
317             } elsif ($l <= 0xffff) {
318 2         6 $pack .= $RAW16 . pack('n', $l);
319             } elsif ($l <= 0xffffffff) {
320 1         4 $pack .= $RAW32 . pack('N', $l);
321             } else {
322 0         0 Carp::croak "this serializer work is under 2^32 length ($k => $v)";
323             }
324 31         261 $pack .= $v;
325             }
326             } else {
327             # undef
328 4         8 $pack .= $NIL;
329             }
330             }
331              
332 20         89 $pack;
333             }
334              
335             sub deserialize {
336 20     20   89 my($class, $c, $pack) = @_;
337 20   50     43 $pack ||= '';
338 20         361 $pack =~ s/^(.)//;
339 20   50     102 my $fmt = $1 || '';
340 20 50       44 Carp::croak "this pack data is not Default format" unless $fmt eq $MAGIC;
341 20 50       39 if ($HAS_DATA_MESSAGEPACK) {
342 0         0 return Data::MessagePack->unpack( $pack );
343             }
344              
345 20         22 my $pos = 0;
346 20         29 my $len = length($pack);
347              
348             # unpack hash header
349 20         38 my $map_type = substr($pack, $pos++, 1);
350 20         24 my $elements = 0;
351 20 100       52 if ($map_type eq $MAP16) {
    50          
352 1         228 $elements = unpack 'n', substr($pack, $pos);
353 1         4 $pos += 2;
354             } elsif ($map_type eq $MAP32) {
355 0         0 $elements = unpack 'N', substr($pack, $pos);
356 0         0 $pos += 4;
357             } else {
358             # under 16 elements
359 19         40 $elements = unpack 'C', $map_type;
360 19         28 $elements -= 0x80;
361 19 50       41 Carp::croak "extra bytes" if $elements >= 16;
362             }
363              
364             # unpack for map elements
365 20         33 my $hash = +{};
366 20         39 for (1..$elements) {
367 77         75 my $k;
368 77         114 for (0..1) {
369 154         165 my $v;
370             my $len;
371              
372 154         225 my $data_type = substr($pack, $pos++, 1);
373 154 100 100     923 if ($data_type eq $NIL) {
    100 100        
374 4         5 $v = undef;
375             } elsif ($data_type eq $UINT8 || $data_type eq $UINT16 || $data_type eq $UINT32) {
376 36 100       99 if ($data_type eq $UINT8) {
    100          
    50          
377 9         20 $v = unpack('C', substr($pack, $pos++));
378             } elsif ($data_type eq $UINT16) {
379 9         22 $v = unpack('n', substr($pack, $pos));
380 9         13 $pos += 2;
381             } elsif ($data_type eq $UINT32) {
382 18         33 $v = unpack('N', substr($pack, $pos));
383 18         47 $pos += 4;
384             }
385             } else {
386 114         114 my $is_num;
387 114 100       221 if ($data_type eq $RAW16) {
    100          
388 4         291 $len = unpack 'n', substr($pack, $pos);
389 4         8 $pos += 2;
390             } elsif ($data_type eq $RAW32) {
391 2         228 $len = unpack 'N', substr($pack, $pos);
392 2         6 $pos += 4;
393             } else {
394 108         189 $len = unpack 'C', $data_type;
395 108 100       194 if ($len <= 0x7f) {
396             # Positive FixNum
397 26         31 $v = $len;
398 26         29 $is_num = 1;
399             } else {
400 82         95 $len -= 0xa0;
401 82 50       164 Carp::croak "extra bytes" if $len >= 32;
402             }
403             }
404 114 100       207 unless ($is_num) {
405 88         262 $v = substr($pack, $pos, $len);
406 88         112 $pos += $len;
407             }
408             }
409              
410 154 100       238 if ($_) {
411 77         578 $hash->{$k} = $v;
412             } else {
413 77         125 $k = $v;
414             }
415             }
416             }
417 20 50       55 Carp::croak "extra bytes" unless $len == $pos;
418              
419 20         56 $hash;
420             }
421              
422             1;
423              
424             =head1 NAME
425              
426             Data::Model::Driver::Memcached - storage driver for memcached protocol
427              
428             =head1 SYNOPSIS
429              
430             package MyDB;
431             use base 'Data::Model';
432             use Data::Model::Schema;
433             use Data::Model::Driver::Memcached;
434            
435             my $dbi_connect_options = {};
436             my $driver = Data::Model::Driver::Memcached->new(
437             memcached => Cache::Memcached::Fast->new({ servers => [ { address => "localhost:11211" }, ], }),
438             );
439            
440             base_driver $driver;
441             install_model model_name => schema {
442             ....
443             };
444              
445             =head1 DESCRIPTION
446              
447             Storage is used via a memcached protocol.
448              
449             It can save at memcached, Tokyo Tyrant, kai, groonga, etc.
450              
451             =head1 OPTIONS
452              
453             =head2 serializer
454              
455             my $driver = Data::Model::Driver::Memcached->new(
456             memcached => Cache::Memcached::Fast->new({ servers => [ { address => "localhost:11211" }, ], }),
457             serializer => 'Default', # default is L or messagepack minimum set for Data::Model
458             );
459              
460             you can use customizable serializer.
461              
462             {
463             package MySerializer;
464             sub serialize {
465             my($class, $c, $hash) = @_;
466             # you serialize of $hash
467             return $serialize_string;
468             }
469             sub deserialize {
470             my($class, $c, $serialize_string) = @_;
471             ...
472             return $hash;
473             }
474             }
475             my $driver = Data::Model::Driver::Memcached->new(
476             memcached => Cache::Memcached::Fast->new({ servers => [ { address => "localhost:11211" }, ], }),
477             serializer => '+MySerializer',
478             );
479              
480             =head2 strip_keys
481              
482             strip tables key data, Because key data stored in a memcached key part.
483              
484             my $driver = Data::Model::Driver::Memcached->new(
485             memcached => Cache::Memcached::Fast->new({ servers => [ { address => "localhost:11211" }, ], }),
486             strip_keys => 1,
487             );
488              
489             =head2 ignore_undef_value
490              
491             When B is B, a value is not put into storage.
492              
493             It becomes size saving at the time of obvious empty data.
494              
495             my $driver = Data::Model::Driver::Memcached->new(
496             memcached => Cache::Memcached::Fast->new({ servers => [ { address => "localhost:11211" }, ], }),
497             ignore_undef_value => 1,
498             );
499              
500             =head2 model_name_realname column_name_rename
501              
502             compress your table name and column name.
503              
504             =head1 OPTIONS EXAMPLE
505              
506             my $driver = Data::Model::Driver::Memcached->new(
507             memcached => Cache::Memcached::Fast->new({ servers => [ { address => "localhost:11211" }, ], namespace => 'test', }),
508             serializer => 'Default',
509             strip_keys => 1,
510             );
511             install_model simple => schema {
512             schema_options model_name_realname => 's';
513             key 'id';
514             column 'id';
515             column 'name';
516             column 'nickname';
517             schema_options column_name_rename => {
518             id => 1,
519             name => 2,
520             nickname => 3,
521             };
522             };
523              
524             $model->set(
525             simple => 'keyvalue' => {
526             name => 'osawa',
527             nickname => 'yappo',
528             }
529             );
530             # same code
531             $memcached->add(
532             'tests:keyvalue',
533             Data::MessagePack->pack({ 2 => 'osawa', 3 => 'yappo' }),
534             );
535              
536             =head1 SEE ALSO
537              
538             L,
539             L
540              
541             =head1 AUTHOR
542              
543             Kazuhiro Osawa Eyappo shibuya plE
544              
545             =head1 LICENSE
546              
547             This library is free software; you can redistribute it and/or modify
548             it under the same terms as Perl itself.
549              
550             =cut