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