X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI%2FReplicated.pm;h=259cdc52bf777de0ad4c84145e316d5c81309e35;hb=a49fe31273f9306254dd3d0a29cbf765146fcfe0;hp=d711f84e90f06512edc038b34f5aa1f66ab49d34;hpb=7e38d85069fbeeff050dc736b756b60c01f85fc6;p=dbsrgits%2FDBIx-Class-Historic.git diff --git a/lib/DBIx/Class/Storage/DBI/Replicated.pm b/lib/DBIx/Class/Storage/DBI/Replicated.pm index d711f84..259cdc5 100644 --- a/lib/DBIx/Class/Storage/DBI/Replicated.pm +++ b/lib/DBIx/Class/Storage/DBI/Replicated.pm @@ -1,15 +1,46 @@ package DBIx::Class::Storage::DBI::Replicated; +BEGIN { + use Carp::Clan qw/^DBIx::Class/; + + ## Modules required for Replication support not required for general DBIC + ## use, so we explicitly test for these. + + my %replication_required = ( + Moose => '0.77', + MooseX::AttributeHelpers => '0.12', + MooseX::Types => '0.10', + namespace::clean => '0.11', + Hash::Merge => '0.11' + ); + + my @didnt_load; + + for my $module (keys %replication_required) { + eval "use $module $replication_required{$module}"; + push @didnt_load, "$module $replication_required{$module}" + if $@; + } + + croak("@{[ join ', ', @didnt_load ]} are missing and are required for Replication") + if @didnt_load; +} + use Moose; -use Class::MOP; -use Moose::Util::TypeConstraints; 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 'BalancerClassNamePart'; +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 -DBIx::Class::Storage::DBI::Replicated - ALPHA Replicated database support +DBIx::Class::Storage::DBI::Replicated - BETA Replicated database support =head1 SYNOPSIS @@ -47,7 +78,7 @@ tasks. =head1 DESCRIPTION Warning: This class is marked BETA. This has been running a production -website using MySQL native replication as it's backend and we have some decent +website using MySQL native replication as its backend and we have some decent test coverage but the code hasn't yet been stressed by a variety of databases. Individual DB's may have quirks we are not aware of. Please use this in first development and pass along your experiences/bug fixes. @@ -71,7 +102,20 @@ selected algorithm. The default algorithm is random weighted. The consistancy betweeen master and replicants is database specific. The Pool gives you a method to validate it's replicants, removing and replacing them when they fail/pass predefined criteria. Please make careful use of the ways -to force a query to run against Master when needed. +to force a query to run against Master when needed. + +=head1 REQUIREMENTS + +Replicated Storage has additional requirements not currently part of L + + Moose => 0.77 + MooseX::AttributeHelpers => 0.12 + 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. =head1 ATTRIBUTES @@ -98,9 +142,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', @@ -115,10 +158,9 @@ See L for available arguments. =cut has 'pool_args' => ( - is=>'ro', - isa=>'HashRef', + is=>'rw', + isa=>HashRef, lazy=>1, - required=>1, default=>sub { {} }, ); @@ -130,23 +172,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', @@ -163,8 +191,8 @@ See L for available arguments. =cut has 'balancer_args' => ( - is=>'ro', - isa=>'HashRef', + is=>'rw', + isa=>HashRef, lazy=>1, required=>1, default=>sub { {} }, @@ -196,7 +224,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/], @@ -231,7 +259,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 @@ -248,8 +276,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 @@ -275,19 +302,83 @@ has 'write_handler' => ( txn_scope_guard sth deploy + with_deferred_fk_checks reload_row _prep_for_execute - configure_sqlt /], ); +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); + } + + # May have to reapply role if master will be reblessed to a more specific + # driver. + $self->master->_determine_driver; + DBIx::Class::Storage::DBI::Replicated::WithDSN->meta->apply($self->master); + + $wantarray ? @res : $res; +}; + =head1 METHODS This class defines the following methods. -=head2 new +=head2 BUILDARGS L when instantiating it's storage passed itself as the first argument. So we need to massage the arguments a bit so that all the @@ -295,10 +386,15 @@ bits get put into the correct places. =cut -around 'new' => sub { - my ($new, $self, $schema, $storage_type_args, @args) = @_; - return $self->$new(schema=>$schema, %$storage_type_args, @args); -}; +sub BUILDARGS { + my ($class, $schema, $storage_type_args, @args) = @_; + + return { + schema=>$schema, + %$storage_type_args, + @args + } +} =head2 _build_master @@ -308,7 +404,9 @@ 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); + DBIx::Class::Storage::DBI::Replicated::WithDSN->meta->apply($master); + $master } =head2 _build_pool @@ -363,13 +461,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 @@ -384,7 +518,7 @@ sub all_storages { my $self = shift @_; return grep {defined $_ && blessed $_} ( $self->master, - $self->replicants, + values %{ $self->replicants }, ); } @@ -533,6 +667,7 @@ sub limit_dialect { foreach my $source ($self->all_storages) { $source->limit_dialect(@_); } + return $self->master->quote_char; } =head2 quote_char @@ -546,6 +681,7 @@ sub quote_char { foreach my $source ($self->all_storages) { $source->quote_char(@_); } + return $self->master->quote_char; } =head2 name_sep @@ -559,6 +695,7 @@ sub name_sep { foreach my $source ($self->all_storages) { $source->name_sep(@_); } + return $self->master->name_sep; } =head2 set_schema @@ -582,9 +719,12 @@ set a debug flag across all storages sub debug { my $self = shift @_; - foreach my $source ($self->all_storages) { - $source->debug(@_); + if(@_) { + foreach my $source ($self->all_storages) { + $source->debug(@_); + } } + return $self->master->debug; } =head2 debugobj @@ -595,9 +735,12 @@ set a debug object across all storages sub debugobj { my $self = shift @_; - foreach my $source ($self->all_storages) { - $source->debugobj(@_); + if(@_) { + foreach my $source ($self->all_storages) { + $source->debugobj(@_); + } } + return $self->master->debugobj; } =head2 debugfh @@ -608,9 +751,12 @@ set a debugfh object across all storages sub debugfh { my $self = shift @_; - foreach my $source ($self->all_storages) { - $source->debugfh(@_); + if(@_) { + foreach my $source ($self->all_storages) { + $source->debugfh(@_); + } } + return $self->master->debugfh; } =head2 debugcb @@ -621,9 +767,12 @@ set a debug callback across all storages sub debugcb { my $self = shift @_; - foreach my $source ($self->all_storages) { - $source->debugcb(@_); + if(@_) { + foreach my $source ($self->all_storages) { + $source->debugcb(@_); + } } + return $self->master->debugcb; } =head2 disconnect @@ -639,6 +788,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 @@ -691,4 +855,6 @@ You may distribute this code under the same terms as Perl itself. =cut +__PACKAGE__->meta->make_immutable; + 1;