__PACKAGE__->mk_classdata('class_mappings' => {});
__PACKAGE__->mk_classdata('source_registrations' => {});
__PACKAGE__->mk_classdata('storage_type' => '::DBI');
-__PACKAGE__->mk_classdata('storage_type_args' => {});
__PACKAGE__->mk_classdata('storage');
__PACKAGE__->mk_classdata('exception_action');
__PACKAGE__->mk_classdata('stacktrace' => $ENV{DBIC_TRACE} || 0);
=over 4
-=item Arguments: $storage_type
+=item Arguments: $storage_type|[$storage_type, \%args]
-=item Return Value: $storage_type
+=item Return Value: $storage_type|[$storage_type, \%args]
=back
dealing with MSSQL via L<DBD::Sybase>, in which case you'd set it to
C<::DBI::Sybase::MSSQL>.
+If your storage type requires instantiation arguments, those are defined as a
+second argument in the form of a hashref and the entire value needs to be
+wrapped into an arrayref. See L<DBIx::Class::Storage::DBI::Replicated> for an
+example of this.
+
=head2 connection
=over 4
sub connection {
my ($self, @info) = @_;
return $self if !@info && $self->storage;
- my $storage_class = $self->storage_type;
+
+ my ($storage_class, $args) = ref $self->storage_type ?
+ (@{$self->storage_type},{}) : ($self->storage_type, {});
+
$storage_class = 'DBIx::Class::Storage'.$storage_class
if $storage_class =~ m/^::/;
eval "require ${storage_class};";
$self->throw_exception(
"No arguments to load_classes and couldn't load ${storage_class} ($@)"
) if $@;
- my $storage = $storage_class->new($self, $self->storage_type_args);
+ my $storage = $storage_class->new($self=>$args);
$storage->connect_info(\@info);
$self->storage($storage);
return $self;
}
}
+=head2 is_replicating
+
+A boolean that reports if a particular L<DBIx::Class::Storage::DBI> is set to
+replicate from a master database. Default is undef, which is the result
+returned by databases that don't support replication.
+
+=cut
+
+sub is_replicating {
+ return;
+
+}
+
+=head2 lag_behind_master
+
+Returns a number that represents a certain amount of lag behind a master db
+when a given storage is replicating. The number is database dependent, but
+starts at zero and increases with the amount of lag. Default in undef
+
+=cut
+
+sub lag_behind_master {
+ return;
+}
+
sub DESTROY {
my $self = shift;
return if !$self->_dbh;
tasks.
## Change storage_type in your schema class
- $schema->storage_type( '::DBI::Replicated' );
+ $schema->storage_type( ['::DBI::Replicated', {balancer=>'::Random'}] );
## Add some slaves. Basically this is an array of arrayrefs, where each
## arrayref is database connect information
has 'pool_type' => (
is=>'ro',
isa=>'ClassName',
- required=>1,
- lazy=>1,
- default=>'DBIx::Class::Storage::DBI::Replicated::Pool',
+ lazy_build=>1,
handles=>{
'create_pool' => 'new',
},
);
-
=head2 balancer_type
The replication pool requires a balance class to provider the methods for
has 'balancer_type' => (
is=>'ro',
isa=>'ClassName',
- required=>1,
- lazy=>1,
- default=>'DBIx::Class::Storage::DBI::Replicated::Balancer',
+ lazy_build=>1,
handles=>{
'create_balancer' => 'new',
},
);
-
=head2 pool
Is a <DBIx::Class::Storage::DBI::Replicated::Pool> or derived class. This is a
/],
);
-
=head2 balancer
Is a <DBIx::Class::Storage::DBI::Replicated::Balancer> or derived class. This
lazy_build=>1,
);
-
=head2 master
The master defines the canonical state for a pool of connected databases. All
lazy_build=>1,
);
-
=head1 ATTRIBUTES IMPLEMENTING THE DBIx::Storage::DBI INTERFACE
The following methods are delegated all the methods required for the
/],
);
-
=head2 write_handler
Defines an object that implements the write side of L<BIx::Class::Storage::DBI>.
/],
);
-
=head1 METHODS
This class defines the following methods.
my $schema = shift @_;
my $storage_type_args = shift @_;
my $obj = $class->SUPER::new($schema, $storage_type_args, @_);
-
+
+ ## Hate to do it this way, but can't seem to get advice on the attribute working right
+ ## maybe we can do a type and coercion for it.
+ if( $storage_type_args->{balancer_type} && $storage_type_args->{balancer_type}=~m/^::/) {
+ $storage_type_args->{balancer_type} = 'DBIx::Class::Storage::DBI::Replicated::Balancer'.$storage_type_args->{balancer_type};
+ eval "require $storage_type_args->{balancer_type}";
+ }
+
return $class->meta->new_object(
__INSTANCE__ => $obj,
%$storage_type_args,
DBIx::Class::Storage::DBI->new;
}
+=head2 _build_pool_type
+
+Lazy builder for the L</pool_type> attribute.
+
+=cut
+
+sub _build_pool_type {
+ return 'DBIx::Class::Storage::DBI::Replicated::Pool';
+}
+
=head2 _build_pool
Lazy builder for the L</pool> attribute.
shift->create_pool;
}
+=head2 _build_balancer_type
+
+Lazy builder for the L</balancer_type> attribute.
+
+=cut
+
+sub _build_balancer_type {
+ return 'DBIx::Class::Storage::DBI::Replicated::Balancer';
+}
+
=head2 _build_balancer
Lazy builder for the L</balancer> attribute. This takes a Pool object so that
sub _build_balancer {
my $self = shift @_;
- $self->create_balancer(pool=>$self->pool);
+ $self->create_balancer(
+ pool=>$self->pool,
+ master=>$self->master);
}
=head2 _build_write_handler
This class defines the following attributes.
+=head2 master
+
+The L<DBIx::Class::Storage::DBI> object that is the master database all the
+replicants are trying to follow. The balancer needs to know it since it's the
+ultimate fallback.
+
+=cut
+
+has 'master' => (
+ is=>'ro',
+ isa=>'DBIx::Class::Storage::DBI',
+ required=>1,
+);
+
=head2 pool
The L<DBIx::Class::Storage::DBI::Replicated::Pool> object that we are trying to
sub _build_current_replicant {
my $self = shift @_;
- $self->next_storage($self->pool);
+ $self->next_storage;
}
=head2 next_storage
your own subclasses of L<DBIx::Class::Storage::DBI::Replicated::Balancer> to
support other balance systems.
+This returns from the pool of active replicants. If there are no active
+replicants, then you should have it return the master as an ultimate fallback.
+
=cut
sub next_storage {
my $self = shift @_;
- return ($self->pool->active_replicants)[0]
- if $self->pool->active_replicants;
+ my $next = ($self->pool->active_replicants)[0];
+ return $next ? $next:$self->master;
}
-=head2 after: select
+=head2 before: select
Advice on the select attribute. Each time we use a replicant
we need to change it via the storage pool algorithm. That way we are spreading
=cut
-after 'select' => sub {
+before 'select' => sub {
my $self = shift @_;
my $next_replicant = $self->next_storage;
$self->current_replicant($next_replicant);
};
-=head2 after: select_single
+=head2 before: select_single
Advice on the select_single attribute. Each time we use a replicant
we need to change it via the storage pool algorithm. That way we are spreading
=cut
-after 'select_single' => sub {
+before 'select_single' => sub {
my $self = shift @_;
my $next_replicant = $self->next_storage;
$self->current_replicant($next_replicant);
};
-=head2 after: columns_info_for
+=head2 before: columns_info_for
Advice on the current_replicant_storage attribute. Each time we use a replicant
we need to change it via the storage pool algorithm. That way we are spreading
=cut
-after 'columns_info_for' => sub {
+before 'columns_info_for' => sub {
my $self = shift @_;
my $next_replicant = $self->next_storage;
$self->current_replicant($next_replicant);
=cut
sub next_storage {
- my $self = shift @_;
- return (shuffle($self->pool->active_replicants))[0]
- if $self->pool->active_replicants;
+ my $self = shift @_;
+ my $next = (shuffle($self->pool->active_replicants))[0];
+ return $next ? $next : $self->master;
}
-
=head1 AUTHOR
John Napiorkowski <john.napiorkowski@takkle.com>
$self->dbh->do("ROLLBACK TO SAVEPOINT $name")
}
+sub is_replicating {
+ my $self = shift @_;
+}
+
+sub lag_behind_master {
+ my $self = shift @_;
+}
+
1;
=head1 NAME
use_ok 'DBIx::Class::Storage::DBI::Replicated::Pool';
use_ok 'DBIx::Class::Storage::DBI::Replicated::Balancer';
-use_ok 'DBIx::Class::Storage::DBI::Replicated::Balancer::Random';
use_ok 'DBIx::Class::Storage::DBI::Replicated::Replicant';
use_ok 'DBIx::Class::Storage::DBI::Replicated';
sub init_schema {
my $class = shift @_;
my $schema = DBICTest->init_schema(
- storage_type=>'::DBI::Replicated',
- storage_type_args=>{
- balancer_type=>'DBIx::Class::Storage::DBI::Replicated::Balancer::Random',
- });
+ storage_type=>[
+ '::DBI::Replicated' => {
+ balancer_type=>'::Random',
+ }],
+ );
return $schema;
}
ok $replicated->schema->resultset('Artist')->find(2)
=> 'back to replicant 2.';
-
+## set all the replicants to inactive, and make sure the balancer falls back to
+## the master.
+
+$replicated->schema->storage->replicants->{"t/var/DBIxClass_slave1.db"}->active(0);
+$replicated->schema->storage->replicants->{"t/var/DBIxClass_slave2.db"}->active(0);
+
+ok $replicated->schema->resultset('Artist')->find(2)
+ => 'Fallback to master';
+
## Delete the old database files
$replicated->cleanup;
} else {
$schema = DBICTest::Schema->compose_namespace('DBICTest');
}
- if( $args{storage_type_args}) {
- $schema->storage_type_args($args{storage_type_args});
- }
if( $args{storage_type}) {
$schema->storage_type($args{storage_type});
}
sub deploy_schema {
my $self = shift;
- my $schema = shift;
+ my $schema = shift;
- if ($ENV{"DBICTEST_SQLT_DEPLOY"}) {
+ if ($ENV{"DBICTEST_SQLT_DEPLOY"}) {
return $schema->deploy();
} else {
open IN, "t/lib/sqlite.sql";