File Coverage

blib/lib/Class/DBI/Replicated.pm
Criterion Covered Total %
statement 30 161 18.6
branch 0 40 0.0
condition 0 21 0.0
subroutine 10 36 27.7
pod 16 16 100.0
total 56 274 20.4


line stmt bran cond sub pod time code
1             package Class::DBI::Replicated;
2              
3 1     1   24215 use warnings;
  1         2  
  1         33  
4 1     1   6 use strict;
  1         2  
  1         34  
5 1     1   955 use Class::Trigger;
  1         1981  
  1         7  
6 1     1   884 use Sub::Install qw(install_sub);
  1         1776  
  1         6  
7 1     1   118 use base qw(Class::Accessor::Class Class::Data::Inheritable);
  1         2  
  1         1201  
8 1     1   8058 use Params::Validate qw(:all);
  1         11561  
  1         256  
9 1     1   2156 use Devel::Peek qw(CvGV);
  1         940  
  1         7  
10 1     1   103 use Carp qw(croak);
  1         2  
  1         1278  
11              
12             =head1 NAME
13              
14             Class::DBI::Replicated - Replication from single master to multiple slaves
15              
16             =head1 VERSION
17              
18             Version 0.040
19              
20             =cut
21              
22             our $VERSION = '0.040';
23              
24             =head1 SYNOPSIS
25              
26             package My::DBI;
27             use base qw(Class::DBI::Replicated::mysql);
28             # use base qw(Class::DBI::Replicated::Pg::Slony1);
29              
30             My::DBI->replication(\%arg);
31              
32             =head1 DESCRIPTION
33              
34             Class::DBI::Replicated does some stuff, blah blah.
35              
36             =head1 METHODS
37              
38             =head2 C<< replication >>
39              
40             $class->replication(\%arg);
41              
42             Analogous to C<< connection >>. Takes a single hashref.
43              
44             =over 4
45              
46             =item B
47              
48             a single arrayref (as passed to C<< connection >>)
49              
50             =item B
51              
52             an arrayref of arrayrefs, one per slave. NOTE: currently,
53             using more than one slave does nothing.
54              
55             =item B
56              
57             =item B
58              
59             If present, these specify the user and password to use for
60             replication-specific queries (such as MySQL's C
61             STATUS>).
62              
63             =back
64              
65             =head2 C<< replication_db >>
66              
67             Returns the name of the current database in use (minus the
68             leading C<< db_ >>).
69              
70             =cut
71              
72             # we also track the most recent slave db name
73             sub replication_db {
74 0     0 1 0 my $class = shift;
75 0 0       0 return $class->__replication_db unless @_;
76 0         0 my ($db_name) = @_;
77 0 0       0 if ($db_name =~ /^Slave_/) {
78 0         0 $class->__slave_db($db_name);
79             }
80 0         0 $class->__replication_db($db_name);
81             }
82              
83             __PACKAGE__->mk_classdata('__replication_std_triggers');
84             __PACKAGE__->mk_classdata('__force_master');
85              
86             sub replication {
87 0     0 1 0 my $class = shift;
88 0         0 my $arg = { validate_with(
89 0         0 params => [ %{ +shift } ],
90             spec => {
91             master => { type => ARRAYREF },
92             slaves => { type => ARRAYREF },
93             user => { type => SCALAR, optional => 1,
94             depends => ['password'],
95             },
96             password => { type => SCALAR, optional => 1,
97             depends => ['user'],
98             },
99             $class->replication_args,
100             }
101             ) };
102              
103 0         0 $class->mk_class_accessors(
104             '__slave_names',
105             '__slave_db',
106             '__replication_db',
107             '__repl_user',
108             '__repl_pass',
109             '__replication_setup',
110             'repl_pos',
111             );
112            
113 0 0       0 if ($arg->{user}) {
114 0         0 $class->__repl_user($arg->{user});
115 0         0 $class->__repl_pass($arg->{password});
116             }
117              
118 0         0 $class->__add_std_triggers;
119 0         0 $class->replication_setup($arg);
120              
121 0         0 my @slaves = @{$arg->{slaves}};
  0         0  
122              
123 0 0 0     0 if (!@slaves or @slaves % 2) {
124 0         0 croak "list of slaves must be name => dsn pairs\n";
125             }
126 0         0 $class->Ima::DBI::set_db('Master' => @{$arg->{master}});
  0         0  
127 0 0       0 if ($arg->{user}) {
128 0         0 $class->Ima::DBI::set_db(
129             'Master_Repl',
130             $arg->{master}->[0],
131             $arg->{user},
132             $arg->{password},
133             );
134             }
135 0         0 my @names;
136 0         0 while (my ($name, $dsn) = splice @slaves, 0, 2) {
137 0         0 push @names, $name;
138 0 0       0 my $slave_arg = ref $dsn eq 'HASH' ? $dsn : { dsn => $dsn };
139 0         0 $class->Ima::DBI::set_db("Slave_$name" => @{ $slave_arg->{dsn} });
  0         0  
140 0 0       0 if ($arg->{user}) {
141 0         0 $class->Ima::DBI::set_db(
142             "Slave_$name\_Repl",
143             $slave_arg->{dsn}->[0],
144             $arg->{user},
145             $arg->{password},
146             );
147             }
148 0 0       0 unless ($class->replication_db) {
149 0         0 $class->replication_db("Slave_$name");
150             }
151             }
152 0         0 $class->__slave_names([ @names ]);
153             }
154              
155             =head2 C<< db_Main >>
156              
157             Return a master or slave DBH, as dictated by the current
158             replication state.
159              
160             =cut
161              
162             sub db_Main {
163 0     0 1 0 my ($class) = @_;
164 0 0       0 my $db_name = $class->__force_master ?
165             'db_Master' : 'db_' . $class->replication_db;
166 0         0 $class->call_trigger(
167             'repl_db',
168             $db_name,
169             );
170 0         0 return $class->$db_name;
171             }
172              
173             =head2 C<< db_Slave >>
174              
175             Always returns a DBH for the most recently-used slave.
176              
177             =cut
178              
179             sub db_Slave {
180 0     0 1 0 my ($class) = @_;
181 0         0 my $db_name = 'db_' . $class->__slave_db;
182 0         0 return $class->$db_name;
183             }
184              
185             =head2 C<< db_Master >>
186              
187             Generated by Class::DBI.
188              
189             =head2 C<< db_Slave_Repl >>
190              
191             Most recently-used slave's connection for replication.
192              
193             Falls back to db_Slave if no user/password given
194              
195             =head2 C<< db_Master_Repl >>
196              
197             Master's connection for replication.
198              
199             Falls back to db_Master if no user/password given
200              
201             =cut
202              
203             sub db_Slave_Repl {
204 0     0 1 0 my ($class) = @_;
205 0 0       0 return $class->db_Slave unless $class->__repl_user;
206 0         0 my $db_name = 'db_' . $class->__slave_db . '_Repl';
207 0         0 return $class->$db_name;
208             }
209              
210             # any auto-generated db_Master_Repl will override this
211              
212             sub db_Master_Repl {
213 0     0 1 0 my ($class) = @_;
214 0         0 return $class->db_Master;
215             }
216              
217             =head2 C<< switch_to_master >>
218              
219             =cut
220              
221             sub switch_to_master {
222 0     0 1 0 my ($class) = @_;
223 0 0       0 return if $class->replication_db eq 'Master';
224 0         0 $class->replication_db('Master');
225 0         0 $class->call_trigger('switch_to_master');
226             }
227              
228             =head2 C<< switch_to_slave >>
229              
230             =cut
231              
232             sub __default_slave {
233 0     0   0 my ($class) = @_;
234 0         0 return $class->__slave_names->[0];
235             }
236              
237             sub switch_to_slave {
238 0     0 1 0 my ($class, $name) = @_;
239 0   0     0 $name ||= $class->__default_slave;
240 0         0 $class->replication_db("Slave_$name");
241 0         0 $class->call_trigger('switch_to_slave', $name);
242             }
243              
244             =head2 C<< wait_for_slave >>
245              
246             =cut
247              
248             sub wait_for_slave {
249 0     0 1 0 my ($class, $name) = @_;
250 0         0 my $ok = eval {
251 0         0 $class->repl_wait({
252             slave => $name
253             });
254             };
255 0         0 my $err = $@;
256 0 0       0 die $err if $err;
257 0         0 return $ok;
258             }
259              
260             =head1 REPLICATION METHODS
261              
262             That is, methods dealing specifically with replication
263             positions.
264              
265             =head2 C<< repl_mark >>
266              
267             Get current master position and save it
268              
269             =head2 C<< repl_pos >>
270              
271             Class data accessor/mutator for current marked master position
272              
273             =head2 C<< repl_get_master >>
274              
275             virtual (scalar)
276              
277             =head2 C<< repl_get_slave >>
278              
279             virtual (scalar)
280              
281             =head2 C<< repl_check >>
282              
283             if ($class->repl_check) { ...
284              
285             =head2 C<< repl_wait >>
286              
287             unless ($class->repl_wait(\%arg)) {
288             # not up to date
289             }
290              
291             Possible arguments:
292              
293             =over 4
294              
295             =item B
296              
297             defaults to 30
298              
299             =item B
300              
301             slave name, defaults to the first one
302              
303             =item B
304              
305             die instead of returning 0
306              
307             =back
308              
309             return 0 for failure
310              
311             =head2 C<< repl_compare >>
312              
313             my $later = $class->repl_compare($my_pos, $master_pos);
314              
315             virtual (boolean)
316              
317             return 1 if $my_pos is at least as new as $master_pos
318             return 0 otherwise
319              
320             =cut
321              
322             sub _mk_unimplemented {
323 3     3   4 my ($class, $meth) = @_;
324 1     1   9 no strict 'refs';
  1         2  
  1         1347  
325 3         18 *{$class . "::" . $meth} = sub {
326 0     0     croak "$class does not implement $meth"
327 3         10 };
328             }
329              
330             sub repl_mark {
331 0     0 1   my ($class) = @_;
332 0           $class->call_trigger(
333             'repl_mark',
334             );
335 0           my $pos = $class->repl_get_master;
336 0           $class->repl_pos($pos);
337             }
338              
339             sub repl_wait {
340 0     0 1   my ($class, $arg) = @_;
341 0 0         $arg->{timeout} = 30 unless defined $arg->{timeout};
342 0   0       $arg->{slave} ||= $class->__default_slave;
343 0           my $tries = 0;
344 0           my $done = $class->repl_check($arg);
345 0   0       while ($tries < $arg->{timeout} and not $done) {
346 0           sleep 1;
347 0           $done = $class->repl_check($arg);
348             }
349 0 0 0       if ($tries >= $arg->{timeout} and not $done) {
350 0 0         die "$arg->{slave} is not up to date after $arg->{timeout} seconds"
351             if $arg->{fatal};
352 0           return 0;
353             }
354 0 0         unless ($class->replication_db eq "Slave_$arg->{slave}") {
355 0           $class->switch_to_slave($arg->{slave});
356             }
357 0           return 1;
358             }
359              
360             sub repl_check {
361 0     0 1   my ($class, $arg) = @_;
362 0   0       $arg->{slave} ||= $class->__default_slave;
363 0 0         return 1 unless defined $class->repl_pos;
364              
365 0           $class->call_trigger(
366             'repl_check',
367             );
368              
369 0     0     my $get = sub { $class->repl_get_slave };
  0            
370 0           my $slave_pos = do {
371 0 0         if ($arg->{slave} eq $class->__slave_db) {
372 0           $get->();
373             } else {
374 0           my $old = $class->__slave_db;
375 0           $class->__slave_db("Slave_" . $arg->{slave});
376 0           my $return = $get->();
377 0           $class->__slave_db($old);
378 0           $return;
379             }
380             };
381 0 0 0       if ($slave_pos && $class->repl_compare($slave_pos, $class->repl_pos)) {
382 0           $class->repl_pos(undef);
383 0           return 1;
384             }
385              
386 0           return 0;
387             }
388              
389             for (qw(
390             repl_get_master
391             repl_get_slave
392             repl_compare
393             )) {
394             __PACKAGE__->_mk_unimplemented($_);
395             }
396              
397              
398             =head1 TRIGGERS
399              
400             =head2 C<< before_create >>
401              
402             =head2 C<< before_update >>
403              
404             =head2 C<< before_delete >>
405              
406             switch to using master
407              
408             =head2 C<< after_create >>
409              
410             =head2 C<< after_update >>
411              
412             =head2 C<< after_delete >>
413              
414             mark master position
415              
416             =head2 C<< select >>
417              
418             =cut
419              
420             sub _mark {
421 0     0     shift->repl_mark;
422             }
423              
424             sub _check {
425             shift->repl_wait({
426 0     0     timeout => 0,
427             });
428             }
429              
430             # this exists only because you can't take a reference to a
431             # method and still let inheritance have a chance, unlike the
432             # two subs above which actually add functionality
433             sub __master {
434 0     0     shift->switch_to_master;
435             }
436              
437             sub __add_std_triggers {
438 0     0     my ($class) = @_;
439 0 0         return if $class->__replication_std_triggers;
440 0           $class->__real_add_std_triggers;
441 0           $class->__replication_std_triggers(1);
442             }
443              
444             sub __real_add_std_triggers {
445 0     0     my ($class) = @_;
446 0           $class->add_trigger(
447             before_create => \&__master,
448             before_update => \&__master,
449             before_delete => \&__master,
450             after_create => \&_mark,
451             after_update => \&_mark,
452             after_delete => \&_mark,
453             select => \&_check,
454             );
455             }
456              
457             =head1 SUBCLASSING
458              
459             =head2 C<< mk_force_masters >>
460              
461             =cut
462              
463             sub mk_force_masters {
464 0     0 1   my $class = shift;
465 0           for my $meth (@_) {
466             # XXX this is very disrespectful.
467 0           my $oldcode = $class->can($meth);
468             install_sub({
469             code => sub {
470 0     0     my $class = shift;
471 0           my $old = $class->__force_master;
472 0           $class->__force_master(1);
473 0           my $r = $class->$oldcode(@_);
474 0           $class->__force_master($old);
475 0           return $r;
476             },
477 0           into => $class,
478             as => $meth,
479             });
480             }
481             }
482              
483             # XXX fix duplication here
484              
485             =head2 C<< mk_markers >>
486              
487             =cut
488              
489             sub mk_markers {
490 0     0 1   my $class = shift;
491 0           for my $meth (@_) {
492             # XXX this is very disrespectful.
493 0           my $oldcode = $class->can($meth);
494             install_sub({
495             code => sub {
496 0     0     my $class = shift;
497 0           $class->switch_to_master;
498 0           my $r = $class->$oldcode(@_);
499 0           $class->repl_mark;
500 0           $class->repl_wait({ timeout => 0 });
501 0           return $r;
502             },
503 0           into => $class,
504             as => $meth,
505             });
506             }
507             }
508              
509             =head2 C<< replication_args >>
510              
511             Extra Params::Validate specifications for C<< replication >>.
512              
513             =cut
514              
515 0     0 1   sub replication_args { () }
516              
517             =head2 C<< replication_setup >>
518              
519             Called automatically inside C<< replication >>.
520              
521             Gets a hashref of the arguments to C<< replication >>.
522              
523             =cut
524              
525 0     0 1   sub replication_setup { () }
526              
527             =head1 AUTHOR
528              
529             Hans Dieter Pearcey, C<< >>
530              
531             =head1 BUGS
532              
533             Please report any bugs or feature requests to
534             C, or through the web interface at
535             L.
536             I will be notified, and then you'll automatically be notified of progress on
537             your bug as I make changes.
538              
539             =head1 ACKNOWLEDGEMENTS
540              
541             =head1 COPYRIGHT & LICENSE
542              
543             Copyright 2005 Hans Dieter Pearcey, all rights reserved.
544              
545             This program is free software; you can redistribute it and/or modify it
546             under the same terms as Perl itself.
547              
548             =cut
549              
550             1; # End of Class::DBI::Replicated