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