Revision history for DBIx::Class
+ - Replication updates: Improved the replication tests so that they are
+ more reliable and accurate, and hopefully solve some cross platform
+ issues. Bugfixes related to naming particular replicants in a
+ 'force_pool' attribute. Lots of documentation updates, including a
+ new Introduction.pod file. Fixed the way we detect transaction to
+ make this more reliable and forward looking. Fixed some trouble with
+ the way Moose Types are used.
- Added call to Pod::Inherit in Makefile.PL -
currently at author-time only, so we need to add the produced
.pod files to the MANIFEST
changes made since the row was last read from storage. Actually
implemented in L<DBIx::Class::PK>
+Note: If you are using L<DBIx::Class::Storage::DBI::Replicated> as your
+storage, please kept in mind that if you L</discard_changes> on a row that you
+just updated or created, you should wrap the entire bit inside a transaction.
+Otherwise you run the risk that you insert or update to the master database
+but read from a replicant database that has not yet been updated from the
+master. This will result in unexpected results.
+
=cut
1;
## 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'
+ 'Moose' => '0.77',
+ 'MooseX::AttributeHelpers' => '0.12',
+ 'MooseX::Types' => '0.10',
+ 'namespace::clean' => '0.11',
+ 'Hash::Merge' => '0.11'
);
my @didnt_load;
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 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/;
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],
[$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<DBIx::Class::Storage::Replicated::Pool> and the replicants attribute for
- ## More. Also see transactions and L</execute_reliably> 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<DBIx::Class::Storage::DBI::Replicated::Pool> and the replicants attribute for more.
+
+Also see transactions and L</execute_reliably> 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<DBIx::Class::Storage::DBI::Replicated::Instructions> for more help and
+walkthroughs.
=head1 DESCRIPTION
has 'schema' => (
is=>'rw',
- isa=>'DBIx::Class::Schema',
+ isa=>DBICSchema,
weak_ref=>1,
required=>1,
);
=head2 pool_args
Contains a hashref of initialized information to pass to the Balancer object.
-See L<DBIx::Class::Storage::Replicated::Pool> for available arguments.
+See L<DBIx::Class::Storage::DBI::Replicated::Pool> for available arguments.
=cut
=head2 balancer_args
Contains a hashref of initialized information to pass to the Balancer object.
-See L<DBIx::Class::Storage::Replicated::Balancer> for available arguments.
+See L<DBIx::Class::Storage::DBI::Replicated::Balancer> for available arguments.
=cut
has 'master' => (
is=> 'ro',
- isa=>'DBIx::Class::Storage::DBI',
+ isa=>DBICStorageDBI,
lazy_build=>1,
);
create_ddl_dir
deployment_statements
datetime_parser
- datetime_parser_type
+ datetime_parser_type
+ build_datetime_parser
last_insert_id
insert
insert_bulk
sth
deploy
with_deferred_fk_checks
-
+ dbh_do
reload_row
+ with_deferred_fk_checks
_prep_for_execute
-
+
+ backup
+ is_datatype_numeric
+ _count_select
+ _subq_count_select
+ _subq_update_delete
+ svp_rollback
+ svp_begin
+ svp_release
/],
);
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<write_handler> is set to. We overload this in order to wrap in inside a
-L</execute_reliably> 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.
use Moose::Role;
requires 'next_storage';
use MooseX::Types::Moose qw/Int/;
-
+use DBIx::Class::Storage::DBI::Replicated::Pool;
+use DBIx::Class::Storage::DBI::Replicated::Types qw/DBICStorageDBI/;
use namespace::clean -except => 'meta';
=head1 NAME
has 'master' => (
is=>'ro',
- isa=>'DBIx::Class::Storage::DBI',
+ isa=>DBICStorageDBI,
required=>1,
);
has 'current_replicant' => (
is=> 'rw',
- isa=>'DBIx::Class::Storage::DBI',
+ isa=>DBICStorageDBI,
lazy_build=>1,
handles=>[qw/
select
around 'select' => sub {
my ($select, $self, @args) = @_;
-
+
if (my $forced_pool = $args[-1]->{force_pool}) {
delete $args[-1]->{force_pool};
return $self->_get_forced_pool($forced_pool)->select(@args);
+ } elsif($self->master->{transaction_depth}) {
+ return $self->master->select(@args);
} else {
$self->increment_storage;
return $self->$select(@args);
around 'select_single' => sub {
my ($select_single, $self, @args) = @_;
-
+
if (my $forced_pool = $args[-1]->{force_pool}) {
delete $args[-1]->{force_pool};
return $self->_get_forced_pool($forced_pool)->select_single(@args);
+ } elsif($self->master->{transaction_depth}) {
+ return $self->master->select_single(@args);
} else {
$self->increment_storage;
return $self->$select_single(@args);
return $forced_pool;
} elsif($forced_pool eq 'master') {
return $self->master;
- } elsif(my $replicant = $self->pool->replicants($forced_pool)) {
+ } elsif(my $replicant = $self->pool->replicants->{$forced_pool}) {
return $replicant;
} else {
$self->master->throw_exception("$forced_pool is not a named replicant.");
=head1 AUTHOR
-John Napiorkowski <john.napiorkowski@takkle.com>
+John Napiorkowski <jjnapiork@cpan.org>
=head1 LICENSE
--- /dev/null
+package DBIx::Class::Storage::DBI::Replicated::Introduction;
+
+=head1 NAME
+
+DBIx::Class::Storage::DBI::Replicated::Introduction - Minimum Need to Know
+
+=head1 SYNOPSIS
+
+This is an introductory document for L<DBIx::Class::Storage::Replication>.
+
+This document is not an overview of what replication is or why you should be
+using it. It is not a document explaing how to setup MySQL native replication
+either. Copious external resources are avialable for both. This document
+presumes you have the basics down.
+
+=head1 DESCRIPTION
+
+L<DBIx::Class> supports a framework for using database replication. This system
+is integrated completely, which means once it's setup you should be able to
+automatically just start using a replication cluster without additional work or
+changes to your code. Some caveats apply, primarily related to the proper use
+of transactions (you are wrapping all your database modifying statements inside
+a transaction, right ;) ) however in our experience properly written DBIC will
+work transparently with Replicated storage.
+
+Currently we have support for MySQL native replication, which is relatively
+easy to install and configure. We also currently support single master to one
+or more replicants (also called 'slaves' in some documentation). However the
+framework is not specifically tied to the MySQL framework and supporting other
+replication systems or topographies should be possible. Please bring your
+patches and ideas to the #dbix-class IRC channel or the mailing list.
+
+For an easy way to start playing with MySQL native replication, see:
+L<MySQL::Sandbox>.
+
+If you are using this with a L<Catalyst> based appplication, you may also wish
+to see more recent updates to L<Catalyst::Model::DBIC::Schema>, which has
+support for replication configuration options as well.
+
+=head1 REPLICATED STORAGE
+
+By default, when you start L<DBIx::Class>, your Schema (L<DBIx::Class::Schema>)
+is assigned a storage_type, which when fully connected will reflect your
+underlying storage engine as defined by your choosen database driver. For
+example, if you connect to a MySQL database, your storage_type will be
+L<DBIx::Class::Storage::DBI::mysql> Your storage type class will contain
+database specific code to help smooth over the differences between databases
+and let L<DBIx::Class> do its thing.
+
+If you want to use replication, you will override this setting so that the
+replicated storage engine will 'wrap' your underlying storages and present to
+the end programmer a unified interface. This wrapper storage class will
+delegate method calls to either a master database or one or more replicated
+databases based on if they are read only (by default sent to the replicants)
+or write (reserved for the master). Additionally, the Replicated storage
+will monitor the health of your replicants and automatically drop them should
+one exceed configurable parameters. Later, it can automatically restore a
+replicant when its health is restored.
+
+This gives you a very robust system, since you can add or drop replicants
+and DBIC will automatically adjust itself accordingly.
+
+Additionally, if you need high data integrity, such as when you are executing
+a transaction, replicated storage will automatically delegate all database
+traffic to the master storage. There are several ways to enable this high
+integrity mode, but wrapping your statements inside a transaction is the easy
+and canonical option.
+
+=head1 PARTS OF REPLICATED STORAGE
+
+A replicated storage contains several parts. First, there is the replicated
+storage itself (L<DBIx::Class::Storage::DBI::Replicated). A replicated storage
+takes a pool of replicants (L<DBIx::Class::Storage::DBI::Replicated::Pool>)
+and a software balancer (L<DBIx::Class::Storage::DBI::Replicated::Pool>). The
+balancer does the job of splitting up all the read traffic amongst each
+replicant in the Pool. Currently there are two types of balancers, a Random one
+which chooses a Replicant in the Pool using a naive randomizer algorithm, and a
+First replicant, which just uses the first one in the Pool (and obviously is
+only of value when you have a single replicant).
+
+=head1 REPLICATED STORAGE CONFIGURATION
+
+All the parts of replication can be altered dynamically at runtime, which makes
+it possibly to create a system that automatically scales under load by creating
+more replicants as needed, perhaps using a cloud system such as Amazon EC2.
+However, for common use you can setup your replicated storage to be enabled at
+the time you connect the databases. The following is a breakdown of how you
+may wish to do this. Again, if you are using L<Catalyst>, I strongly recommend
+you use (or upgrade to) the latest L<Catalyst::Model::DBIC::Schema>, which makes
+this job even easier.
+
+First, you need to connect your L<DBIx::Class::Schema>. Let's assume you have
+such a schema called, "MyApp::Schema".
+
+ use MyApp::Schema;
+ my $schema = MyApp::Schema->connect($dsn, $user, $pass);
+
+Next, you need to set the storage_type.
+
+ $schema->storage_type(
+ ::DBI::Replicated' => {
+ balancer_type => '::Random',
+ balancer_args => {
+ auto_validate_every => 5,
+ master_read_weight => 1
+ },
+ pool_args => {
+ maximum_lag =>2,
+ },
+ }
+ );
+
+Let's break down the settings. The method L<DBIx::Class::Schema/storage_type>
+takes one mandatory parameter, a scalar value, and an option second value which
+is a Hash Reference of configuration options for that storage. In this case,
+we are setting the Replicated storage type using '::DBI::Replicated' as the
+first value. You will only use a different value if you are subclassing the
+replicated storage, so for now just copy that first parameter.
+
+The second parameter contains a hash reference of stuff that gets passed to the
+replicated storage. L<DBIx::Class::Storage::DBI::Replicated/balancer_type> is
+the type of software load balancer you will use to split up traffic among all
+your replicants. Right now we have two options, "::Random" and "::First". You
+can review documentation for both at:
+
+L<DBIx::Class::Storage::DBI::Replicated::Balancer::First>,
+L<DBIx::Class::Storage::DBI::Replicated::Balancer::Random>.
+
+In this case we will have three replicants, so the ::Random option is the only
+one that makes sense.
+
+'balancer_args' get passed to the balancer when it's instantiated. All
+balancers have the 'auto_validate_every' option. This is the number of seconds
+we allow to pass between validation checks on a load balanced replicant. So
+the higher the number, the more possibility that your reads to the replicant
+may be inconsistant with what's on the master. Setting this number too low
+will result in increased database loads, so choose a number with care. Our
+experience is that setting the number around 5 seconds results in a good
+performance / integrity balance.
+
+'master_read_weight' is an option associated with the ::Random balancer. It
+allows you to let the master be read from. I usually leave this off (default
+is off).
+
+The 'pool_args' are configuration options associated with the replicant pool.
+This object (L<DBIx::Class::Storage::DBI::Replicated::Pool>) manages all the
+declared replicants. 'maximum_lag' is the number of seconds a replicant is
+allowed to lag behind the master before being temporarily removed from the pool.
+Keep in mind that the Balancer option 'auto_validate_every' determins how often
+a replicant is tested against this condition, so the true possible lag can be
+higher than the number you set. The default is zero.
+
+No matter how low you set the maximum_lag or the auto_validate_every settings,
+there is always the chance that your replicants will lag a bit behind the
+master for the supported replication system built into MySQL. You can ensure
+reliabily reads by using a transaction, which will force both read and write
+activity to the master, however this will increase the load on your master
+database.
+
+After you've configured the replicated storage, you need to add the connection
+information for the replicants:
+
+ $schema->storage->connect_replicants(
+ [$dsn1, $user, $pass, \%opts],
+ [$dsn2, $user, $pass, \%opts],
+ [$dsn3, $user, $pass, \%opts],
+ );
+
+These replicants should be configured as slaves to the master using the
+instructions for MySQL native replication, or if you are just learning, you
+will find L<MySQL::Sandbox> an easy way to set up a replication cluster.
+
+And now your $schema object is properly configured! Enjoy!
+
+=head1 AUTHOR
+
+John Napiorkowski <jjnapiork@cpan.org>
+
+=head1 LICENSE
+
+You may distribute this code under the same terms as Perl itself.
+
+=cut
+
+1;
has 'replicants' => (
is=>'rw',
metaclass => 'Collection::Hash',
- isa=>HashRef['DBIx::Class::Storage::DBI'],
+ isa=>HashRef['Object'],
default=>sub {{}},
provides => {
'set' => 'set_replicant',
'empty' => 'has_replicants',
'count' => 'num_replicants',
'delete' => 'delete_replicant',
+ 'values' => 'all_replicant_storages',
},
);
# L<DBIx::Class::Storage::DBI::Replicated>
use MooseX::Types
- -declare => [qw/BalancerClassNamePart Weight/];
+ -declare => [qw/BalancerClassNamePart Weight DBICSchema DBICStorageDBI/];
use MooseX::Types::Moose qw/ClassName Str Num/;
class_type 'DBIx::Class::Storage::DBI';
class_type 'DBIx::Class::Schema';
+subtype DBICSchema, as 'DBIx::Class::Schema';
+subtype DBICStorageDBI, as 'DBIx::Class::Storage::DBI';
+
subtype BalancerClassNamePart,
as ClassName;
around '_query_start' => sub {
my ($method, $self, $sql, @bind) = @_;
my $dsn = $self->_dbi_connect_info->[0];
- $self->$method("DSN: $dsn SQL: $sql", @bind);
+ my($op, $rest) = (($sql=~m/^(\w+)(.+)$/),'NOP', 'NO SQL');
+ my $storage_type = $self->can('active') ? 'REPLICANT' : 'MASTER';
+
+ $self->$method("$op [DSN_$storage_type=$dsn]$rest", @bind);
};
=head1 ALSO SEE
use DBICTest;
use List::Util 'first';
use Scalar::Util 'reftype';
+use File::Spec;
use IO::Handle;
BEGIN {
eval "use DBIx::Class::Storage::DBI::Replicated; use Test::Moose";
plan $@
? ( skip_all => "Deps not installed: $@" )
- : ( tests => 90 );
+ : ( tests => 126 );
}
use_ok 'DBIx::Class::Storage::DBI::Replicated::Pool';
use_ok 'DBIx::Class::Storage::DBI::Replicated::Replicant';
use_ok 'DBIx::Class::Storage::DBI::Replicated';
+use Moose();
+use MooseX::Types();
+diag "Using Moose version $Moose::VERSION and MooseX::Types version $MooseX::Types::VERSION";
+
=head1 HOW TO USE
This is a test of the replicated storage system. This will work in one of
use File::Copy;
use base 'DBIx::Class::DBI::Replicated::TestReplication';
- __PACKAGE__->mk_accessors( qw/master_path slave_paths/ );
+ __PACKAGE__->mk_accessors(qw/master_path slave_paths/);
- ## Set the mastep path from DBICTest
+ ## Set the master path from DBICTest
sub new {
my $class = shift @_;
$self->master_path( DBICTest->_sqlite_dbfilename );
$self->slave_paths([
- "t/var/DBIxClass_slave1.db",
- "t/var/DBIxClass_slave2.db",
- ]);
+ File::Spec->catfile(qw/t var DBIxClass_slave1.db/),
+ File::Spec->catfile(qw/t var DBIxClass_slave2.db/),
+ ]);
return $self;
}
my @connect_infos = map { [$_,'','',{AutoCommit=>1}] } @dsn;
- # try a hashref too
+ ## Make sure nothing is left over from a failed test
+ $self->cleanup;
+
+ ## try a hashref too
my $c = $connect_infos[0];
$connect_infos[0] = {
dsn => $c->[0],
sub cleanup {
my $self = shift @_;
foreach my $slave (@{$self->slave_paths}) {
- unlink $slave;
+ if(-e $slave) {
+ unlink $slave;
+ }
}
}
ok my @replicated_storages = $replicated->schema->storage->connect_replicants(@replicant_connects)
=> 'Created some storages suitable for replicants';
+our %debug;
+$replicated->schema->storage->debug(1);
+$replicated->schema->storage->debugcb(sub {
+ my ($op, $info) = @_;
+ ##warn "\n$op, $info\n";
+ %debug = (
+ op => $op,
+ info => $info,
+ dsn => ($info=~m/\[(.+)\]/)[0],
+ storage_type => $info=~m/REPLICANT/ ? 'REPLICANT' : 'MASTER',
+ );
+});
+
ok my @all_storages = $replicated->schema->storage->all_storages
=> '->all_storages';
my @replicant_names = keys %{ $replicated->schema->storage->replicants };
+ok @replicant_names, "found replicant names @replicant_names";
+
## Silence warning about not supporting the is_replicating method if using the
## sqlite dbs.
$replicated->schema->storage->debugobj->silence(1)
[ qw/artistid name/ ],
[ 4, "Ozric Tentacles"],
]);
+
+ is $debug{storage_type}, 'MASTER',
+ "got last query from a master: $debug{dsn}";
+
+ like $debug{info}, qr/INSERT/, 'Last was an insert';
## Make sure all the slaves have the table definitions
ok my $artist1 = $replicated->schema->resultset('Artist')->find(4)
=> 'Created Result';
+## We removed testing here since master read weight is on, so we can't tell in
+## advance what storage to expect. We turn master read weight off a bit lower
+## is $debug{storage_type}, 'REPLICANT'
+## => "got last query from a replicant: $debug{dsn}, $debug{info}";
+
isa_ok $artist1
=> 'DBICTest::Artist';
[ 7, "Watergate"],
]);
+ is $debug{storage_type}, 'MASTER',
+ "got last query from a master: $debug{dsn}";
+
+ like $debug{info}, qr/INSERT/, 'Last was an insert';
+
## Make sure all the slaves have the table definitions
$replicated->replicate;
ok my $artist2 = $replicated->schema->resultset('Artist')->find(5)
=> 'Sync succeed';
-
+
+is $debug{storage_type}, 'REPLICANT'
+ => "got last query from a replicant: $debug{dsn}";
+
isa_ok $artist2
=> 'DBICTest::Artist';
ok my $artist3 = $replicated->schema->resultset('Artist')->find(6)
=> 'Still finding stuff.';
-
+
+is $debug{storage_type}, 'REPLICANT'
+ => "got last query from a replicant: $debug{dsn}";
+
isa_ok $artist3
=> 'DBICTest::Artist';
ok ! $replicated->schema->resultset('Artist')->find(666)
=> 'Correctly failed to find something.';
-
+
+is $debug{storage_type}, 'REPLICANT'
+ => "got last query from a replicant: $debug{dsn}";
+
## test the reliable option
TESTRELIABLE: {
ok $replicated->schema->resultset('Artist')->find(2)
=> 'Read from master 1';
-
+
+ is $debug{storage_type}, 'MASTER',
+ "got last query from a master: $debug{dsn}";
+
ok $replicated->schema->resultset('Artist')->find(5)
=> 'Read from master 2';
-
+
+ is $debug{storage_type}, 'MASTER',
+ "got last query from a master: $debug{dsn}";
+
$replicated->schema->storage->set_balanced_storage;
ok $replicated->schema->resultset('Artist')->find(3)
=> 'Read from replicant';
+
+ is $debug{storage_type}, 'REPLICANT',
+ "got last query from a replicant: $debug{dsn}";
}
## Make sure when reliable goes out of scope, we are using replicants again
ok $replicated->schema->resultset('Artist')->find(1)
=> 'back to replicant 1.';
-
+
+ is $debug{storage_type}, 'REPLICANT',
+ "got last query from a replicant: $debug{dsn}";
+
ok $replicated->schema->resultset('Artist')->find(2)
=> 'back to replicant 2.';
+ is $debug{storage_type}, 'REPLICANT',
+ "got last query from a replicant: $debug{dsn}";
+
## set all the replicants to inactive, and make sure the balancer falls back to
## the master.
$replicated->schema->storage->debugfh($debugfh);
ok $replicated->schema->resultset('Artist')->find(2)
- => 'Fallback to master';
+ => 'Fallback to master';
+
+ is $debug{storage_type}, 'MASTER',
+ "got last query from a master: $debug{dsn}";
like $fallback_warning, qr/falling back to master/
- => 'emits falling back to master warning';
+ => 'emits falling back to master warning';
$replicated->schema->storage->debugfh($oldfh);
}
ok $replicated->schema->resultset('Artist')->find(2)
=> 'Returned to replicates';
+
+is $debug{storage_type}, 'REPLICANT',
+ "got last query from a replicant: $debug{dsn}";
## Getting slave status tests
## We skip this tests unless you have a custom replicants, since the default
## sqlite based replication tests don't support these functions.
- skip 'Cannot Test Replicant Status on Non Replicating Database', 9
+ skip 'Cannot Test Replicant Status on Non Replicating Database', 10
unless DBICTest->has_custom_dsn && $ENV{"DBICTEST_SLAVE0_DSN"};
$replicated->replicate; ## Give the slaves a chance to catchup.
ok $replicated->schema->resultset('Artist')->find(5)
=> 'replicant reactivated';
+
+ is $debug{storage_type}, 'REPLICANT',
+ "got last query from a replicant: $debug{dsn}";
is $replicated->schema->storage->pool->active_replicants => 2
=> "both replicants reactivated";
ok my $reliably = sub {
ok $replicated->schema->resultset('Artist')->find(5)
- => 'replicant reactivated';
+ => 'replicant reactivated';
+
+ is $debug{storage_type}, 'MASTER',
+ "got last query from a master: $debug{dsn}";
} => 'created coderef properly';
ok $replicated->schema->resultset('Artist')->find(3)
=> 'replicant reactivated';
+
+is $debug{storage_type}, 'REPLICANT', "got last query from a replicant: $debug{dsn}";
## make sure transactions are set to execute_reliably
]);
ok my $result = $replicated->schema->resultset('Artist')->find($id)
- => 'Found expected artist';
-
+ => "Found expected artist for $id";
+
+ is $debug{storage_type}, 'MASTER',
+ "got last query from a master: $debug{dsn}";
+
ok my $more = $replicated->schema->resultset('Artist')->find(1)
- => 'Found expected artist again';
-
+ => 'Found expected artist again for 1';
+
+ is $debug{storage_type}, 'MASTER',
+ "got last query from a master: $debug{dsn}";
+
return ($result, $more);
} => 'Created a coderef properly';
is $return[0]->id, 666
=> 'first returned value is correct';
+
+ is $debug{storage_type}, 'MASTER',
+ "got last query from a master: $debug{dsn}";
is $return[1]->id, 1
=> 'second returned value is correct';
+
+ is $debug{storage_type}, 'MASTER',
+ "got last query from a master: $debug{dsn}";
+
}
## Test that asking for single return works
{
- ok my $return = $replicated->schema->txn_do($transaction, 777)
+ ok my @return = $replicated->schema->txn_do($transaction, 777)
=> 'did transaction';
- is $return->id, 777
+ is $return[0]->id, 777
=> 'first returned value is correct';
+
+ is $return[1]->id, 1
+ => 'second returned value is correct';
}
## Test transaction returning a single value
ok my $result = $replicated->schema->txn_do(sub {
ok my $more = $replicated->schema->resultset('Artist')->find(1)
=> 'found inside a transaction';
+ is $debug{storage_type}, 'MASTER', "got last query from a master: $debug{dsn}";
return $more;
}) => 'successfully processed transaction';
ok $replicated->schema->resultset('Artist')->find(1)
=> 'replicant reactivated';
+
+is $debug{storage_type}, 'REPLICANT', "got last query from a replicant: $debug{dsn}";
## Test Discard changes
{
ok my $artist = $replicated->schema->resultset('Artist')->find(2)
=> 'got an artist to test discard changes';
-
- ok $artist->discard_changes
+
+ is $debug{storage_type}, 'REPLICANT', "got last query from a replicant: $debug{dsn}";
+
+ ok $artist->get_from_storage({force_pool=>'master'})
=> 'properly discard changes';
+
+ is $debug{storage_type}, 'MASTER', "got last query from a master: $debug{dsn}";
+
}
## Test some edge cases, like trying to do a transaction inside a transaction, etc
return $replicated->schema->txn_do(sub {
ok my $more = $replicated->schema->resultset('Artist')->find(1)
=> 'found inside a transaction inside a transaction';
+ is $debug{storage_type}, 'MASTER', "got last query from a master: $debug{dsn}";
return $more;
});
}) => 'successfully processed transaction';
return $replicated->schema->txn_do(sub {
return $replicated->schema->storage->execute_reliably(sub {
ok my $more = $replicated->schema->resultset('Artist')->find(1)
- => 'found inside crazy deep transactions and execute_reliably';
+ => 'found inside crazy deep transactions and execute_reliably';
+ is $debug{storage_type}, 'MASTER', "got last query from a master: $debug{dsn}";
return $more;
});
});
ok my $artist = $reliable_artist_rs->find(2)
=> 'got an artist result via force_pool storage';
+
+ is $debug{storage_type}, 'MASTER', "got last query from a master: $debug{dsn}";
}
+## Test the force_pool resultset attribute part two.
+
+{
+ ok my $artist_rs = $replicated->schema->resultset('Artist')
+ => 'got artist resultset';
+
+ ## Turn on Forced Pool Storage
+ ok my $reliable_artist_rs = $artist_rs->search(undef, {force_pool=>$replicant_names[0]})
+ => 'Created a resultset using force_pool storage';
+
+ ok my $artist = $reliable_artist_rs->find(2)
+ => 'got an artist result via force_pool storage';
+
+ is $debug{storage_type}, 'REPLICANT', "got last query from a replicant: $debug{dsn}";
+}
## Delete the old database files
$replicated->cleanup;
},
'link' => {
data_type => 'integer',
+ is_nullable => 1,
},
);