Permalink
Browse files

Merge pull request #55 from nihen/mode

implement mode
  • Loading branch information...
nekokak committed Dec 26, 2011
2 parents 890dc38 + 4cb250b commit 1f14e193ddf139274b2079502cfe54a384bbd0c5
Showing with 546 additions and 23 deletions.
  1. +1 −0 Makefile.PL
  2. +101 −23 lib/Teng.pm
  3. +138 −0 t/001_basic/030_reconnect_ping.t
  4. +162 −0 t/001_basic/031_reconnect_fixup.t
  5. +144 −0 t/001_basic/032_reconnect_no_ping.t
View
@@ -20,6 +20,7 @@ test_requires 'Test::More' => '0.96';
test_requires 'Test::SharedFork' => 0.15;
test_requires 'Test::mysqld' if $Module::Install::AUTHOR;
test_requires 'Test::postgresql' if $Module::Install::AUTHOR;
+test_requires 'Test::Mock::Guard';
tests 't/*.t t/*/*.t';
View
@@ -19,6 +19,7 @@ use Class::Accessor::Lite
sql_builder
sql_comment
owner_pid
+ mode
)]
;
@@ -48,6 +49,7 @@ sub new {
my $self = bless {
schema_class => "$class\::Schema",
owner_pid => $$,
+ mode => 'ping',
%args,
}, $class;
@@ -123,9 +125,7 @@ sub _on_connect_do {
sub reconnect {
my $self = shift;
- if ($self->in_transaction_check) {
- Carp::confess("Detected disconnected database during a transaction. Refusing to proceed");
- }
+ $self->in_transaction_check;
my $dbh = $self->{dbh};
@@ -187,7 +187,10 @@ sub _verify_pid {
$self->reconnect;
}
elsif ( my $dbh = $self->{dbh} ) {
- if ( !$dbh->FETCH('Active') || !$dbh->ping ) {
+ if ( !$dbh->FETCH('Active') ) {
+ $self->reconnect;
+ }
+ elsif ( $self->mode eq 'ping' && !$dbh->ping) {
$self->reconnect;
}
}
@@ -200,35 +203,61 @@ sub dbh {
$self->{dbh};
}
+sub connected {
+ my $self = shift;
+ my $dbh = $self->{dbh};
+ return $self->owner_pid && $dbh->ping;
+}
+
sub _execute {
my ($self, $sql, $binds) = @_;
+ if ($ENV{TENG_SQL_COMMENT} || $self->sql_comment) {
+ my $i = 1; # optimize, as we would *NEVER* be called
+ while ( my (@caller) = caller($i++) ) {
+ next if ( $caller[0]->isa( __PACKAGE__ ) );
+ my $comment = "$caller[1] at line $caller[2]";
+ $comment =~ s/\*\// /g;
+ $sql = "/* $comment */\n$sql";
+ last;
+ }
+ }
+
my $sth;
- eval {
- if ($ENV{TENG_SQL_COMMENT} || $self->sql_comment) {
- my $i = 1; # optimize, as we would *NEVER* be called
- while ( my (@caller) = caller($i++) ) {
- next if ( $caller[0]->isa( __PACKAGE__ ) );
- my $comment = "$caller[1] at line $caller[2]";
- $comment =~ s/\*\// /g;
- $sql = "/* $comment */\n$sql";
- last;
+ eval { $sth = $self->__execute($sql, $binds) };
+
+ if ($@) {
+ if ( $self->mode eq 'fixup' ) {
+ if ( $self->connected ) {
+ $self->handle_error($sql, $binds, $@);
+ }
+ $self->reconnect;
+ eval { $sth = $self->__execute($sql, $binds) };
+ if ($@) {
+ $self->handle_error($sql, $binds, $@);
}
}
- $sth = $self->dbh->prepare($sql);
- my $i = 1;
- for my $v ( @{ $binds || [] } ) {
- $sth->bind_param( $i++, ref($v) ? @$v : $v );
+ else {
+ $self->handle_error($sql, $binds, $@);
}
- $sth->execute();
- };
- if ($@) {
- $self->handle_error($sql, $binds, $@);
}
return $sth;
}
+sub __execute {
+ my ($self, $sql, $binds) = @_;
+
+ my $sth = $self->dbh->prepare($sql);
+ my $i = 1;
+ for my $v ( @{ $binds || [] } ) {
+ $sth->bind_param( $i++, ref($v) ? @$v : $v );
+ }
+ $sth->execute();
+
+ return $sth;
+}
+
sub _last_insert_id {
my ($self, $table_name) = @_;
@@ -406,11 +435,42 @@ sub in_transaction_check {
}
sub txn_scope {
+ my $self = shift;
my @caller = caller();
- $_[0]->txn_manager->txn_scope(caller => \@caller);
+
+ my $scope;
+ if ( $self->mode eq 'fixup' ) {
+ eval { $scope = $self->txn_manager->txn_scope(caller => \@caller) };
+ if ( $@ ) {
+ if ( $self->connected ) {
+ die $@;
+ }
+ $self->reconnect;
+ $scope = $self->txn_manager->txn_scope(caller => \@caller);
+ }
+ }
+ else {
+ $scope = $self->txn_manager->txn_scope(caller => \@caller);
+ }
+ return $scope;
}
-sub txn_begin { $_[0]->txn_manager->txn_begin }
+sub txn_begin {
+ my $self = shift;
+ if ( $self->mode eq 'fixup' ) {
+ eval { $self->txn_manager->txn_begin };
+ if ( $@ ) {
+ if ( $self->connected ) {
+ die $@;
+ }
+ $self->reconnect;
+ $self->txn_manager->txn_begin;
+ }
+ }
+ else {
+ $self->txn_manager->txn_begin;
+ }
+}
sub txn_rollback { $_[0]->txn_manager->txn_rollback }
sub txn_commit { $_[0]->txn_manager->txn_commit }
sub txn_end { $_[0]->txn_manager->txn_end }
@@ -691,6 +751,24 @@ You must pass C<connect_info> or C<dbh> to the constructor.
Specifies the database handle to use.
+=item * C<mode>
+
+=over
+
+=item * C<ping(default)>
+
+reconnect at dbh->ping fail each execute.
+
+=item * C<fixup>
+
+reconnect at fail execute.
+
+=item * C<no_ping>
+
+no auto reconnect.
+
+=back
+
=item * C<schema>
Specifies the Teng::Schema instance to use.
@@ -0,0 +1,138 @@
+use t::Utils;
+use Test::More;
+use Mock::Basic;
+use Test::Mock::Guard qw/mock_guard/;
+
+unlink 'test.db';
+my $dbh = t::Utils->setup_dbh('test.db');
+my $db = Mock::Basic->new({dbh => $dbh, mode => 'ping'});
+$db->setup_test_db;
+
+subtest 'ping_reconnect' => sub {
+ $db->reconnect;
+
+ my $row;
+ my $old_dbh = $db->dbh;
+ eval {
+ my $guard; $guard = mock_guard('DBI::db' => +{ping => sub { undef $guard; return 0 } });
+ $row = $db->insert('mock_basic',{
+ name => 'perl',
+ });
+ };
+ ok(!$@);
+
+ isnt($old_dbh, $db->dbh);
+ is_deeply($db->single('mock_basic', { id => $row->id })->get_columns, $row->get_columns);
+};
+
+subtest 'ping_reconnect_at_txn_begin' => sub {
+ $db->reconnect;
+ my $old_dbh = $db->dbh;
+ eval {
+ my $guard; $guard = mock_guard('DBI::db' => +{ping => sub { undef $guard; return 0 } });
+ $db->txn_begin;
+ };
+ ok(!$@);
+ isnt($old_dbh, $db->dbh);
+
+ my $row = $db->insert('mock_basic',{
+ name => 'python',
+ });
+ $db->txn_commit;
+
+ is_deeply($db->single('mock_basic', { id => $row->id })->get_columns, $row->get_columns);
+};
+
+subtest 'ping_reconnect_at_txn_scope' => sub {
+ $db->reconnect;
+ my $old_dbh = $db->dbh;
+ my $scope;
+ eval {
+ my $guard; $guard = mock_guard('DBI::db' => +{ping => sub { undef $guard; return 0 } });
+ $scope = $db->txn_scope;
+ };
+ ok(!$@);
+ isnt($old_dbh, $db->dbh);
+
+ my $row = $db->insert('mock_basic',{
+ name => 'ruby',
+ });
+ $scope->commit;
+
+ is_deeply($db->single('mock_basic', { id => $row->id })->get_columns, $row->get_columns);
+};
+
+subtest 'ping_reconnect_at_after_txn_begin' => sub {
+ $db->reconnect;
+ $db->txn_begin;
+
+ my $row;
+ eval {
+ my $guard; $guard = mock_guard('DBI::db' => +{ping => sub { undef $guard; return 0 } });
+ $row = $db->insert('mock_basic',{
+ name => 'c++',
+ });
+ };
+ like $@, qr/Detected transaction during a connect operation \(last known transaction at/;
+ ok(!$row);
+
+ $db->txn_rollback;
+};
+
+subtest 'ping_reconnect_at_after_txn_scope' => sub {
+ $db->reconnect;
+ my $scope = $db->txn_scope;
+
+ my $row;
+ eval {
+ my $guard; $guard = mock_guard('DBI::db' => +{ping => sub { undef $guard; return 0 } });
+ $row = $db->insert('mock_basic',{
+ name => 'golang',
+ });
+ };
+ like $@, qr/Detected transaction during a connect operation \(last known transaction at/;
+ ok(!$row);
+ $scope->rollback;
+};
+
+subtest 'ping_reconnect_at_txn_commit' => sub {
+ $db->reconnect;
+ $db->txn_begin;
+
+ my $row = $db->insert('mock_basic',{
+ name => 'basic',
+ });
+
+ eval {
+ my $guard; $guard = mock_guard('DBI::db' => +{ping => sub { undef $guard; return 0 } });
+ $db->txn_commit;
+ };
+ like $@, qr/Detected transaction during a connect operation \(last known transaction at/;
+ $db->txn_rollback;
+
+ ok(!$db->single('mock_basic', { id => $row->id }));
+};
+
+subtest 'ping_reconnect_at_txn_scope_commit' => sub {
+ $db->reconnect;
+ my $row;
+ {
+ my $scope = $db->txn_scope;
+
+ $row = $db->insert('mock_basic',{
+ name => 'cobol',
+ });
+
+ eval {
+ my $guard; $guard = mock_guard('DBIx::TransactionManager::ScopeGuard' => +{commit => sub { undef $guard; die('disconnect dbh') } });
+ $scope->commit;
+ };
+ like $@, qr/disconnect dbh/;
+ $scope->rollback;
+ }
+
+ ok(!$db->single('mock_basic', { id => $row->id }));
+};
+
+unlink 'test.db';
+done_testing;
Oops, something went wrong.

0 comments on commit 1f14e19

Please sign in to comment.