File Coverage

blib/lib/MemcacheDBI.pm
Criterion Covered Total %
statement 28 157 17.8
branch 2 60 3.3
condition 2 29 6.9
subroutine 8 30 26.6
pod 5 5 100.0
total 45 281 16.0


line stmt bran cond sub pod time code
1             package MemcacheDBI;
2 3     3   1832 use strict;
  3         6  
  3         103  
3 3     3   10 use warnings;
  3         3  
  3         81  
4 3     3   4368 use DBI;
  3         40250  
  3         195  
5 3     3   1490 use Clone;
  3         7382  
  3         128  
6 3     3   15 use vars qw( $AUTOLOAD $VERSION );
  3         4  
  3         1842  
7             $VERSION = '0.08';
8             require 5.10.0;
9              
10             our $DEBUG;
11             our $me = '[MemcacheDBI]';
12              
13             =head1 NAME
14              
15             MemcacheDBI - Queue memcache calls when in a dbh transaction
16              
17             =head1 SYNOPSYS
18              
19             MemcacheDBI is a drop in replacement for DBI. It allows you to do trivial caching of some objects in a somewhat transactionally safe manner.
20              
21             use MemcacheDBI;
22             my $dbh = MemcacheDBI->connect($data_source, $user, $password, {} ); # just like DBI
23             $dbh->memd_init(\%memcache_connection_args) # see Cache::Memcached::Fast
24              
25             # Cache::Memcached::Fast should work using these calls
26             $dbh->memd->get();
27             $dbh->memd->set();
28             $memd = $dbh->memd; #get a handle you can use wherever
29              
30             # DBI methods should all work as normal. Additional new methods listed below
31             $dbh->prepare();
32             $dbh->execute();
33             etc
34              
35             =head1 DESCRIPTION
36              
37             Attach your memcached to your DBH handle. By doing so we can automatically queue set/get calls so that they happen at the same time as a commit. If a rollback is issued then the queue will be cleared.
38              
39             =head1 CAVEATS
40              
41             As long as DBI and Memcache are both up and running your fine. However this module will experience race conditions when one or the other goes down. We are currently working to see if some of this can be minimized, but be aware it is impossible to protect you if the DB/Memcache servers go down.
42              
43             =head1 METHODS
44              
45             =head2 memd_init
46              
47             Normally you would use a MemcacheDBI->connect to create a new handle. However if you already have a DBH handle you can use this method to create a MemcacheDBI object using your existing handle.
48              
49             Accepts a the following data types
50              
51             Cache::Memcached::Fast (new Cache::Memcached::Fast)
52             A DBI handle (DBI->connect)
53             HASH of arguments to pass to new Cache::Memcached::Fast
54              
55             =cut
56              
57             sub memd_init {
58 0 0 0 0 1 0 warn "[debug $DEBUG]$me->memd_init\n" if $DEBUG && $DEBUG > 3;
59 0         0 my $class = shift;
60 0 0       0 my $node = ref $class ? $class : do{ tie my %node, 'MemcacheDBI::Tie'; warn 'whee'; \%node; };
  0         0  
  0         0  
  0         0  
61 0         0 while (my $handle = shift) {
62 0 0       0 if (ref $handle eq 'DBI::db') {
    0          
    0          
63 0         0 $node->{'MemcacheDBI'}->{'dbh'} = $handle;
64             } elsif (ref $handle eq 'Cache::Memcached::Fast') {
65 0         0 $node->{'MemcacheDBI'}->{'memd'} = MemcacheDBI::Memd->memd_init($node,$handle);
66             } elsif (ref $handle eq 'HASH') {
67 0         0 $node->{'MemcacheDBI'}->{'memd'} = MemcacheDBI::Memd->memd_init($node,$handle);
68             } else {
69 0         0 die 'Unknown ref type'.do{my @c = caller; ' at '.$c[1].' line '.$c[2]."\n" };
  0         0  
  0         0  
70             }
71             }
72 0 0       0 if (! ref $class) {
73 0 0       0 return unless $node->{'MemcacheDBI'}->{'dbh'};
74 0         0 return bless $node, $class;
75             }
76 0         0 return $class;
77             }
78              
79             =head2 memd
80              
81             Get a memcache object that supports get/set/transactions
82              
83             =cut
84              
85             sub memd {
86 0     0 1 0 shift->{'MemcacheDBI'}->{'memd'};
87             }
88              
89             =head1 DBI methods can also be used, including but not limited to:
90              
91             =head2 connect
92              
93             The same as DBI->connect, returns a MemcacheDBI object so you can get your additional memcache functionality
94              
95             =cut
96              
97             sub connect {
98 2 50 33 2 1 740 warn "[debug $DEBUG]$me->connect\n" if $DEBUG && $DEBUG > 3;
99 2         2 my $class = shift;
100 2         6 tie my %node, 'MemcacheDBI::Tie';
101 2 50       2 eval{ $node{'MemcacheDBI'}->{'dbh'} = DBI->connect(@_) } or die $@.do{my @c = caller; ' at '.$c[1].' line '.$c[2]."\n" };
  2         1493  
  2         58  
  2         8  
102 0 0       0 return unless $node{'MemcacheDBI'}->{'dbh'};
103 0         0 return bless \%node, $class;
104             }
105              
106             =head2 commit
107              
108             The same as DBI->commit, however it will also commit the memcached queue
109              
110             =cut
111              
112             sub commit {
113 0 0 0 0 1 0 warn "[debug $DEBUG]$me->commit\n" if $DEBUG && $DEBUG > 3;
114 0         0 my $self = shift;
115             # TODO handle rolling back the memcache stuff if dbh fails
116 0 0       0 warn 'Commit ineffective while AutoCommit is on'.do{my @c = caller; ' at '.$c[1].' line '.$c[2]."\n" } if $self->{'AutoCommit'};
  0         0  
  0         0  
117 0         0 my $memd = $self->memd;
118 0 0       0 $memd->commit if $memd;
119 0         0 $self->{'MemcacheDBI'}->{'dbh'}->commit(@_);
120             }
121              
122             =head2 rollback
123              
124             The same as DBI->rollback, however it will also rollback the memcached queue
125              
126             =cut
127              
128             sub rollback {
129 0 0 0 0 1 0 warn "[debug $DEBUG]$me->rollback\n" if $DEBUG && $DEBUG > 3;
130 0         0 my $self = shift;
131 0 0       0 warn 'rollback ineffective with AutoCommit enabled'.do{my @c = caller; ' at '.$c[1].' line '.$c[2]."\n" } if $self->{'AutoCommit'};
  0         0  
  0         0  
132 0         0 my $memd = $self->memd;
133 0 0       0 $memd->rollback if $memd;
134 0         0 $self->{'MemcacheDBI'}->{'dbh'}->rollback(@_);
135             }
136              
137             sub AUTOLOAD {
138 0     0   0 my $self = shift;
139 0         0 my($field)=$AUTOLOAD;
140 0         0 $field =~ s/.*://;
141 0         0 my $method = (ref $self).'::'.$field;
142 0 0 0     0 warn "[debug $DEBUG]$me create autoload for $method\n" if $DEBUG && $DEBUG > 1;
143 3     3   12 no strict 'refs'; ## no critic
  3         5  
  3         3281  
144             *$method = sub {
145 0     0   0 my $self = shift;
146 0 0 0     0 warn "[debug $DEBUG]${me}->{'dbh'}->$field\n" if $DEBUG && $DEBUG > 3;
147 0 0       0 die 'Can\'t locate object method "'.$field.'" via package "'.(ref $self->{'MemcacheDBI'}->{'dbh'}).'"'.do{my @c = caller; ' at '.$c[1].' line '.$c[2]."\n" } unless $self->{'MemcacheDBI'}->{'dbh'}->can($field);
  0         0  
  0         0  
148 0         0 $self->{'MemcacheDBI'}->{'dbh'}->$field(@_);
149 0         0 };
150 0 0       0 die 'Can\'t locate object method "'.$field.'" via package "'.(ref $self->{'MemcacheDBI'}->{'dbh'}).'"'.do{my @c = caller; ' at '.$c[1].' line '.$c[2]."\n" } unless $self->{'MemcacheDBI'}->{'dbh'}->can($field);
  0         0  
  0         0  
151 0         0 $self->$field(@_);
152             }
153              
154             package MemcacheDBI::Memd;
155              
156             sub memd_init {
157 0     0   0 my $class = shift;
158 0         0 my $dbh = shift;
159 0         0 my $handle = shift;
160 0         0 tie my %node, 'MemcacheDBI::Tie', 'memd';
161 0         0 require Cache::Memcached::Fast;
162 0 0       0 $handle = Cache::Memcached::Fast->new($handle) if ref $handle eq 'HASH';
163 0         0 $node{'MemcacheDBI'}{'memd'} = $handle;
164 0         0 $node{'MemcacheDBI'}{'dbh'} = $dbh; # careful, circular
165 0         0 return bless \%node, $class;
166             }
167              
168             sub get {
169 0     0   0 my ($self,$key) = @_;
170 0 0       0 return if $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue_delete'}->{$key};
171 0 0       0 if (exists $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue'}->{$key}) {
172 0         0 return $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue'}->{$key};
173             }
174 0         0 $self->{'MemcacheDBI'}->{'memd'}->get($key);
175             }
176              
177             sub set {
178 0     0   0 my ($self,$key,$value) = @_;
179 0         0 delete $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue_delete'}->{$key};
180 0 0       0 if ($self->{'MemcacheDBI'}->{'dbh'}->{'AutoCommit'}) {
181 0         0 delete $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue'}->{$key};
182 0         0 return $self->{'MemcacheDBI'}->{'memd'}->set($key, $value);
183             }
184 0         0 $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue'}->{$key} = Clone::clone($value);
185 0         0 1;
186             }
187              
188             sub delete {
189 0     0   0 my ($self,$key) = @_;
190 0 0       0 if ($self->{'MemcacheDBI'}->{'dbh'}->{'AutoCommit'}) {
191 0         0 delete $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue_delete'}->{$key};
192 0         0 delete $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue'}->{$key};
193 0         0 return $self->{'MemcacheDBI'}->{'memd'}->delete($key);
194             }
195 0         0 my $val = $self->get($key);
196 0         0 delete $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue'}->{$key};
197 0         0 $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue_delete'}->{$key} = 1;
198 0 0       0 $val ? 1 : '';
199             }
200 0     0   0 sub remove { shift->delete(@_); }
201              
202             sub namespace {
203 0     0   0 my $self = shift;
204 0 0 0     0 if (scalar @_ && !$self->{'MemcacheDBI'}->{'dbh'}->{'AutoCommit'} && (
      0        
      0        
205             $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue_delete'}
206             || $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue'}
207             )) {
208 0         0 die 'Cannot set namespace during a transaction'.do{my @c = caller; ' at '.$c[1].' line '.$c[2]."\n" };
  0         0  
  0         0  
209             }
210 0         0 $self->{'MemcacheDBI'}->{'memd'}->namespace(@_);
211             }
212              
213 0     0   0 sub server_versions { shift->{'MemcacheDBI'}->{'memd'}->server_versions(@_); }
214              
215             # do not confuse this with DBH commits
216             sub commit {
217 0     0   0 my ($self) = @_;
218 0         0 my $queue = $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue'};
219 0         0 foreach my $key (keys %$queue) {
220 0         0 $self->{'MemcacheDBI'}->{'memd'}->set($key, $queue->{$key});
221             }
222 0         0 delete $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue'};
223              
224 0         0 $queue = $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue_delete'};
225 0         0 foreach my $key (keys %$queue) {
226 0         0 $self->{'MemcacheDBI'}->{'memd'}->delete($key);
227             }
228 0         0 delete $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue_delete'};
229              
230 0         0 return 1;
231             }
232              
233             sub rollback {
234 0     0   0 my ($self) = @_;
235 0         0 delete $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue'};
236 0         0 delete $self->{'MemcacheDBI'}->{'dbh'}->{'MemcacheDBI'}->{'queue_delete'};
237              
238 0         0 return 1;
239             }
240              
241             package MemcacheDBI::Tie;
242              
243             # passes all calls to the parent $tie_type unless the key is MemcacheDBI
244             # allows me to wrap my data in a container while somewhat preseving the parents operation
245              
246             sub TIEHASH {
247 2     2   2 my $class = shift;
248 2   50     11 my $tie_type = shift || 'dbh'; # dbh or memd
249 2         7 return bless {MemcacheDBI=>{tie_type=>$tie_type}}, $class;
250             }
251              
252             sub FETCH {
253 0     0     my ($self,$key) = @_;
254 0           my $short = $self->{'MemcacheDBI'};
255 0 0         return $short if $key eq 'MemcacheDBI';
256 0           $short->{$short->{'tie_type'}}->{$key};
257             }
258              
259             sub STORE {
260 0     0     my ($self,$key,$value) = @_;
261 0           my $short = $self->{'MemcacheDBI'};
262 0           $short->{$short->{'tie_type'}}->{$key} = $value;
263             }
264              
265             sub DELETE {
266 0     0     my ($self,$key) = @_;
267 0 0         die 'Cannot delete MemcacheDBI'.do{my @c = caller; ' at '.$c[1].' line '.$c[2]."\n" } if $key eq 'MemcacheDBI';
  0            
  0            
268 0           my $short = $self->{'MemcacheDBI'};
269 0           delete $short->{$short->{'tie_type'}}->{$key};
270             }
271              
272             sub CLEAR {
273 0     0     my ($self) = @_;
274             }
275              
276             sub FIRSTKEY {
277 0     0     my ($self) = @_;
278 0           my $tmp = $self->{'MemcacheDBI'}->{$self->{'MemcacheDBI'}->{'tie_type'}};
279 0 0         return unless ref $tmp eq 'HASH';
280 0           keys %$tmp;
281 0           return scalar each %$tmp;
282             }
283              
284             sub NEXTKEY {
285 0     0     my ($self) = @_;
286 0           my $tmp = $self->{'MemcacheDBI'}->{$self->{'MemcacheDBI'}->{'tie_type'}};
287 0           return scalar each %$tmp;
288             }
289              
290             sub EXISTS {
291 0     0     my ($self,$key) = @_;
292 0           return exists $self->{'MemcacheDBI'}->{$self->{'MemcacheDBI'}->{'tie_type'}}->{$key};
293             }
294              
295             1;
296              
297             =head1 REPOSITORY
298              
299             The code is available on github:
300              
301             https://github.com/oaxlin/MemcacheDBI.git
302              
303             =head1 DISCLAIMER
304              
305             THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
306             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
307             MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
308