Skip Menu |

This queue is for tickets about the DBD-Pg CPAN distribution.

Report information
The Basics
Id: 18387
Status: resolved
Priority: 0/
Queue: DBD-Pg

People
Owner: Nobody in particular
Requestors: siracusa [...] mindspring.com
Cc:
AdminCc:

Bug Information
Severity: Critical
Broken in: 1.47
Fixed in: 1.48



Subject: Apparent transaction state corruption leads to silent failures
I expect the following two pieces of code to give the same result. One is SQL, fed directly to Postgres using the psql command-line tool, and the other is Perl code using DBI. In the code, note that I'm intentionally sending statements that I know will fail. This seems to be necessary to reproduce the bug. First, the SQL: DROP TABLE t1; DROP TABLE t2; CREATE TABLE t2 ( id INT PRIMARY KEY ); CREATE TABLE t1 ( id INT PRIMARY KEY, t2_id INT REFERENCES t2 (id) INITIALLY DEFERRED ); BEGIN; INSERT INTO t2 (id) VALUES (1); INSERT INTO t1 (id, t2_id) VALUES (1, 1); COMMIT; BEGIN; INSERT INTO t1 (id, t2_id) VALUES (2, 1); INSERT INTO t1 (id, t2_id) VALUES (3, 2); COMMIT; BEGIN; INSERT INTO t1 (id, t2_id) VALUES (2, 1); ROLLBACK; SELECT * FROM t1; The final SELECT should find only one row: id | t2_id ----+------- 1 | 1 It works as expected. Now the Perl code: use DBI; my $dbh = DBI->connect('dbi:Pg:dbname=test', 'postgres', '...', { PrintError => 1, RaiseError => 0 }); $dbh->do('CREATE TABLE t2 ( id INT PRIMARY KEY )'); $dbh->do('CREATE TABLE t1 ( id INT PRIMARY KEY, t2_id INT REFERENCES t2 (id) INITIALLY DEFERRED )'); $dbh->do('DELETE FROM t1'); $dbh->do('DELETE FROM t2'); $dbh->begin_work; $dbh->do('INSERT INTO t2 (id) VALUES (1)'); $dbh->do('INSERT INTO t1 (id, t2_id) VALUES (1, 1)'); $dbh->commit; $dbh->begin_work; $dbh->do('INSERT INTO t1 (id, t2_id) VALUES (2, 1)'); $dbh->do('INSERT INTO t1 (id, t2_id) VALUES (3, 2)'); $dbh->commit; $dbh->begin_work; $dbh->do('INSERT INTO t1 (id, t2_id) VALUES (2, 1)'); $dbh->rollback; # XXX: This doesn't seem to work my $sth = $dbh->prepare('SELECT * FROM t1'); $sth->execute; while(my $row = $sth->fetchrow_hashref) { print "$row->{'id'}, $row->{'t2_id'}\n"; } $dbh->do('DROP TABLE t1'); $dbh->do('DROP TABLE t2'); $dbh->disconnect; Unfortunately, it prints this: 1, 1 2, 1 Basically, the call to rollback() appears to fail. The row inserted is not rolled back but remains visible. In fact, if you remove the DROP TABLE statements from the end of the Perl script, row id 2 in the t1 table stays in the database even after the script exits. The calls to begin_work() and rollback() around the final insert both give affirmative return values, but actually do nothing. Here's that code extracted, but with fatal errors attached: $dbh->begin_work or die "Could not begin work\n"; $dbh->do('INSERT INTO t1 (id, t2_id) VALUES (2, 1)'); $dbh->rollback or die "Could not roll back\n"; It sails right through that code without dying, but it doesn't actually begin and then roll back a transaction. Here's the DBI trace of that section of code: -> begin_work for DBD::Pg::db (DBI::db=HASH(0x1833704)~0x1869bf0) 1 -> FETCH for DBD::Pg::db (DBI::db=HASH(0x1869bf0)~INNER 'AutoCommit') dbdpg: dbd_db_FETCH (AutoCommit) dbh=25449128 1 <- FETCH= 1 at /usr/local/lib/perl5/site_perl/5.8.8/darwin-2level/DBI.pm line 1655 via new.pl line 34 1 -> STORE for DBD::Pg::db (DBI::db=HASH(0x1869bf0)~INNER 'AutoCommit' 0) dbdpg: dbd_db_STORE (AutoCommit) (0) 1 <- STORE= 1 at /usr/local/lib/perl5/site_perl/5.8.8/darwin-2level/DBI.pm line 1657 via new.pl line 34 1 -> STORE for DBD::Pg::db (DBI::db=HASH(0x1869bf0)~INNER 'BegunWork' 1) dbdpg: dbd_db_STORE (BegunWork) (1) STORE DBI::db=HASH(0x1869bf0) 'BegunWork' => 1 1 <- STORE= 1 at /usr/local/lib/perl5/site_perl/5.8.8/darwin-2level/DBI.pm line 1658 via new.pl line 34 <- begin_work= 1 at new.pl line 34 -> do for DBD::Pg::db (DBI::db=HASH(0x1833704)~0x1869bf0 'INSERT INTO t1 (id, t2_id) VALUES (2, 1)') dbdpg: pg_quickexec (INSERT INTO t1 (id, t2_id) VALUES (2, 1)) dbdpg: _sqlstate <- do= 1 at new.pl line 36 -> rollback for DBD::Pg::db (DBI::db=HASH(0x1833704)~0x1869bf0) dbdpg: dbd_db_rollback (AutoCommit is 0) (BegunWork is 1) dbdpg: dbd_db_txn_status dbdpg: Warning: invalid done_begin turned off <- rollback= 1 at new.pl line 38 Martin J. Evans had this to say: --- I've never used Postgres under Perl but that: Show quoted text
> dbdpg: Warning: invalid done_begin turned off > <- rollback= 1 at new.pl line 38
looked suspicious. From the Postgres code: if (PQTRANS_IDLE == tstatus) { /* Not in a transact if (imp_dbh->done_begin) { /* We think we ARE in a tranaction but we really are not */ - if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "Warning: invalid done_begin turned off\n"); } + if (dbis->debug >= 3) { PerlIO_printf(DBILOGFP, "Warning: invalid done_begin turned off\n"); } imp_dbh->done_begin = 0;ion */ I draw your attention to the comment "We think we ARE in a tranaction but we really are not". The above code seemed to end up being put in an ifdef PG74 later and then even later in a ifdef PGLIBVERSION >= 70400. So, I'd suggest it may be Postgres library version related. --- I'm using Pg 8.1.3, Perl 5.8.8, DBI 1.50, and DBD::Pg 1.47.
From: Jon Jensen
Show quoted text
> Basically, the call to rollback() appears to fail. The row inserted > is not rolled back but > remains visible. In fact, if you remove the DROP TABLE statements > from the end of the Perl > script, row id 2 in the t1 table stays in the database even after the > script exits. > > The calls to begin_work() and rollback() around the final insert both > give affirmative return > values, but actually do nothing.
John, By my reading of DBD::Pg, this is by design when you have AutoCommit on (the default). The DBD::Pg manpage says: According to the DBI specification the default for "AutoCommit" is a true value. In this mode, any change to the database becomes valid immediately. Any "BEGIN", "COMMIT" or "ROLLBACK" statements will be rejected. DBD::Pg implements "AutoCommit" by issuing a "BEGIN" statement immediately before executing a statement, and a "COMMIT" afterwards. So if you have RaiseError off (which your test did), and AutoCommit on (which it also did), then any begin_work, commit, or rollback calls will silently fail. I believe if you check the return status of those calls, the failure will be apparent. Jon
From: siracusa [...] mindspring.com
Show quoted text
> By my reading of DBD::Pg, this is by design when you have AutoCommit > on (the default). The DBD::Pg manpage says: > > According to the DBI specification the default for "AutoCommit" is a > true value. In this mode, any change to the database becomes valid > immediately. Any "BEGIN", "COMMIT" or "ROLLBACK" statements will be > rejected. DBD::Pg implements "AutoCommit" by issuing a "BEGIN" > statement immediately before executing a statement, and a "COMMIT" > afterwards. > > So if you have RaiseError off (which your test did), and AutoCommit > on (which it also did), then any begin_work, commit, or rollback > calls will silently fail. I believe if you check the return status of > those calls, the failure will be apparent.
This is not the case. Of all the calls to begin_work(), commit(), and rollback() in the example code, the only one that does not return a true value is the lone call to commit() after these two inserts: $dbh->do('INSERT INTO t1 (id, t2_id) VALUES (2, 1)'); $dbh->do('INSERT INTO t1 (id, t2_id) VALUES (3, 2)'); and that is by design, since the latter of these two inserts is supposed to trigger a commit() failure. I also think you're misreading the meaning of AutoCommit with respect to begin_work(). From the DBI docs: http://search.cpan.org/dist/DBI/DBI.pm#begin_work Show quoted text
> $rc = $dbh->begin_work or die $dbh->errstr; > > Enable transactions (by turning AutoCommit off) until the next call > to commit or rollback. After the next commit or rollback, AutoCommit > will automatically be turned on again. > > If AutoCommit is already off when begin_work is called then it does > nothing except return an error. If the driver does not support > transactions then when begin_work attempts to set AutoCommit off the > driver will trigger a fatal error.
The whole point of begin_work() is that it begins a transaction when AutoCommit is on. In light of this, look again at the example and it should become clear that it demonstrates obviously incorrect behavior. If you need me to annotate the example further with comments and explanations of return values, let me know.
On Tue Mar 28 09:32:42 2006, JSIRACUSA wrote: Show quoted text
> I also think you're misreading the meaning of AutoCommit with respect to > begin_work(). From the DBI docs: > > http://search.cpan.org/dist/DBI/DBI.pm#begin_work >
> > $rc = $dbh->begin_work or die $dbh->errstr; > > > > Enable transactions (by turning AutoCommit off) until the next call > > to commit or rollback. After the next commit or rollback, AutoCommit > > will automatically be turned on again. > > > > If AutoCommit is already off when begin_work is called then it does > > nothing except return an error. If the driver does not support > > transactions then when begin_work attempts to set AutoCommit off the > > driver will trigger a fatal error.
> > The whole point of begin_work() is that it begins a transaction when > AutoCommit is on. In light of this, look again at the example and it > should become clear that it demonstrates obviously incorrect behavior.
Huh. Yeah, the DBI docs make more sense there. But maybe I was misunderstanding the DBD::Pg docs on the subject. They mention "BEGIN" et al. (SQL statements), and not the begin_work method, which are two different things. If AutoCommit is on, and one does a begin_work, some other statements, and a commit or rollback, is everything after that point auto-committed again until the next begin_work call?
On Tue Mar 28 10:02:29 2006, JONJ wrote: Show quoted text
> Huh. Yeah, the DBI docs make more sense there. But maybe I was > misunderstanding the DBD::Pg docs on the subject. They mention "BEGIN" > et al. (SQL statements), and not the begin_work method, which are two > different things.
They're just describing how DBD::Pg implements AutoCommit=1 mode. Show quoted text
> If AutoCommit is on, and one does a begin_work, some other statements, > and a commit or rollback, is everything after that point auto-committed > again until the next begin_work call?
Yep.
Subject: Re: [Dbdpg-general] [rt.cpan.org #18387] Apparent transaction state corruption leads to silent failures
Date: Wed, 5 Apr 2006 12:33:21 -0000
To: bug-DBD-Pg [...] rt.cpan.org
From: greg [...] turnstep.com
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Show quoted text
> Basically, the call to rollback() appears to fail.
This is now fixed, and will appear in version 1.48. The problem was that the commit after the first error was not setting begin_work off, as the error diverted the script before it could get there. Please test against cvs if possible, or at least against 1.48 when it comes out. Thanks for the report and test script. - -- Greg Sabino Mullane greg@turnstep.com PGP Key: 0x14964AC8 200604050931 http://biglumber.com/x/web?pk=2529DF6AB8F79407E94445B4BC9B906714964AC8 -----BEGIN PGP SIGNATURE----- iD8DBQFEM8b1vJuQZxSWSsgRAiSyAJ9BrDT0VeHXphXsadA6Zz26wl5wCgCePCuQ vFZecYydOuDZ240B08a7AFE= =iaK4 -----END PGP SIGNATURE-----