5 use Data::Dump qw/dump/;
10 ? ( skip_all => 'needs Moose for testing' )
14 use_ok 'DBIx::Class::Storage::DBI::Replicated::Pool';
15 use_ok 'DBIx::Class::Storage::DBI::Replicated::Balancer';
16 use_ok 'DBIx::Class::Storage::DBI::Replicated::Replicant';
17 use_ok 'DBIx::Class::Storage::DBI::Replicated';
19 ## ----------------------------------------------------------------------------
20 ## Build a class to hold all our required testing data and methods.
21 ## ----------------------------------------------------------------------------
25 package DBIx::Class::DBI::Replicated::TestReplication;
30 use base qw/Class::Accessor::Fast/;
32 __PACKAGE__->mk_accessors( qw/schema master_path slave_paths/ );
34 ## Initialize the object
38 my $self = $class->SUPER::new(@_);
40 $self->schema( $self->init_schema );
41 $self->master_path("t/var/DBIxClass.db");
46 ## Get the Schema and set the replication storage type
50 my $schema = DBICTest->init_schema(storage_type=>'::DBI::Replicated');
54 ## Return an Array of ArrayRefs where each ArrayRef is suitable to use for
55 ## $storage->connect_info to be used for connecting replicants.
57 sub generate_replicant_connect_info {
61 } @{$self->slave_paths};
63 return map { [$_,'','',{}] } @dsn;
66 ## Do a 'good enough' replication by copying the master dbfile over each of
71 foreach my $slave (@{$self->slave_paths}) {
72 copy($self->master_path, $slave);
76 ## Cleanup after ourselves. Unlink all gthe slave paths.
80 foreach my $slave (@{$self->slave_paths}) {
86 ## ----------------------------------------------------------------------------
87 ## Create an object and run some tests
88 ## ----------------------------------------------------------------------------
90 ## Thi first bunch of tests are basic, just make sure all the bits are behaving
92 ok my $replicated = DBIx::Class::DBI::Replicated::TestReplication
95 "t/var/DBIxClass_slave1.db",
96 "t/var/DBIxClass_slave2.db",
98 }) => 'Created a replication object';
100 isa_ok $replicated->schema
101 => 'DBIx::Class::Schema';
103 isa_ok $replicated->schema->storage
104 => 'DBIx::Class::Storage::DBI::Replicated';
106 ok $replicated->schema->storage->meta
107 => 'has a meta object';
109 isa_ok $replicated->schema->storage->master
110 => 'DBIx::Class::Storage::DBI';
112 isa_ok $replicated->schema->storage->pool
113 => 'DBIx::Class::Storage::DBI::Replicated::Pool';
115 isa_ok $replicated->schema->storage->balancer
116 => 'DBIx::Class::Storage::DBI::Replicated::Balancer';
118 ok my @replicant_connects = $replicated->generate_replicant_connect_info
119 => 'got replication connect information';
121 ok my @replicated_storages = $replicated->schema->storage->create_replicants(@replicant_connects)
122 => 'Created some storages suitable for replicants';
124 isa_ok $replicated->schema->storage->current_replicant
125 => 'DBIx::Class::Storage::DBI';
127 ok $replicated->schema->storage->pool->has_replicants
128 => 'does have replicants';
130 is $replicated->schema->storage->num_replicants => 2
131 => 'has two replicants';
133 isa_ok $replicated_storages[0]
134 => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
136 isa_ok $replicated_storages[1]
137 => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
139 isa_ok $replicated->schema->storage->replicants->{"t/var/DBIxClass_slave1.db"}
140 => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
142 isa_ok $replicated->schema->storage->replicants->{"t/var/DBIxClass_slave2.db"}
143 => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
145 ## Add some info to the database
149 ->populate('Artist', [
150 [ qw/artistid name/ ],
151 [ 4, "Ozric Tentacles"],
154 ## Make sure all the slaves have the table definitions
156 $replicated->replicate;
158 ## Make sure we can read the data.
160 ok my $artist1 = $replicated->schema->resultset('Artist')->find(4)
164 => 'DBICTest::Artist';
166 is $artist1->name, 'Ozric Tentacles'
167 => 'Found expected name for first result';
169 ## Add some new rows that only the master will have This is because
170 ## we overload any type of write operation so that is must hit the master
175 ->populate('Artist', [
176 [ qw/artistid name/ ],
177 [ 5, "Doom's Children"],
178 [ 6, "Dead On Arrival"],
182 ## Alright, the database 'cluster' is not in a consistent state. When we do
183 ## a read now we expect bad news
185 is $replicated->schema->resultset('Artist')->find(5), undef
186 => 'read after disconnect fails because it uses a replicant which we have neglected to "replicate" yet';
188 ## Make sure all the slaves have the table definitions
189 $replicated->replicate;
191 ## Should find some data now
193 ok my $artist2 = $replicated->schema->resultset('Artist')->find(5)
197 => 'DBICTest::Artist';
199 is $artist2->name, "Doom's Children"
200 => 'Found expected name for first result';
202 ## What happens when we disconnect all the replicants?
204 $replicated->schema->storage->replicants->{"t/var/DBIxClass_slave1.db"}->disconnect;
205 $replicated->schema->storage->replicants->{"t/var/DBIxClass_slave2.db"}->disconnect;
207 ok my $artist3 = $replicated->schema->resultset('Artist')->find(6)
208 => 'Still finding stuff.';
211 => 'DBICTest::Artist';
213 is $artist3->name, "Dead On Arrival"
214 => 'Found expected name for first result';
219 ## ----------------------------------------------------------------------------
220 ## Build a class to hold all our required testing data and methods.
221 ## ----------------------------------------------------------------------------
225 package DBIx::Class::DBI::Replicated::TestReplication;
231 ## Create a constructor
234 my $class = shift @_;
238 db_paths => $params{db_paths},
239 dsns => $class->init_dsns(%params),
240 schema=>$class->init_schema,
247 ## get the DSNs. We build this up from the list of file paths
250 my $class = shift @_;
252 my $db_paths = $params{db_paths};
261 ## get the Schema and set the replication storage type
264 my $class = shift @_;
265 my $schema = DBICTest->init_schema();
266 $schema->storage_type( '::DBI::Replicated' );
271 ## connect the Schema
275 my ($master, @slaves) = @{$self->{dsns}};
276 my $master_connect_info = [$master, '','', {AutoCommit=>1, PrintError=>0}];
279 foreach my $slave (@slaves)
281 my $dbh = shift @{$self->{slaves}}
282 || DBI->connect($slave,"","",{PrintError=>0, PrintWarn=>0});
284 push @{$master_connect_info->[-1]->{slaves_connect_info}},
285 [$dbh, '','',{priority=>10}];
291 ## Keep track of the created slave databases
292 $self->{slaves} = \@slavesob;
296 ->connect(@$master_connect_info);
303 my ($master, @slaves) = @{$self->{db_paths}};
305 foreach my $slave (@slaves) {
306 copy($master, $slave);
310 ## Cleanup afer ourselves.
314 my ($master, @slaves) = @{$self->{db_paths}};
316 foreach my $slave (@slaves) {
321 ## Force a reconnection
325 my $schema = $self->connect;
326 $self->{schema} = $schema;
331 ## ----------------------------------------------------------------------------
332 ## Create an object and run some tests
333 ## ----------------------------------------------------------------------------
337 "t/var/DBIxClass.db",
338 "t/var/DBIxClass_slave1.db",
339 "t/var/DBIxClass_slave2.db",
343 ok my $replicate = DBIx::Class::DBI::Replicated::TestReplication->new(%params)
344 => 'Created a replication object';
346 isa_ok $replicate->{schema}
347 => 'DBIx::Class::Schema';
349 ## Add some info to the database
353 ->populate('Artist', [
354 [ qw/artistid name/ ],
355 [ 4, "Ozric Tentacles"],
358 ## Make sure all the slaves have the table definitions
360 $replicate->replicate;
362 ## Make sure we can read the data.
364 ok my $artist1 = $replicate->{schema}->resultset('Artist')->find(4)
368 => 'DBICTest::Artist';
370 is $artist1->name, 'Ozric Tentacles'
371 => 'Found expected name for first result';
373 ## Add some new rows that only the master will have This is because
374 ## we overload any type of write operation so that is must hit the master
379 ->populate('Artist', [
380 [ qw/artistid name/ ],
381 [ 5, "Doom's Children"],
382 [ 6, "Dead On Arrival"],
386 ## Reconnect the database
387 $replicate->reconnect;
389 ## Alright, the database 'cluster' is not in a consistent state. When we do
390 ## a read now we expect bad news
392 is $replicate->{schema}->resultset('Artist')->find(5), undef
393 => 'read after disconnect fails because it uses slave 1 which we have neglected to "replicate" yet';
395 ## Make sure all the slaves have the table definitions
396 $replicate->replicate;
398 ## Should find some data now
400 ok my $artist2 = $replicate->{schema}->resultset('Artist')->find(5)
404 => 'DBICTest::Artist';
406 is $artist2->name, "Doom's Children"
407 => 'Found expected name for first result';
409 ## What happens when we delete one of the slaves?
411 ok my $slave1 = @{$replicate->{slaves}}[0]
414 ok $slave1->disconnect
415 => 'disconnected slave1';
417 $replicate->reconnect;
419 ok my $artist3 = $replicate->{schema}->resultset('Artist')->find(6)
420 => 'Still finding stuff.';
423 => 'DBICTest::Artist';
425 is $artist3->name, "Dead On Arrival"
426 => 'Found expected name for first result';
428 ## Let's delete all the slaves
430 ok my $slave2 = @{$replicate->{slaves}}[1]
433 ok $slave2->disconnect
434 => 'Disconnected slave2';
436 $replicate->reconnect;
438 ## We expect an error now, since all the slaves are dead
441 $replicate->{schema}->resultset('Artist')->find(4)->name;
444 ok $@ => 'Got error when trying to find artistid 4';
446 ## This should also be an error
449 my $artist4 = $replicate->{schema}->resultset('Artist')->find(7);
452 ok $@ => 'Got read errors after everything failed';
454 ## make sure ->connect_info returns something sane
456 ok $replicate->{schema}->storage->connect_info
457 => 'got something out of ->connect_info';
459 ## Force a connection to the write source for testing.
461 $replicate->{schema}->storage($replicate->{schema}->storage->write_source);
463 ## What happens when we do a find for something that doesn't exist?
465 ok ! $replicate->{schema}->resultset('Artist')->find(666)
466 => 'Correctly did not find a bad artist id';
468 ## Delete the old database files