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