7323b42c9475c2d3dae9da894121028d11055125
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Replicated.pm
1 package DBIx::Class::Storage::DBI::Replicated;
2
3 use Moose;
4 use DBIx::Class::Storage::DBI;
5 use DBIx::Class::Storage::DBI::Replicated::Pool;
6 use DBIx::Class::Storage::DBI::Replicated::Balancer;
7 use Scalar::Util qw(blessed);
8
9 extends 'DBIx::Class::Storage::DBI', 'Moose::Object';
10
11 =head1 NAME
12
13 DBIx::Class::Storage::DBI::Replicated - ALPHA Replicated database support
14
15 =head1 SYNOPSIS
16
17 The Following example shows how to change an existing $schema to a replicated
18 storage type, add some replicated (readonly) databases, and perform reporting
19 tasks.
20
21     ## Change storage_type in your schema class
22     $schema->storage_type( ['::DBI::Replicated', {balancer=>'::Random'}] );
23     
24     ## Add some slaves.  Basically this is an array of arrayrefs, where each
25     ## arrayref is database connect information
26     
27     $schema->storage->connect_replicants(
28         [$dsn1, $user, $pass, \%opts],
29         [$dsn1, $user, $pass, \%opts],
30         [$dsn1, $user, $pass, \%opts],
31     );
32     
33 =head1 DESCRIPTION
34
35 Warning: This class is marked ALPHA.  We are using this in development and have
36 some basic test coverage but the code hasn't yet been stressed by a variety
37 of databases.  Individual DB's may have quirks we are not aware of.  Please
38 use this in development and pass along your experiences/bug fixes.
39
40 This class implements replicated data store for DBI. Currently you can define
41 one master and numerous slave database connections. All write-type queries
42 (INSERT, UPDATE, DELETE and even LAST_INSERT_ID) are routed to master
43 database, all read-type queries (SELECTs) go to the slave database.
44
45 Basically, any method request that L<DBIx::Class::Storage::DBI> would normally
46 handle gets delegated to one of the two attributes: L</master_storage> or to
47 L</current_replicant_storage>.  Additionally, some methods need to be distributed
48 to all existing storages.  This way our storage class is a drop in replacement
49 for L<DBIx::Class::Storage::DBI>.
50
51 Read traffic is spread across the replicants (slaves) occuring to a user
52 selected algorithm.  The default algorithm is random weighted.
53
54 TODO more details about the algorithm.
55
56 =head1 ATTRIBUTES
57
58 This class defines the following attributes.
59
60 =head2 pool_type
61
62 Contains the classname which will instantiate the L</pool> object.  Defaults 
63 to: L<DBIx::Class::Storage::DBI::Replicated::Pool>.
64
65 =cut
66
67 has 'pool_type' => (
68     is=>'ro',
69     isa=>'ClassName',
70     lazy_build=>1,
71     handles=>{
72         'create_pool' => 'new',
73     },
74 );
75
76 =head2 pool_args
77
78 Contains a hashref of initialized information to pass to the Pool object.  See
79 L<DBIx::Class::Storage::Replicated::Pool> for available arguments.
80
81 =cut
82
83 has 'pool_args' => (
84     is=>'ro',
85     isa=>'HashRef',
86 );
87
88 =head2 balancer_type
89
90 The replication pool requires a balance class to provider the methods for
91 choose how to spread the query load across each replicant in the pool.
92
93 =cut
94
95 has 'balancer_type' => (
96     is=>'ro',
97     isa=>'ClassName',
98     lazy_build=>1,
99     handles=>{
100         'create_balancer' => 'new',
101     },
102 );
103
104 =head2 pool
105
106 Is a <DBIx::Class::Storage::DBI::Replicated::Pool> or derived class.  This is a
107 container class for one or more replicated databases.
108
109 =cut
110
111 has 'pool' => (
112     is=>'ro',
113     isa=>'DBIx::Class::Storage::DBI::Replicated::Pool',
114     lazy_build=>1,
115     handles=>[qw/
116         connect_replicants    
117         replicants
118         has_replicants
119         num_replicants
120         delete_replicant
121         validate_replicants
122     /],
123 );
124
125 =head2 balancer
126
127 Is a <DBIx::Class::Storage::DBI::Replicated::Balancer> or derived class.  This 
128 is a class that takes a pool (<DBIx::Class::Storage::DBI::Replicated::Pool>)
129
130 =cut
131
132 has 'balancer' => (
133     is=>'ro',
134     isa=>'DBIx::Class::Storage::DBI::Replicated::Balancer',
135     lazy_build=>1,
136 );
137
138 =head2 master
139
140 The master defines the canonical state for a pool of connected databases.  All
141 the replicants are expected to match this databases state.  Thus, in a classic
142 Master / Slaves distributed system, all the slaves are expected to replicate
143 the Master's state as quick as possible.  This is the only database in the
144 pool of databases that is allowed to handle write traffic.
145
146 =cut
147
148 has 'master' => (
149     is=> 'ro',
150     isa=>'DBIx::Class::Storage::DBI',
151     lazy_build=>1,
152 );
153
154 =head1 ATTRIBUTES IMPLEMENTING THE DBIx::Storage::DBI INTERFACE
155
156 The following methods are delegated all the methods required for the 
157 L<DBIx::Class::Storage::DBI> interface.
158
159 =head2 read_handler
160
161 Defines an object that implements the read side of L<BIx::Class::Storage::DBI>.
162
163 =cut
164
165 has 'read_handler' => (
166     is=>'rw',
167     isa=>'Object',
168     lazy_build=>1,
169     handles=>[qw/
170         select
171         select_single
172         columns_info_for
173     /],    
174 );
175
176 =head2 write_handler
177
178 Defines an object that implements the write side of L<BIx::Class::Storage::DBI>.
179
180 =cut
181
182 has 'write_handler' => (
183     is=>'ro',
184     isa=>'Object',
185     lazy_build=>1,
186     lazy_build=>1,
187     handles=>[qw/   
188         on_connect_do
189         on_disconnect_do       
190         connect_info
191         throw_exception
192         sql_maker
193         sqlt_type
194         create_ddl_dir
195         deployment_statements
196         datetime_parser
197         datetime_parser_type        
198         last_insert_id
199         insert
200         insert_bulk
201         update
202         delete
203         dbh
204         txn_do
205         txn_commit
206         txn_rollback
207         sth
208         deploy
209         schema
210     /],
211 );
212
213 =head1 METHODS
214
215 This class defines the following methods.
216
217 =head2 new
218
219 L<DBIx::Class::Schema> when instantiating it's storage passed itself as the
220 first argument.  We need to invoke L</new> on the underlying parent class, make
221 sure we properly give it a L<Moose> meta class, and then correctly instantiate
222 our attributes.  Basically we pass on whatever the schema has in it's class
223 data for 'storage_type_args' to our replicated storage type.
224
225 =cut
226
227 sub new {
228     my $class = shift @_;
229     my $schema = shift @_;
230     my $storage_type_args = shift @_;
231     my $obj = $class->SUPER::new($schema, $storage_type_args, @_);
232     
233     ## Hate to do it this way, but can't seem to get advice on the attribute working right
234     ## maybe we can do a type and coercion for it. 
235     if( $storage_type_args->{balancer_type} && $storage_type_args->{balancer_type}=~m/^::/) {
236         $storage_type_args->{balancer_type} = 'DBIx::Class::Storage::DBI::Replicated::Balancer'.$storage_type_args->{balancer_type};
237         eval "require $storage_type_args->{balancer_type}";
238     }
239     
240     return $class->meta->new_object(
241         __INSTANCE__ => $obj,
242         %$storage_type_args,
243         @_,
244     );
245 }
246
247 =head2 _build_master
248
249 Lazy builder for the L</master> attribute.
250
251 =cut
252
253 sub _build_master {
254         DBIx::Class::Storage::DBI->new;
255 }
256
257 =head2 _build_pool_type
258
259 Lazy builder for the L</pool_type> attribute.
260
261 =cut
262
263 sub _build_pool_type {
264     return 'DBIx::Class::Storage::DBI::Replicated::Pool';
265 }
266
267 =head2 _build_pool
268
269 Lazy builder for the L</pool> attribute.
270
271 =cut
272
273 sub _build_pool {
274         my $self = shift @_;
275     $self->create_pool($self->pool_args);
276 }
277
278 =head2 _build_balancer_type
279
280 Lazy builder for the L</balancer_type> attribute.
281
282 =cut
283
284 sub _build_balancer_type {
285     return 'DBIx::Class::Storage::DBI::Replicated::Balancer';
286 }
287
288 =head2 _build_balancer
289
290 Lazy builder for the L</balancer> attribute.  This takes a Pool object so that
291 the balancer knows which pool it's balancing.
292
293 =cut
294
295 sub _build_balancer {
296     my $self = shift @_;
297     $self->create_balancer(
298         pool=>$self->pool, 
299         master=>$self->master);
300 }
301
302 =head2 _build_write_handler
303
304 Lazy builder for the L</write_handler> attribute.  The default is to set this to
305 the L</master>.
306
307 =cut
308
309 sub _build_write_handler {
310     return shift->master;
311 }
312
313 =head2 _build_read_handler
314
315 Lazy builder for the L</read_handler> attribute.  The default is to set this to
316 the L</balancer>.
317
318 =cut
319
320 sub _build_read_handler {
321     return shift->balancer;
322 }
323
324 =head2 around: connect_replicants
325
326 All calls to connect_replicants needs to have an existing $schema tacked onto
327 top of the args, since L<DBIx::Storage::DBI> needs it.
328
329 =cut
330
331 around 'connect_replicants' => sub {
332         my ($method, $self, @args) = @_;
333         $self->$method($self->schema, @args);
334 };
335
336 =head2 all_storages
337
338 Returns an array of of all the connected storage backends.  The first element
339 in the returned array is the master, and the remainings are each of the
340 replicants.
341
342 =cut
343
344 sub all_storages {
345         my $self = shift @_;
346         
347         return grep {defined $_ && blessed $_} (
348            $self->master,
349            $self->replicants,
350         );
351 }
352
353 =head2 set_reliable_storage
354
355 Sets the current $schema to be 'reliable', that is all queries, both read and
356 write are sent to the master
357     
358 =cut
359
360 sub set_reliable_storage {
361         my $self = shift @_;
362         my $schema = $self->schema;
363         my $write_handler = $self->schema->storage->write_handler;
364         
365         $schema->storage->read_handler($write_handler);
366 }
367
368 =head2 set_balanced_storage
369
370 Sets the current $schema to be use the </balancer> for all reads, while all
371 writea are sent to the master only
372     
373 =cut
374
375 sub set_balanced_storage {
376     my $self = shift @_;
377     my $schema = $self->schema;
378     my $write_handler = $self->schema->storage->balancer;
379     
380     $schema->storage->read_handler($write_handler);
381 }
382
383 =head2 connected
384
385 Check that the master and at least one of the replicants is connected.
386
387 =cut
388
389 sub connected {
390         my $self = shift @_;
391         
392         return
393            $self->master->connected &&
394            $self->pool->connected_replicants;
395 }
396
397 =head2 ensure_connected
398
399 Make sure all the storages are connected.
400
401 =cut
402
403 sub ensure_connected {
404     my $self = shift @_;
405     foreach my $source ($self->all_storages) {
406         $source->ensure_connected(@_);
407     }
408 }
409
410 =head2 limit_dialect
411
412 Set the limit_dialect for all existing storages
413
414 =cut
415
416 sub limit_dialect {
417     my $self = shift @_;
418     foreach my $source ($self->all_storages) {
419         $source->limit_dialect(@_);
420     }
421 }
422
423 =head2 quote_char
424
425 Set the quote_char for all existing storages
426
427 =cut
428
429 sub quote_char {
430     my $self = shift @_;
431     foreach my $source ($self->all_storages) {
432         $source->quote_char(@_);
433     }
434 }
435
436 =head2 name_sep
437
438 Set the name_sep for all existing storages
439
440 =cut
441
442 sub name_sep {
443     my $self = shift @_;
444     foreach my $source ($self->all_storages) {
445         $source->name_sep(@_);
446     }
447 }
448
449 =head2 set_schema
450
451 Set the schema object for all existing storages
452
453 =cut
454
455 sub set_schema {
456         my $self = shift @_;
457         foreach my $source ($self->all_storages) {
458                 $source->set_schema(@_);
459         }
460 }
461
462 =head2 debug
463
464 set a debug flag across all storages
465
466 =cut
467
468 sub debug {
469     my $self = shift @_;
470     foreach my $source ($self->all_storages) {
471         $source->debug(@_);
472     }
473 }
474
475 =head2 debugobj
476
477 set a debug object across all storages
478
479 =cut
480
481 sub debugobj {
482     my $self = shift @_;
483     foreach my $source ($self->all_storages) {
484         $source->debugobj(@_);
485     }
486 }
487
488 =head2 debugfh
489
490 set a debugfh object across all storages
491
492 =cut
493
494 sub debugfh {
495     my $self = shift @_;
496     foreach my $source ($self->all_storages) {
497         $source->debugfh(@_);
498     }
499 }
500
501 =head2 debugcb
502
503 set a debug callback across all storages
504
505 =cut
506
507 sub debugcb {
508     my $self = shift @_;
509     foreach my $source ($self->all_storages) {
510         $source->debugcb(@_);
511     }
512 }
513
514 =head2 disconnect
515
516 disconnect everything
517
518 =cut
519
520 sub disconnect {
521     my $self = shift @_;
522     foreach my $source ($self->all_storages) {
523         $source->disconnect(@_);
524     }
525 }
526
527 =head1 AUTHOR
528
529 Norbert Csongrádi <bert@cpan.org>
530
531 Peter Siklósi <einon@einon.hu>
532
533 John Napiorkowski <john.napiorkowski@takkle.com>
534
535 =head1 LICENSE
536
537 You may distribute this code under the same terms as Perl itself.
538
539 =cut
540
541 1;