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