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