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