removed ->reload_row from storage, changed this to a method based on the actual row...
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Replicated.pm
CommitLineData
2156bbdd 1package DBIx::Class::Storage::DBI::Replicated;
f5d3a5de 2
2bf79155 3use Moose;
2ce6e9a6 4use Class::MOP;
5use Moose::Util::TypeConstraints;
26ab719a 6use DBIx::Class::Storage::DBI;
2bf79155 7use DBIx::Class::Storage::DBI::Replicated::Pool;
26ab719a 8use DBIx::Class::Storage::DBI::Replicated::Balancer;
2bf79155 9
10=head1 NAME
11
12DBIx::Class::Storage::DBI::Replicated - ALPHA Replicated database support
13
14=head1 SYNOPSIS
15
16The Following example shows how to change an existing $schema to a replicated
17storage type, add some replicated (readonly) databases, and perform reporting
955a6df6 18tasks.
2bf79155 19
64cdad22 20 ## Change storage_type in your schema class
21 $schema->storage_type( ['::DBI::Replicated', {balancer=>'::Random'}] );
22
23 ## Add some slaves. Basically this is an array of arrayrefs, where each
24 ## arrayref is database connect information
25
26 $schema->storage->connect_replicants(
27 [$dsn1, $user, $pass, \%opts],
28 [$dsn2, $user, $pass, \%opts],
29 [$dsn3, $user, $pass, \%opts],
30 );
31
2bf79155 32=head1 DESCRIPTION
33
34Warning: This class is marked ALPHA. We are using this in development and have
35some basic test coverage but the code hasn't yet been stressed by a variety
36of databases. Individual DB's may have quirks we are not aware of. Please
37use this in development and pass along your experiences/bug fixes.
38
39This class implements replicated data store for DBI. Currently you can define
40one master and numerous slave database connections. All write-type queries
41(INSERT, UPDATE, DELETE and even LAST_INSERT_ID) are routed to master
42database, all read-type queries (SELECTs) go to the slave database.
43
44Basically, any method request that L<DBIx::Class::Storage::DBI> would normally
bca099a3 45handle gets delegated to one of the two attributes: L</read_handler> or to
46L</write_handler>. Additionally, some methods need to be distributed
2bf79155 47to all existing storages. This way our storage class is a drop in replacement
48for L<DBIx::Class::Storage::DBI>.
49
50Read traffic is spread across the replicants (slaves) occuring to a user
51selected algorithm. The default algorithm is random weighted.
52
bca099a3 53=head1 NOTES
54
55The consistancy betweeen master and replicants is database specific. The Pool
56gives you a method to validate it's replicants, removing and replacing them
57when they fail/pass predefined criteria. It is recommened that your application
58define two schemas, one using the replicated storage and another that just
59connects to the master.
2bf79155 60
61=head1 ATTRIBUTES
62
63This class defines the following attributes.
64
2ce6e9a6 65=head2 schema
66
67The underlying L<DBIx::Class::Schema> object this storage is attaching
68
69=cut
70
71has 'schema' => (
72 is=>'rw',
73 isa=>'DBIx::Class::Schema',
74 weak_ref=>1,
75 required=>1,
76);
77
26ab719a 78=head2 pool_type
2bf79155 79
26ab719a 80Contains the classname which will instantiate the L</pool> object. Defaults
81to: L<DBIx::Class::Storage::DBI::Replicated::Pool>.
2bf79155 82
83=cut
84
26ab719a 85has 'pool_type' => (
64cdad22 86 is=>'ro',
87 isa=>'ClassName',
2ce6e9a6 88 required=>1,
89 default=>'DBIx::Class::Storage::DBI::Replicated::Pool',
64cdad22 90 handles=>{
91 'create_pool' => 'new',
92 },
2bf79155 93);
94
f068a139 95=head2 pool_args
96
97Contains a hashref of initialized information to pass to the Balancer object.
98See L<DBIx::Class::Storage::Replicated::Pool> for available arguments.
99
100=cut
101
102has 'pool_args' => (
64cdad22 103 is=>'ro',
104 isa=>'HashRef',
105 lazy=>1,
106 required=>1,
107 default=>sub { {} },
f068a139 108);
109
110
26ab719a 111=head2 balancer_type
2bf79155 112
113The replication pool requires a balance class to provider the methods for
114choose how to spread the query load across each replicant in the pool.
115
116=cut
117
2ce6e9a6 118subtype 'DBIx::Class::Storage::DBI::Replicated::BalancerClassNamePart',
119 as 'ClassName';
120
121coerce 'DBIx::Class::Storage::DBI::Replicated::BalancerClassNamePart',
122 from 'Str',
123 via {
124 my $type = $_;
125 if($type=~m/^::/) {
126 $type = 'DBIx::Class::Storage::DBI::Replicated::Balancer'.$type;
127 }
128 Class::MOP::load_class($type);
129 $type;
130 };
131
26ab719a 132has 'balancer_type' => (
64cdad22 133 is=>'ro',
2ce6e9a6 134 isa=>'DBIx::Class::Storage::DBI::Replicated::BalancerClassNamePart',
135 coerce=>1,
136 required=>1,
137 default=> 'DBIx::Class::Storage::DBI::Replicated::Balancer::First',
64cdad22 138 handles=>{
139 'create_balancer' => 'new',
140 },
2bf79155 141);
142
17b05c13 143=head2 balancer_args
144
145Contains a hashref of initialized information to pass to the Balancer object.
f068a139 146See L<DBIx::Class::Storage::Replicated::Balancer> for available arguments.
17b05c13 147
148=cut
149
150has 'balancer_args' => (
64cdad22 151 is=>'ro',
152 isa=>'HashRef',
153 lazy=>1,
154 required=>1,
155 default=>sub { {} },
17b05c13 156);
157
26ab719a 158=head2 pool
2bf79155 159
26ab719a 160Is a <DBIx::Class::Storage::DBI::Replicated::Pool> or derived class. This is a
161container class for one or more replicated databases.
2bf79155 162
163=cut
164
26ab719a 165has 'pool' => (
64cdad22 166 is=>'ro',
167 isa=>'DBIx::Class::Storage::DBI::Replicated::Pool',
168 lazy_build=>1,
169 handles=>[qw/
170 connect_replicants
171 replicants
172 has_replicants
173 /],
2bf79155 174);
175
26ab719a 176=head2 balancer
2bf79155 177
26ab719a 178Is a <DBIx::Class::Storage::DBI::Replicated::Balancer> or derived class. This
179is a class that takes a pool (<DBIx::Class::Storage::DBI::Replicated::Pool>)
2bf79155 180
26ab719a 181=cut
2bf79155 182
26ab719a 183has 'balancer' => (
64cdad22 184 is=>'ro',
185 isa=>'DBIx::Class::Storage::DBI::Replicated::Balancer',
186 lazy_build=>1,
187 handles=>[qw/auto_validate_every/],
26ab719a 188);
2bf79155 189
cb6ec758 190=head2 master
191
192The master defines the canonical state for a pool of connected databases. All
193the replicants are expected to match this databases state. Thus, in a classic
194Master / Slaves distributed system, all the slaves are expected to replicate
195the Master's state as quick as possible. This is the only database in the
196pool of databases that is allowed to handle write traffic.
197
198=cut
199
200has 'master' => (
64cdad22 201 is=> 'ro',
202 isa=>'DBIx::Class::Storage::DBI',
203 lazy_build=>1,
cb6ec758 204);
205
cb6ec758 206=head1 ATTRIBUTES IMPLEMENTING THE DBIx::Storage::DBI INTERFACE
207
208The following methods are delegated all the methods required for the
209L<DBIx::Class::Storage::DBI> interface.
210
211=head2 read_handler
212
213Defines an object that implements the read side of L<BIx::Class::Storage::DBI>.
214
215=cut
216
217has 'read_handler' => (
64cdad22 218 is=>'rw',
219 isa=>'Object',
220 lazy_build=>1,
221 handles=>[qw/
222 select
223 select_single
224 columns_info_for
225 /],
cb6ec758 226);
227
cb6ec758 228=head2 write_handler
229
230Defines an object that implements the write side of L<BIx::Class::Storage::DBI>.
231
232=cut
233
234has 'write_handler' => (
64cdad22 235 is=>'ro',
236 isa=>'Object',
237 lazy_build=>1,
238 lazy_build=>1,
239 handles=>[qw/
240 on_connect_do
241 on_disconnect_do
242 connect_info
243 throw_exception
244 sql_maker
245 sqlt_type
246 create_ddl_dir
247 deployment_statements
248 datetime_parser
249 datetime_parser_type
250 last_insert_id
251 insert
252 insert_bulk
253 update
254 delete
255 dbh
2ce6e9a6 256 txn_begin
64cdad22 257 txn_do
258 txn_commit
259 txn_rollback
2ce6e9a6 260 txn_scope_guard
64cdad22 261 sth
262 deploy
2ce6e9a6 263
64cdad22 264 reload_row
2ce6e9a6 265 _prep_for_execute
266 configure_sqlt
267
64cdad22 268 /],
cb6ec758 269);
270
26ab719a 271=head1 METHODS
2bf79155 272
26ab719a 273This class defines the following methods.
2bf79155 274
cb6ec758 275=head2 new
2bf79155 276
cb6ec758 277L<DBIx::Class::Schema> when instantiating it's storage passed itself as the
2ce6e9a6 278first argument. So we need to massage the arguments a bit so that all the
279bits get put into the correct places.
2bf79155 280
281=cut
282
2ce6e9a6 283around 'new' => sub {
284 my ($new, $self, $schema, $storage_type_args, @args) = @_;
285 return $self->$new(schema=>$schema, %$storage_type_args, @args);
286};
2bf79155 287
cb6ec758 288=head2 _build_master
2bf79155 289
cb6ec758 290Lazy builder for the L</master> attribute.
2bf79155 291
292=cut
293
cb6ec758 294sub _build_master {
2ce6e9a6 295 my $self = shift @_;
296 DBIx::Class::Storage::DBI->new($self->schema);
106d5f3b 297}
298
26ab719a 299=head2 _build_pool
2bf79155 300
26ab719a 301Lazy builder for the L</pool> attribute.
2bf79155 302
303=cut
304
26ab719a 305sub _build_pool {
64cdad22 306 my $self = shift @_;
307 $self->create_pool(%{$self->pool_args});
2bf79155 308}
309
26ab719a 310=head2 _build_balancer
2bf79155 311
cb6ec758 312Lazy builder for the L</balancer> attribute. This takes a Pool object so that
313the balancer knows which pool it's balancing.
2bf79155 314
315=cut
316
26ab719a 317sub _build_balancer {
64cdad22 318 my $self = shift @_;
319 $self->create_balancer(
320 pool=>$self->pool,
321 master=>$self->master,
322 %{$self->balancer_args},
323 );
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 {
64cdad22 334 return shift->master;
cb6ec758 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 {
64cdad22 345 return shift->balancer;
cb6ec758 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 {
64cdad22 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 {
64cdad22 369 my $self = shift @_;
370 return grep {defined $_ && blessed $_} (
371 $self->master,
372 $self->replicants,
373 );
2bf79155 374}
375
c4d3fae2 376=head2 execute_reliably ($coderef, ?@args)
377
378Given a coderef, saves the current state of the L</read_handler>, forces it to
379use reliable storage (ie sets it to the master), executes a coderef and then
380restores the original state.
381
382Example:
383
64cdad22 384 my $reliably = sub {
385 my $name = shift @_;
386 $schema->resultset('User')->create({name=>$name});
387 my $user_rs = $schema->resultset('User')->find({name=>$name});
388 return $user_rs;
389 };
c4d3fae2 390
64cdad22 391 my $user_rs = $schema->storage->execute_reliably($reliably, 'John');
c4d3fae2 392
393Use this when you must be certain of your database state, such as when you just
394inserted something and need to get a resultset including it, etc.
395
396=cut
397
398sub execute_reliably {
64cdad22 399 my ($self, $coderef, @args) = @_;
400
401 unless( ref $coderef eq 'CODE') {
402 $self->throw_exception('Second argument must be a coderef');
403 }
404
405 ##Get copy of master storage
406 my $master = $self->master;
407
408 ##Get whatever the current read hander is
409 my $current = $self->read_handler;
410
411 ##Set the read handler to master
412 $self->read_handler($master);
413
414 ## do whatever the caller needs
415 my @result;
416 my $want_array = wantarray;
417
418 eval {
419 if($want_array) {
420 @result = $coderef->(@args);
421 } elsif(defined $want_array) {
422 ($result[0]) = ($coderef->(@args));
ed213e85 423 } else {
64cdad22 424 $coderef->(@args);
425 }
426 };
427
428 ##Reset to the original state
429 $self->read_handler($current);
430
431 ##Exception testing has to come last, otherwise you might leave the
432 ##read_handler set to master.
433
434 if($@) {
435 $self->throw_exception("coderef returned an error: $@");
436 } else {
437 return $want_array ? @result : $result[0];
438 }
c4d3fae2 439}
440
cb6ec758 441=head2 set_reliable_storage
442
443Sets the current $schema to be 'reliable', that is all queries, both read and
444write are sent to the master
64cdad22 445
cb6ec758 446=cut
447
448sub set_reliable_storage {
64cdad22 449 my $self = shift @_;
450 my $schema = $self->schema;
451 my $write_handler = $self->schema->storage->write_handler;
452
453 $schema->storage->read_handler($write_handler);
cb6ec758 454}
455
456=head2 set_balanced_storage
457
458Sets the current $schema to be use the </balancer> for all reads, while all
459writea are sent to the master only
64cdad22 460
cb6ec758 461=cut
462
463sub set_balanced_storage {
64cdad22 464 my $self = shift @_;
465 my $schema = $self->schema;
466 my $write_handler = $self->schema->storage->balancer;
467
468 $schema->storage->read_handler($write_handler);
cb6ec758 469}
2bf79155 470
6834cc1d 471=head2 around: txn_do ($coderef)
c4d3fae2 472
473Overload to the txn_do method, which is delegated to whatever the
474L<write_handler> is set to. We overload this in order to wrap in inside a
475L</execute_reliably> method.
476
477=cut
478
6834cc1d 479around 'txn_do' => sub {
64cdad22 480 my($txn_do, $self, $coderef, @args) = @_;
481 $self->execute_reliably(sub {$self->$txn_do($coderef, @args)});
6834cc1d 482};
c4d3fae2 483
2bf79155 484=head2 connected
485
486Check that the master and at least one of the replicants is connected.
487
488=cut
489
490sub connected {
64cdad22 491 my $self = shift @_;
492 return
493 $self->master->connected &&
494 $self->pool->connected_replicants;
2bf79155 495}
496
2bf79155 497=head2 ensure_connected
498
499Make sure all the storages are connected.
500
501=cut
502
503sub ensure_connected {
64cdad22 504 my $self = shift @_;
505 foreach my $source ($self->all_storages) {
506 $source->ensure_connected(@_);
507 }
2bf79155 508}
509
2bf79155 510=head2 limit_dialect
511
512Set the limit_dialect for all existing storages
513
514=cut
515
516sub limit_dialect {
64cdad22 517 my $self = shift @_;
518 foreach my $source ($self->all_storages) {
519 $source->limit_dialect(@_);
520 }
2bf79155 521}
522
2bf79155 523=head2 quote_char
524
525Set the quote_char for all existing storages
526
527=cut
528
529sub quote_char {
64cdad22 530 my $self = shift @_;
531 foreach my $source ($self->all_storages) {
532 $source->quote_char(@_);
533 }
2bf79155 534}
535
2bf79155 536=head2 name_sep
537
538Set the name_sep for all existing storages
539
540=cut
541
542sub name_sep {
64cdad22 543 my $self = shift @_;
544 foreach my $source ($self->all_storages) {
545 $source->name_sep(@_);
546 }
2bf79155 547}
548
2bf79155 549=head2 set_schema
550
551Set the schema object for all existing storages
552
553=cut
554
555sub set_schema {
64cdad22 556 my $self = shift @_;
557 foreach my $source ($self->all_storages) {
558 $source->set_schema(@_);
559 }
2bf79155 560}
561
2bf79155 562=head2 debug
563
564set a debug flag across all storages
565
566=cut
567
568sub debug {
64cdad22 569 my $self = shift @_;
570 foreach my $source ($self->all_storages) {
571 $source->debug(@_);
572 }
2bf79155 573}
574
2bf79155 575=head2 debugobj
576
577set a debug object across all storages
578
579=cut
580
581sub debugobj {
64cdad22 582 my $self = shift @_;
583 foreach my $source ($self->all_storages) {
584 $source->debugobj(@_);
585 }
2bf79155 586}
587
2bf79155 588=head2 debugfh
589
590set a debugfh object across all storages
591
592=cut
593
594sub debugfh {
64cdad22 595 my $self = shift @_;
596 foreach my $source ($self->all_storages) {
597 $source->debugfh(@_);
598 }
2bf79155 599}
600
2bf79155 601=head2 debugcb
602
603set a debug callback across all storages
604
605=cut
606
607sub debugcb {
64cdad22 608 my $self = shift @_;
609 foreach my $source ($self->all_storages) {
610 $source->debugcb(@_);
611 }
2bf79155 612}
613
2bf79155 614=head2 disconnect
615
616disconnect everything
617
618=cut
619
620sub disconnect {
64cdad22 621 my $self = shift @_;
622 foreach my $source ($self->all_storages) {
623 $source->disconnect(@_);
624 }
2bf79155 625}
626
f5d3a5de 627=head1 AUTHOR
628
64cdad22 629 John Napiorkowski <john.napiorkowski@takkle.com>
f5d3a5de 630
c4d3fae2 631Based on code originated by:
f5d3a5de 632
64cdad22 633 Norbert Csongrádi <bert@cpan.org>
634 Peter Siklósi <einon@einon.hu>
2156bbdd 635
f5d3a5de 636=head1 LICENSE
637
638You may distribute this code under the same terms as Perl itself.
639
640=cut
641
6421;