fixed up the relicant status tests to exclude them if the database is not a real...
[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     /],
122 );
123
124 =head2 balancer
125
126 Is a <DBIx::Class::Storage::DBI::Replicated::Balancer> or derived class.  This 
127 is a class that takes a pool (<DBIx::Class::Storage::DBI::Replicated::Pool>)
128
129 =cut
130
131 has 'balancer' => (
132     is=>'ro',
133     isa=>'DBIx::Class::Storage::DBI::Replicated::Balancer',
134     lazy_build=>1,
135 );
136
137 =head2 master
138
139 The master defines the canonical state for a pool of connected databases.  All
140 the replicants are expected to match this databases state.  Thus, in a classic
141 Master / Slaves distributed system, all the slaves are expected to replicate
142 the Master's state as quick as possible.  This is the only database in the
143 pool of databases that is allowed to handle write traffic.
144
145 =cut
146
147 has 'master' => (
148     is=> 'ro',
149     isa=>'DBIx::Class::Storage::DBI',
150     lazy_build=>1,
151 );
152
153 =head1 ATTRIBUTES IMPLEMENTING THE DBIx::Storage::DBI INTERFACE
154
155 The following methods are delegated all the methods required for the 
156 L<DBIx::Class::Storage::DBI> interface.
157
158 =head2 read_handler
159
160 Defines an object that implements the read side of L<BIx::Class::Storage::DBI>.
161
162 =cut
163
164 has 'read_handler' => (
165     is=>'rw',
166     isa=>'Object',
167     lazy_build=>1,
168     handles=>[qw/
169         select
170         select_single
171         columns_info_for
172     /],    
173 );
174
175 =head2 write_handler
176
177 Defines an object that implements the write side of L<BIx::Class::Storage::DBI>.
178
179 =cut
180
181 has 'write_handler' => (
182     is=>'ro',
183     isa=>'Object',
184     lazy_build=>1,
185     lazy_build=>1,
186     handles=>[qw/   
187         on_connect_do
188         on_disconnect_do       
189         connect_info
190         throw_exception
191         sql_maker
192         sqlt_type
193         create_ddl_dir
194         deployment_statements
195         datetime_parser
196         datetime_parser_type        
197         last_insert_id
198         insert
199         insert_bulk
200         update
201         delete
202         dbh
203         txn_do
204         txn_commit
205         txn_rollback
206         sth
207         deploy
208         schema
209     /],
210 );
211
212 =head1 METHODS
213
214 This class defines the following methods.
215
216 =head2 new
217
218 L<DBIx::Class::Schema> when instantiating it's storage passed itself as the
219 first argument.  We need to invoke L</new> on the underlying parent class, make
220 sure we properly give it a L<Moose> meta class, and then correctly instantiate
221 our attributes.  Basically we pass on whatever the schema has in it's class
222 data for 'storage_type_args' to our replicated storage type.
223
224 =cut
225
226 sub new {
227     my $class = shift @_;
228     my $schema = shift @_;
229     my $storage_type_args = shift @_;
230     my $obj = $class->SUPER::new($schema, $storage_type_args, @_);
231     
232     ## Hate to do it this way, but can't seem to get advice on the attribute working right
233     ## maybe we can do a type and coercion for it. 
234     if( $storage_type_args->{balancer_type} && $storage_type_args->{balancer_type}=~m/^::/) {
235         $storage_type_args->{balancer_type} = 'DBIx::Class::Storage::DBI::Replicated::Balancer'.$storage_type_args->{balancer_type};
236         eval "require $storage_type_args->{balancer_type}";
237     }
238     
239     return $class->meta->new_object(
240         __INSTANCE__ => $obj,
241         %$storage_type_args,
242         @_,
243     );
244 }
245
246 =head2 _build_master
247
248 Lazy builder for the L</master> attribute.
249
250 =cut
251
252 sub _build_master {
253         DBIx::Class::Storage::DBI->new;
254 }
255
256 =head2 _build_pool_type
257
258 Lazy builder for the L</pool_type> attribute.
259
260 =cut
261
262 sub _build_pool_type {
263     return 'DBIx::Class::Storage::DBI::Replicated::Pool';
264 }
265
266 =head2 _build_pool
267
268 Lazy builder for the L</pool> attribute.
269
270 =cut
271
272 sub _build_pool {
273         my $self = shift @_;
274     $self->create_pool($self->pool_args);
275 }
276
277 =head2 _build_balancer_type
278
279 Lazy builder for the L</balancer_type> attribute.
280
281 =cut
282
283 sub _build_balancer_type {
284     return 'DBIx::Class::Storage::DBI::Replicated::Balancer';
285 }
286
287 =head2 _build_balancer
288
289 Lazy builder for the L</balancer> attribute.  This takes a Pool object so that
290 the balancer knows which pool it's balancing.
291
292 =cut
293
294 sub _build_balancer {
295     my $self = shift @_;
296     $self->create_balancer(
297         pool=>$self->pool, 
298         master=>$self->master);
299 }
300
301 =head2 _build_write_handler
302
303 Lazy builder for the L</write_handler> attribute.  The default is to set this to
304 the L</master>.
305
306 =cut
307
308 sub _build_write_handler {
309     return shift->master;
310 }
311
312 =head2 _build_read_handler
313
314 Lazy builder for the L</read_handler> attribute.  The default is to set this to
315 the L</balancer>.
316
317 =cut
318
319 sub _build_read_handler {
320     return shift->balancer;
321 }
322
323 =head2 around: connect_replicants
324
325 All calls to connect_replicants needs to have an existing $schema tacked onto
326 top of the args, since L<DBIx::Storage::DBI> needs it.
327
328 =cut
329
330 around 'connect_replicants' => sub {
331         my ($method, $self, @args) = @_;
332         $self->$method($self->schema, @args);
333 };
334
335 =head2 all_storages
336
337 Returns an array of of all the connected storage backends.  The first element
338 in the returned array is the master, and the remainings are each of the
339 replicants.
340
341 =cut
342
343 sub all_storages {
344         my $self = shift @_;
345         
346         return grep {defined $_ && blessed $_} (
347            $self->master,
348            $self->replicants,
349         );
350 }
351
352 =head2 set_reliable_storage
353
354 Sets the current $schema to be 'reliable', that is all queries, both read and
355 write are sent to the master
356     
357 =cut
358
359 sub set_reliable_storage {
360         my $self = shift @_;
361         my $schema = $self->schema;
362         my $write_handler = $self->schema->storage->write_handler;
363         
364         $schema->storage->read_handler($write_handler);
365 }
366
367 =head2 set_balanced_storage
368
369 Sets the current $schema to be use the </balancer> for all reads, while all
370 writea are sent to the master only
371     
372 =cut
373
374 sub set_balanced_storage {
375     my $self = shift @_;
376     my $schema = $self->schema;
377     my $write_handler = $self->schema->storage->balancer;
378     
379     $schema->storage->read_handler($write_handler);
380 }
381
382 =head2 connected
383
384 Check that the master and at least one of the replicants is connected.
385
386 =cut
387
388 sub connected {
389         my $self = shift @_;
390         
391         return
392            $self->master->connected &&
393            $self->pool->connected_replicants;
394 }
395
396 =head2 ensure_connected
397
398 Make sure all the storages are connected.
399
400 =cut
401
402 sub ensure_connected {
403     my $self = shift @_;
404     foreach my $source ($self->all_storages) {
405         $source->ensure_connected(@_);
406     }
407 }
408
409 =head2 limit_dialect
410
411 Set the limit_dialect for all existing storages
412
413 =cut
414
415 sub limit_dialect {
416     my $self = shift @_;
417     foreach my $source ($self->all_storages) {
418         $source->limit_dialect(@_);
419     }
420 }
421
422 =head2 quote_char
423
424 Set the quote_char for all existing storages
425
426 =cut
427
428 sub quote_char {
429     my $self = shift @_;
430     foreach my $source ($self->all_storages) {
431         $source->quote_char(@_);
432     }
433 }
434
435 =head2 name_sep
436
437 Set the name_sep for all existing storages
438
439 =cut
440
441 sub name_sep {
442     my $self = shift @_;
443     foreach my $source ($self->all_storages) {
444         $source->name_sep(@_);
445     }
446 }
447
448 =head2 set_schema
449
450 Set the schema object for all existing storages
451
452 =cut
453
454 sub set_schema {
455         my $self = shift @_;
456         foreach my $source ($self->all_storages) {
457                 $source->set_schema(@_);
458         }
459 }
460
461 =head2 debug
462
463 set a debug flag across all storages
464
465 =cut
466
467 sub debug {
468     my $self = shift @_;
469     foreach my $source ($self->all_storages) {
470         $source->debug(@_);
471     }
472 }
473
474 =head2 debugobj
475
476 set a debug object across all storages
477
478 =cut
479
480 sub debugobj {
481     my $self = shift @_;
482     foreach my $source ($self->all_storages) {
483         $source->debugobj(@_);
484     }
485 }
486
487 =head2 debugfh
488
489 set a debugfh object across all storages
490
491 =cut
492
493 sub debugfh {
494     my $self = shift @_;
495     foreach my $source ($self->all_storages) {
496         $source->debugfh(@_);
497     }
498 }
499
500 =head2 debugcb
501
502 set a debug callback across all storages
503
504 =cut
505
506 sub debugcb {
507     my $self = shift @_;
508     foreach my $source ($self->all_storages) {
509         $source->debugcb(@_);
510     }
511 }
512
513 =head2 disconnect
514
515 disconnect everything
516
517 =cut
518
519 sub disconnect {
520     my $self = shift @_;
521     foreach my $source ($self->all_storages) {
522         $source->disconnect(@_);
523     }
524 }
525
526 =head1 AUTHOR
527
528 Norbert Csongrádi <bert@cpan.org>
529
530 Peter Siklósi <einon@einon.hu>
531
532 John Napiorkowski <john.napiorkowski@takkle.com>
533
534 =head1 LICENSE
535
536 You may distribute this code under the same terms as Perl itself.
537
538 =cut
539
540 1;