changed the way args are passed to a storage type that needs args so they can be...
[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 balancer_args
93
94 Contains a hashref of initialized information to pass to the Balancer object.
95 See L<DBIx::Class::Storage::Replicated::Pool> for available arguments.
96
97 =cut
98
99 has 'balancer_args' => (
100     is=>'ro',
101     isa=>'HashRef',
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     /],
120 );
121
122 =head2 balancer
123
124 Is a <DBIx::Class::Storage::DBI::Replicated::Balancer> or derived class.  This 
125 is a class that takes a pool (<DBIx::Class::Storage::DBI::Replicated::Pool>)
126
127 =cut
128
129 has 'balancer' => (
130     is=>'ro',
131     isa=>'DBIx::Class::Storage::DBI::Replicated::Balancer',
132     lazy_build=>1,
133     handles=>[qw/auto_validate_every/],
134 );
135
136 =head2 master
137
138 The master defines the canonical state for a pool of connected databases.  All
139 the replicants are expected to match this databases state.  Thus, in a classic
140 Master / Slaves distributed system, all the slaves are expected to replicate
141 the Master's state as quick as possible.  This is the only database in the
142 pool of databases that is allowed to handle write traffic.
143
144 =cut
145
146 has 'master' => (
147     is=> 'ro',
148     isa=>'DBIx::Class::Storage::DBI',
149     lazy_build=>1,
150 );
151
152 =head1 ATTRIBUTES IMPLEMENTING THE DBIx::Storage::DBI INTERFACE
153
154 The following methods are delegated all the methods required for the 
155 L<DBIx::Class::Storage::DBI> interface.
156
157 =head2 read_handler
158
159 Defines an object that implements the read side of L<BIx::Class::Storage::DBI>.
160
161 =cut
162
163 has 'read_handler' => (
164     is=>'rw',
165     isa=>'Object',
166     lazy_build=>1,
167     handles=>[qw/
168         select
169         select_single
170         columns_info_for
171     /],    
172 );
173
174 =head2 write_handler
175
176 Defines an object that implements the write side of L<BIx::Class::Storage::DBI>.
177
178 =cut
179
180 has 'write_handler' => (
181     is=>'ro',
182     isa=>'Object',
183     lazy_build=>1,
184     lazy_build=>1,
185     handles=>[qw/   
186         on_connect_do
187         on_disconnect_do       
188         connect_info
189         throw_exception
190         sql_maker
191         sqlt_type
192         create_ddl_dir
193         deployment_statements
194         datetime_parser
195         datetime_parser_type        
196         last_insert_id
197         insert
198         insert_bulk
199         update
200         delete
201         dbh
202         txn_do
203         txn_commit
204         txn_rollback
205         sth
206         deploy
207         schema
208     /],
209 );
210
211 =head1 METHODS
212
213 This class defines the following methods.
214
215 =head2 new
216
217 L<DBIx::Class::Schema> when instantiating it's storage passed itself as the
218 first argument.  We need to invoke L</new> on the underlying parent class, make
219 sure we properly give it a L<Moose> meta class, and then correctly instantiate
220 our attributes.  Basically we pass on whatever the schema has in it's class
221 data for 'storage_type_args' to our replicated storage type.
222
223 =cut
224
225 sub new {
226     my $class = shift @_;
227     my $schema = shift @_;
228     my $storage_type_args = shift @_;
229     my $obj = $class->SUPER::new($schema, $storage_type_args, @_);
230     
231     ## Hate to do it this way, but can't seem to get advice on the attribute working right
232     ## maybe we can do a type and coercion for it. 
233     if( $storage_type_args->{balancer_type} && $storage_type_args->{balancer_type}=~m/^::/) {
234         $storage_type_args->{balancer_type} = 'DBIx::Class::Storage::DBI::Replicated::Balancer'.$storage_type_args->{balancer_type};
235         eval "require $storage_type_args->{balancer_type}";
236     }
237     
238     return $class->meta->new_object(
239         __INSTANCE__ => $obj,
240         %$storage_type_args,
241         @_,
242     );
243 }
244
245 =head2 _build_master
246
247 Lazy builder for the L</master> attribute.
248
249 =cut
250
251 sub _build_master {
252         DBIx::Class::Storage::DBI->new;
253 }
254
255 =head2 _build_pool_type
256
257 Lazy builder for the L</pool_type> attribute.
258
259 =cut
260
261 sub _build_pool_type {
262     return 'DBIx::Class::Storage::DBI::Replicated::Pool';
263 }
264
265 =head2 _build_pool
266
267 Lazy builder for the L</pool> attribute.
268
269 =cut
270
271 sub _build_pool {
272         my $self = shift @_;
273     $self->create_pool;
274 }
275
276 =head2 _build_balancer_type
277
278 Lazy builder for the L</balancer_type> attribute.
279
280 =cut
281
282 sub _build_balancer_type {
283     return 'DBIx::Class::Storage::DBI::Replicated::Balancer::First';
284 }
285
286 =head2 _build_balancer
287
288 Lazy builder for the L</balancer> attribute.  This takes a Pool object so that
289 the balancer knows which pool it's balancing.
290
291 =cut
292
293 sub _build_balancer {
294     my $self = shift @_;
295     $self->create_balancer(
296         pool=>$self->pool, 
297         master=>$self->master,
298         %{$self->balancer_args},);
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;