[DBD::Pg 1/2] Add support for AutoInactiveDestroy. Committer note: Per RT #68893. Changed pg_server_prepare from 1 to 0 in the tests.

dbdpg-commits-gENoVmVU/[email protected] Sat, 16 Nov 2013 21:44:32 -0500
Newsgroups gmane.comp.db.postgresql.dbdpg.cvs
Message-ID <[email protected]>
Committed by David Dick <[email protected]>

Subject: [DBD::Pg 1/2] Add support for AutoInactiveDestroy. Committer note:
Per RT #68893. Changed pg_server_prepare from 1 to 0 in the tests.

---
 Pg.pm         |  7 ++++++
 dbdimp.c      | 10 ++++++++
 t/02attribs.t | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 92 insertions(+), 1 deletion(-)

diff --git a/Pg.pm b/Pg.pm
index f7a6333..9fd8cd3 100644
--- a/Pg.pm
+++ b/Pg.pm
@@ -2244,6 +2244,13 @@ have the child process reconnect to the database with a fresh database handle, o
 rewrite your application not to use forking. See the section on L</Asynchronous Queries> 
 for a way to have your script continue to work while the database is processing a request.
 
+=head3 B<AutoInactiveDestroy> (boolean)
+
+The InactiveDestroy attribute, described above, needs to be explicitly set in the child 
+process after a fork. If the code that performs the fork is in a third party module such 
+as Sys::Syslog, this can present a problem. Use AutoInactiveDestroy to get around this 
+problem.
+
 =head3 B<RaiseError> (boolean, inherited)
 
 Forces errors to always raise an exception. Although it defaults to off, it is recommended that this 
diff --git a/dbdimp.c b/dbdimp.c
index 0792b86..43293a4 100644
--- a/dbdimp.c
+++ b/dbdimp.c
@@ -3737,6 +3737,16 @@ void dbd_st_destroy (SV * sth, imp_sth_t * imp_sth)
 	if (NULL == imp_sth->seg) /* Already been destroyed! */
 		croak("dbd_st_destroy called twice!");
 
+	/* If the AutoInactiveDestroy flag has been set, we go no further */
+	if (DBIc_AIADESTROY(imp_dbh)) {
+		if (TRACE4) {
+			TRC(DBILOGFP, "%sskipping sth destroy due to AutoInactiveDestroy\n", THEADER);
+		}
+		DBIc_IMPSET_off(imp_sth); /* let DBI know we've done it */
+		if (TEND) TRC(DBILOGFP, "%sEnd dbd_st_destroy (AutoInactiveDestroy set)\n", THEADER);
+		return;
+	}
+
 	/* If the InactiveDestroy flag has been set, we go no further */
 	if (DBIc_IADESTROY(imp_dbh)) {
 		if (TRACE4_slow) {
diff --git a/t/02attribs.t b/t/02attribs.t
index ecc540c..b97512a 100644
--- a/t/02attribs.t
+++ b/t/02attribs.t
@@ -18,7 +18,7 @@ my ($helpconnect,$connerror,$dbh) = connect_database();
 if (! $dbh) {
 	plan skip_all => 'Connection to database failed, cannot continue testing';
 }
-plan tests => 249;
+plan tests => 260;
 
 isnt ($dbh, undef, 'Connect to database for handle attributes testing');
 
@@ -101,6 +101,7 @@ a Taint
 a Profile (not tested)
 a ReadOnly
 
+d AutoInactiveDestroy (must be the last one tested)
 d InactiveDestroy (must be the last one tested)
 
 };
@@ -1539,6 +1540,79 @@ $attrib = $dbh->{Active};
 is ($attrib, '', $t);
 
 SKIP: {
+	skip ('Cannot test database handle "AutoInactiveDestroy" on a non-forking system', 8)
+		if $^O =~ /Win/;
+
+	require Test::Simple;
+
+	skip ('Test::Simple version 0.47 or better required for testing of attribute "AutoInactiveDestroy"', 8)
+		if $Test::Simple::VERSION < 0.47;
+
+	# Test of forking. Hang on to your hats
+
+	my $answer = 42;
+	$SQL = "SELECT $answer FROM dbd_pg_test WHERE id > ? LIMIT 1";
+
+	for my $destroy (0,1) {
+
+		$dbh = connect_database({nosetup => 1, AutoCommit => 1 });
+		$dbh->{'AutoInactiveDestroy'} = $destroy;
+		$dbh->{'pg_server_prepare'} = 0;
+		$sth = $dbh->prepare($SQL);
+		$sth->execute(1);
+		$sth->finish();
+
+		# Desired flow: parent test, child test, child kill, parent test
+
+		if (fork) {
+			$t=qq{Parent in fork test is working properly ("AutoInactiveDestroy" = $destroy)};
+			$sth->execute(1);
+			$val = $sth->fetchall_arrayref()->[0][0];
+			is ($val, $answer, $t);
+			# Let the child exit first
+			select(undef,undef,undef,0.3);
+		}
+		else { # Child
+			select(undef,undef,undef,0.1); # Age before beauty
+			exit; ## Calls disconnect via DESTROY unless AutoInactiveDestroy set
+		}
+
+		if ($destroy) {
+			$t=qq{Ping works after the child has exited ("AutoInactiveDestroy" = $destroy)};
+			ok ($dbh->ping(), $t);
+
+			$t='Successful ping returns a SQLSTATE code of 00000 (empty string)';
+			my $state = $dbh->state();
+			is ($state, '', $t);
+
+			$t='Statement handle works after forking';
+			$sth->execute(1);
+			$val = $sth->fetchall_arrayref()->[0][0];
+			is ($val, $answer, $t);
+		}
+		else {
+			$t=qq{Ping fails after the child has exited ("AutoInactiveDestroy" = $destroy)};
+			is ( $dbh->ping(), 0, $t);
+
+			$t='Failed ping returns a SQLSTATE code of 08000';
+			my $state = $dbh->state();
+			is ($state, '08000', $t);
+
+			$t=qq{pg_ping gives an error code of -2 after the child has exited ("AutoInactiveDestroy" = $destroy)};
+			is ( $dbh->pg_ping(), -2,$t);
+			ok ($dbh->disconnect(), 'Disconnect from database');
+		}
+	}
+}
+
+# Disconnect in preparation for the fork tests
+ok ($dbh->disconnect(), 'Disconnect from database');
+
+$t='Database handle attribute "Active" is false after disconnect';
+$attrib = $dbh->{Active};
+is ($attrib, '', $t);
+
+SKIP: {
 	skip ('Cannot test database handle "InactiveDestroy" on a non-forking system', 8)
 		if $^O =~ /Win/;
 
-- 
1.8.4