X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI%2FReplicated.pm;h=c583c6e58edcf3655cee7d1e2a97683791a0677a;hb=6a151f5877412d2824295071428c52eb4bf646ec;hp=3b99b3fed9bcee1a4ba6fca56996f7ba9f8b1012;hpb=e3c2b86d7e6a879e7b93765da07ad98ad720bd96;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Storage/DBI/Replicated.pm b/lib/DBIx/Class/Storage/DBI/Replicated.pm index 3b99b3f..c583c6e 100644 --- a/lib/DBIx/Class/Storage/DBI/Replicated.pm +++ b/lib/DBIx/Class/Storage/DBI/Replicated.pm @@ -7,10 +7,11 @@ BEGIN { ## use, so we explicitly test for these. my %replication_required = ( - Moose => '0.54', - MooseX::AttributeHelpers => '0.12', - Moose::Util::TypeConstraints => '0.54', - Class::MOP => '0.63', + 'Moose' => '0.77', + 'MooseX::AttributeHelpers' => '0.12', + 'MooseX::Types' => '0.10', + 'namespace::clean' => '0.11', + 'Hash::Merge' => '0.11' ); my @didnt_load; @@ -25,9 +26,17 @@ BEGIN { if @didnt_load; } +use Moose; use DBIx::Class::Storage::DBI; use DBIx::Class::Storage::DBI::Replicated::Pool; use DBIx::Class::Storage::DBI::Replicated::Balancer; +use DBIx::Class::Storage::DBI::Replicated::Types qw/BalancerClassNamePart DBICSchema DBICStorageDBI/; +use MooseX::Types::Moose qw/ClassName HashRef Object/; +use Scalar::Util 'reftype'; +use Carp::Clan qw/^DBIx::Class/; +use Hash::Merge 'merge'; + +use namespace::clean -except => 'meta'; =head1 NAME @@ -39,11 +48,15 @@ The Following example shows how to change an existing $schema to a replicated storage type, add some replicated (readonly) databases, and perform reporting tasks. - ## Change storage_type in your schema class +You should set the 'storage_type attribute to a replicated type. You should +also defined you arguments, such as which balancer you want and any arguments +that the Pool object should get. + $schema->storage_type( ['::DBI::Replicated', {balancer=>'::Random'}] ); - ## Add some slaves. Basically this is an array of arrayrefs, where each - ## arrayref is database connect information +Next, you need to add in the Replicants. Basically this is an array of +arrayrefs, where each arrayref is database connect information. Think of these +arguments as what you'd pass to the 'normal' $schema->connect method. $schema->storage->connect_replicants( [$dsn1, $user, $pass, \%opts], @@ -51,20 +64,28 @@ tasks. [$dsn3, $user, $pass, \%opts], ); - ## Now, just use the $schema as normal +Now, just use the $schema as you normally would. Automatically all reads will +be delegated to the replicants, while writes to the master. + $schema->resultset('Source')->search({name=>'etc'}); - ## You can force a given query to use a particular storage using the search - ### attribute 'force_pool'. For example: +You can force a given query to use a particular storage using the search +attribute 'force_pool'. For example: my $RS = $schema->resultset('Source')->search(undef, {force_pool=>'master'}); - - ## Now $RS will force everything (both reads and writes) to use whatever was - ## setup as the master storage. 'master' is hardcoded to always point to the - ## Master, but you can also use any Replicant name. Please see: - ## L and the replicants attribute for - ## More. Also see transactions and L for alternative ways - ## to force read traffic to the master. + +Now $RS will force everything (both reads and writes) to use whatever was setup +as the master storage. 'master' is hardcoded to always point to the Master, +but you can also use any Replicant name. Please see: +L and the replicants attribute for more. + +Also see transactions and L for alternative ways to +force read traffic to the master. In general, you should wrap your statements +in a transaction when you are reading and writing to the same tables at the +same time, since your replicants will often lag a bit behind the master. + +See L for more help and +walkthroughs. =head1 DESCRIPTION @@ -99,10 +120,11 @@ to force a query to run against Master when needed. Replicated Storage has additional requirements not currently part of L - Moose => 0.54 + Moose => 0.77 MooseX::AttributeHelpers => 0.12 - Moose::Util::TypeConstraints => 0.54 - Class::MOP => 0.63 + MooseX::Types => 0.10 + namespace::clean => 0.11 + Hash::Merge => 0.11 You will need to install these modules manually via CPAN or make them part of the Makefile for your distribution. @@ -119,7 +141,7 @@ The underlying L object this storage is attaching has 'schema' => ( is=>'rw', - isa=>'DBIx::Class::Schema', + isa=>DBICSchema, weak_ref=>1, required=>1, ); @@ -132,9 +154,8 @@ to: L. =cut has 'pool_type' => ( - is=>'ro', - isa=>'ClassName', - required=>1, + is=>'rw', + isa=>ClassName, default=>'DBIx::Class::Storage::DBI::Replicated::Pool', handles=>{ 'create_pool' => 'new', @@ -144,15 +165,14 @@ has 'pool_type' => ( =head2 pool_args Contains a hashref of initialized information to pass to the Balancer object. -See L for available arguments. +See L for available arguments. =cut has 'pool_args' => ( - is=>'ro', - isa=>'HashRef', + is=>'rw', + isa=>HashRef, lazy=>1, - required=>1, default=>sub { {} }, ); @@ -164,23 +184,9 @@ choose how to spread the query load across each replicant in the pool. =cut -subtype 'DBIx::Class::Storage::DBI::Replicated::BalancerClassNamePart', - as 'ClassName'; - -coerce 'DBIx::Class::Storage::DBI::Replicated::BalancerClassNamePart', - from 'Str', - via { - my $type = $_; - if($type=~m/^::/) { - $type = 'DBIx::Class::Storage::DBI::Replicated::Balancer'.$type; - } - Class::MOP::load_class($type); - $type; - }; - has 'balancer_type' => ( - is=>'ro', - isa=>'DBIx::Class::Storage::DBI::Replicated::BalancerClassNamePart', + is=>'rw', + isa=>BalancerClassNamePart, coerce=>1, required=>1, default=> 'DBIx::Class::Storage::DBI::Replicated::Balancer::First', @@ -192,13 +198,13 @@ has 'balancer_type' => ( =head2 balancer_args Contains a hashref of initialized information to pass to the Balancer object. -See L for available arguments. +See L for available arguments. =cut has 'balancer_args' => ( - is=>'ro', - isa=>'HashRef', + is=>'rw', + isa=>HashRef, lazy=>1, required=>1, default=>sub { {} }, @@ -230,7 +236,7 @@ is a class that takes a pool () =cut has 'balancer' => ( - is=>'ro', + is=>'rw', isa=>'DBIx::Class::Storage::DBI::Replicated::Balancer', lazy_build=>1, handles=>[qw/auto_validate_every/], @@ -248,7 +254,7 @@ pool of databases that is allowed to handle write traffic. has 'master' => ( is=> 'ro', - isa=>'DBIx::Class::Storage::DBI', + isa=>DBICStorageDBI, lazy_build=>1, ); @@ -265,7 +271,7 @@ Defines an object that implements the read side of L. has 'read_handler' => ( is=>'rw', - isa=>'Object', + isa=>Object, lazy_build=>1, handles=>[qw/ select @@ -282,8 +288,7 @@ Defines an object that implements the write side of L. has 'write_handler' => ( is=>'ro', - isa=>'Object', - lazy_build=>1, + isa=>Object, lazy_build=>1, handles=>[qw/ on_connect_do @@ -295,7 +300,8 @@ has 'write_handler' => ( create_ddl_dir deployment_statements datetime_parser - datetime_parser_type + datetime_parser_type + build_datetime_parser last_insert_id insert insert_bulk @@ -310,14 +316,87 @@ has 'write_handler' => ( sth deploy with_deferred_fk_checks - + dbh_do reload_row + with_deferred_fk_checks _prep_for_execute - configure_sqlt - + + backup + is_datatype_numeric + _count_select + _subq_count_select + _subq_update_delete + svp_rollback + svp_begin + svp_release /], ); +has _master_connect_info_opts => + (is => 'rw', isa => HashRef, default => sub { {} }); + +=head2 around: connect_info + +Preserve master's C options (for merging with replicants.) +Also set any Replicated related options from connect_info, such as +C, C, C and C. + +=cut + +around connect_info => sub { + my ($next, $self, $info, @extra) = @_; + + my $wantarray = wantarray; + + my %opts; + for my $arg (@$info) { + next unless (reftype($arg)||'') eq 'HASH'; + %opts = %{ merge($arg, \%opts) }; + } + delete $opts{dsn}; + + if (@opts{qw/pool_type pool_args/}) { + $self->pool_type(delete $opts{pool_type}) + if $opts{pool_type}; + + $self->pool_args( + merge((delete $opts{pool_args} || {}), $self->pool_args) + ); + + $self->pool($self->_build_pool) + if $self->pool; + } + + if (@opts{qw/balancer_type balancer_args/}) { + $self->balancer_type(delete $opts{balancer_type}) + if $opts{balancer_type}; + + $self->balancer_args( + merge((delete $opts{balancer_args} || {}), $self->balancer_args) + ); + + $self->balancer($self->_build_balancer) + if $self->balancer; + } + + $self->_master_connect_info_opts(\%opts); + + my (@res, $res); + if ($wantarray) { + @res = $self->$next($info, @extra); + } else { + $res = $self->$next($info, @extra); + } + + # Make sure master is blessed into the correct class and apply role to it. + my $master = $self->master; + $master->_determine_driver; + Moose::Meta::Class->initialize(ref $master); + DBIx::Class::Storage::DBI::Replicated::WithDSN->meta->apply($master); + + $wantarray ? @res : $res; +}; + =head1 METHODS This class defines the following methods. @@ -348,7 +427,8 @@ Lazy builder for the L attribute. sub _build_master { my $self = shift @_; - DBIx::Class::Storage::DBI->new($self->schema); + my $master = DBIx::Class::Storage::DBI->new($self->schema); + $master } =head2 _build_pool @@ -403,13 +483,49 @@ sub _build_read_handler { =head2 around: connect_replicants All calls to connect_replicants needs to have an existing $schema tacked onto -top of the args, since L needs it. +top of the args, since L needs it, and any C +options merged with the master, with replicant opts having higher priority. =cut -around 'connect_replicants' => sub { - my ($method, $self, @args) = @_; - $self->$method($self->schema, @args); +around connect_replicants => sub { + my ($next, $self, @args) = @_; + + for my $r (@args) { + $r = [ $r ] unless reftype $r eq 'ARRAY'; + + croak "coderef replicant connect_info not supported" + if ref $r->[0] && reftype $r->[0] eq 'CODE'; + +# any connect_info options? + my $i = 0; + $i++ while $i < @$r && (reftype($r->[$i])||'') ne 'HASH'; + +# make one if none + $r->[$i] = {} unless $r->[$i]; + +# merge if two hashes + my @hashes = @$r[$i .. $#{$r}]; + + croak "invalid connect_info options" + if (grep { reftype($_) eq 'HASH' } @hashes) != @hashes; + + croak "too many hashrefs in connect_info" + if @hashes > 2; + + my %opts = %{ merge(reverse @hashes) }; + +# delete them + splice @$r, $i+1, ($#{$r} - $i), (); + +# merge with master + %opts = %{ merge(\%opts, $self->_master_connect_info_opts) }; + +# update + $r->[$i] = \%opts; + } + + $self->$next($self->schema, @args); }; =head2 all_storages @@ -424,7 +540,7 @@ sub all_storages { my $self = shift @_; return grep {defined $_ && blessed $_} ( $self->master, - $self->replicants, + values %{ $self->replicants }, ); } @@ -518,24 +634,11 @@ writea are sent to the master only sub set_balanced_storage { my $self = shift @_; my $schema = $self->schema; - my $write_handler = $self->schema->storage->balancer; + my $balanced_handler = $self->schema->storage->balancer; - $schema->storage->read_handler($write_handler); + $schema->storage->read_handler($balanced_handler); } -=head2 around: txn_do ($coderef) - -Overload to the txn_do method, which is delegated to whatever the -L is set to. We overload this in order to wrap in inside a -L method. - -=cut - -around 'txn_do' => sub { - my($txn_do, $self, $coderef, @args) = @_; - $self->execute_reliably(sub {$self->$txn_do($coderef, @args)}); -}; - =head2 connected Check that the master and at least one of the replicants is connected. @@ -694,6 +797,21 @@ sub disconnect { } } +=head2 cursor_class + +set cursor class on all storages, or return master's + +=cut + +sub cursor_class { + my ($self, $cursor_class) = @_; + + if ($cursor_class) { + $_->cursor_class($cursor_class) for $self->all_storages; + } + $self->master->cursor_class; +} + =head1 GOTCHAS Due to the fact that replicants can lag behind a master, you must take care to