Author: turnstep
Date: Sat Mar 21 21:01:43 2009
New Revision: 12621

Modified:
   DBD-Pg/trunk/Changes
   DBD-Pg/trunk/Pg.pm
   DBD-Pg/trunk/dbdimp.c
   DBD-Pg/trunk/t/03dbmethod.t

Log:
Allow lo_import and lo_export to work if AutoCommit is enabled.
Per bug #44461, reported by Kynn Jones on the pgsql-general list.
Throw exceptions if any other large object function is called 
when AutoCommit is enabled. Add tests for lo_import and lo_export, 
clean up and expand the large object tests in general, and add 
specific AutoCommit tests.


Modified: DBD-Pg/trunk/Changes
==============================================================================
--- DBD-Pg/trunk/Changes        (original)
+++ DBD-Pg/trunk/Changes        Sat Mar 21 21:01:43 2009
@@ -2,7 +2,10 @@
 
 2.11.9
 
-  - Fix a memory leak when parsing returned arrays [reported by Bálint 
Szilakszi]
+  - Throw an exception for large_object functions called when AutoCommit is 
on, 
+    but allow lo_import and lo_export to work. Reported by Kynn Jones.
+    [GSM] (CPAN bug #44461)
+  - Fix a memory leak when parsing returned arrays. Reported by Bálint 
Szilakszi.
     [GSM] (CPAN bug #44225)
   - Do proper dequoting of boolean arrays [Armando Santos, GSM] (CPAN bug 
#43768)
   - Fix minor bugs in POD docs. [Frank Wiegand] (CPAN bug #44242)

Modified: DBD-Pg/trunk/Pg.pm
==============================================================================
--- DBD-Pg/trunk/Pg.pm  (original)
+++ DBD-Pg/trunk/Pg.pm  Sat Mar 21 21:01:43 2009
@@ -2042,7 +2042,7 @@
 since Postgres version 8.1. For backwards compatibility, however, you should 
 set a valid mode anyway (see L</lo_open> for a list of valid modes).
 
-Upon failure it returns C<undef>.
+Upon failure it returns C<undef>. This function cannot be used if AutoCommit 
is enabled.
 
 =item lo_open
 
@@ -2065,21 +2065,21 @@
 which was active when C<lo_write> was called.
 
 Returns C<undef> upon failure. Note that 0 is a perfectly correct (and common)
-object descriptor!
+object descriptor! This function cannot be used if AutoCommit is enabled.
 
 =item lo_write
 
   $nbytes = $dbh->func($lobj_fd, $buffer, $len, 'lo_write');
 
 Writes C<$len> bytes of c<$buffer> into the large object C<$lobj_fd>. Returns 
the number
-of bytes written and C<undef> upon failure.
+of bytes written and C<undef> upon failure. This function cannot be used if 
AutoCommit is enabled.
 
 =item lo_read
 
   $nbytes = $dbh->func($lobj_fd, $buffer, $len, 'lo_read');
 
 Reads C<$len> bytes into c<$buffer> from large object C<$lobj_fd>. Returns the 
number of
-bytes read and C<undef> upon failure.
+bytes read and C<undef> upon failure. This function cannot be used if 
AutoCommit is enabled.
 
 =item lo_lseek
 
@@ -2087,25 +2087,28 @@
 
 Changes the current read or write location on the large object
 C<$obj_id>. Currently C<$whence> can only be 0 (which is L_SET). Returns the 
current
-location and C<undef> upon failure.
+location and C<undef> upon failure. This function cannot be used if AutoCommit 
is enabled.
 
 =item lo_tell
 
   $loc = $dbh->func($lobj_fd, 'lo_tell');
 
 Returns the current read or write location on the large object C<$lobj_fd> and 
C<undef> upon failure.
+This function cannot be used if AutoCommit is enabled.
 
 =item lo_close
 
   $lobj_fd = $dbh->func($lobj_fd, 'lo_close');
 
 Closes an existing large object. Returns true upon success and false upon 
failure.
+This function cannot be used if AutoCommit is enabled.
 
 =item lo_unlink
 
   $ret = $dbh->func($lobjId, 'lo_unlink');
 
 Deletes an existing large object. Returns true upon success and false upon 
failure.
+This function cannot be used if AutoCommit is enabled.
 
 =item lo_import
 

Modified: DBD-Pg/trunk/dbdimp.c
==============================================================================
--- DBD-Pg/trunk/dbdimp.c       (original)
+++ DBD-Pg/trunk/dbdimp.c       Sat Mar 21 21:01:43 2009
@@ -4088,7 +4088,7 @@
 
 
 /* ================================================================== */
-/* Used to ensure we are in a txn, e.g. the lo_ functions below */
+/* For lo_* functions. Used to ensure we are in a transaction */
 static int pg_db_start_txn (pTHX_ SV * dbh, imp_dbh_t * imp_dbh)
 {
        int status = -1;
@@ -4096,7 +4096,7 @@
        if (TSTART) TRC(DBILOGFP, "%sBegin pg_db_start_txn\n", THEADER);
 
        /* If not autocommit, start a new transaction */
-       if (!imp_dbh->done_begin && !DBIc_has(imp_dbh, DBIcf_AutoCommit)) {
+       if (!imp_dbh->done_begin) {
                status = _result(aTHX_ imp_dbh, "begin");
                if (PGRES_COMMAND_OK != status) {
                        TRACE_PQERRORMESSAGE;
@@ -4107,160 +4107,270 @@
                imp_dbh->done_begin = DBDPG_TRUE;
        }
        if (TEND) TRC(DBILOGFP, "%sEnd pg_db_start_txn\n", THEADER);
+
        return 1;
+
 } /* end of pg_db_start_txn */
 
 
+/* ================================================================== */
+/* For lo_import and lo_export functions. Used to commit or rollback a 
+   transaction, if AutoCommit is on. */
+static int pg_db_end_txn (pTHX_ SV * dbh, imp_dbh_t * imp_dbh, int commit)
+{
+       int status = -1;
+
+       if (TSTART) TRC(DBILOGFP, "%sBegin pg_db_end_txn with %s\n",
+                                       THEADER, commit ? "commit" : 
"rollback");
+
+       /* If not autocommit, start a new transaction */
+       status = _result(aTHX_ imp_dbh, commit ? "commit" : "rollback");
+       if (PGRES_COMMAND_OK != status) {
+               TRACE_PQERRORMESSAGE;
+               pg_error(aTHX_ dbh, status, PQerrorMessage(imp_dbh->conn));
+               if (TEND) TRC(DBILOGFP, "%sEnd pg_db_end_txn (error: status not 
OK for %s)\n",
+                                         THEADER, commit ? "commit" : 
"rollback");
+               return 0;
+       }
+
+       imp_dbh->done_begin = DBDPG_FALSE;
+
+       if (TEND) TRC(DBILOGFP, "%sEnd pg_db_end_txn\n", THEADER);
+       return 1;
+
+} /* end of pg_db_end_txn */
+
 /* Large object functions */
 
 /* ================================================================== */
 unsigned int pg_db_lo_creat (SV * dbh, int mode)
 {
+
        dTHX;
        D_imp_dbh(dbh);
 
        if (TSTART) TRC(DBILOGFP, "%sBegin pg_db_pg_lo_creat (mode: %d)\n", 
THEADER, mode);
 
-       if (!pg_db_start_txn(aTHX_ dbh,imp_dbh))
+       if (DBIc_has(imp_dbh, DBIcf_AutoCommit)) {
+               croak("Cannot call lo_creat when AutoCommit is on");
+       }
+
+       if (!pg_db_start_txn(aTHX_ dbh,imp_dbh)) {
                return 0; /* No other option, because lo_creat returns an Oid */
+       }
 
        if (TLIBPQ) {
                TRC(DBILOGFP, "%slo_creat\n", THEADER);
        }
+
        return lo_creat(imp_dbh->conn, mode); /* 0 on error */
+
 }
 
 /* ================================================================== */
 int pg_db_lo_open (SV * dbh, unsigned int lobjId, int mode)
 {
+
        dTHX;
        D_imp_dbh(dbh);
 
        if (TSTART) TRC(DBILOGFP, "%sBegin pg_db_pg_lo_open (mode: %d objectid: 
%d)\n",
                                        THEADER, mode, lobjId);
 
+       if (DBIc_has(imp_dbh, DBIcf_AutoCommit)) {
+               croak("Cannot call lo_open when AutoCommit is on");
+       }
+
        if (!pg_db_start_txn(aTHX_ dbh,imp_dbh))
                return -2;
 
        if (TLIBPQ) {
                TRC(DBILOGFP, "%slo_open\n", THEADER);
        }
+
        return lo_open(imp_dbh->conn, lobjId, mode); /* -1 on error */
+
 }
 
 /* ================================================================== */
 int pg_db_lo_close (SV * dbh, int fd)
 {
+
        dTHX;
        D_imp_dbh(dbh);
 
        if (TSTART) TRC(DBILOGFP, "%sBegin pg_db_lo_close (fd: %d)\n", THEADER, 
fd);
 
+       if (DBIc_has(imp_dbh, DBIcf_AutoCommit)) {
+               croak("Cannot call lo_close when AutoCommit is on");
+       }
+
+       if (!pg_db_start_txn(aTHX_ dbh,imp_dbh))
+               return -1;
+
        if (TLIBPQ) {
                TRC(DBILOGFP, "%slo_close\n", THEADER);
        }
+
        return lo_close(imp_dbh->conn, fd); /* <0 on error, 0 if ok */
+
 }
 
 /* ================================================================== */
 int pg_db_lo_read (SV * dbh, int fd, char * buf, size_t len)
 {
+
        dTHX;
        D_imp_dbh(dbh);
 
        if (TSTART) TRC(DBILOGFP, "%sBegin pg_db_lo_read (fd: %d length: %d)\n",
                                        THEADER, fd, len);
 
+       if (DBIc_has(imp_dbh, DBIcf_AutoCommit)) {
+               croak("Cannot call lo_read when AutoCommit is on");
+       }
+
+       if (!pg_db_start_txn(aTHX_ dbh,imp_dbh))
+               return -1;
+
        if (TLIBPQ) {
                TRC(DBILOGFP, "%slo_read\n", THEADER);
        }
+
        return lo_read(imp_dbh->conn, fd, buf, len); /* bytes read, <0 on error 
*/
+
 }
 
 /* ================================================================== */
 int pg_db_lo_write (SV * dbh, int fd, char * buf, size_t len)
 {
+
        dTHX;
        D_imp_dbh(dbh);
 
        if (TSTART) TRC(DBILOGFP, "%sBegin pg_db_lo_write (fd: %d length: 
%d)\n",
                                        THEADER, fd, len);
 
+       if (DBIc_has(imp_dbh, DBIcf_AutoCommit)) {
+               croak("Cannot call lo_write when AutoCommit is on");
+       }
+
+       if (!pg_db_start_txn(aTHX_ dbh,imp_dbh))
+               return -1;
+
        if (TLIBPQ) {
                TRC(DBILOGFP, "%slo_write\n", THEADER);
        }
+
        return lo_write(imp_dbh->conn, fd, buf, len); /* bytes written, <0 on 
error */
+
 }
 
 /* ================================================================== */
 int pg_db_lo_lseek (SV * dbh, int fd, int offset, int whence)
 {
+
        dTHX;
        D_imp_dbh(dbh);
 
        if (TSTART) TRC(DBILOGFP, "%sBegin pg_db_lo_lseek (fd: %d offset: %d 
whence: %d)\n",
                                        THEADER, fd, offset, whence);
 
+       if (DBIc_has(imp_dbh, DBIcf_AutoCommit)) {
+               croak("Cannot call lo_lseek when AutoCommit is on");
+       }
+
+       if (!pg_db_start_txn(aTHX_ dbh,imp_dbh))
+               return -1;
+
        if (TLIBPQ) {
                TRC(DBILOGFP, "%slo_lseek\n", THEADER);
        }
+
        return lo_lseek(imp_dbh->conn, fd, offset, whence); /* new position, -1 
on error */
+
 }
 
 /* ================================================================== */
 int pg_db_lo_tell (SV * dbh, int fd)
 {
+
        dTHX;
        D_imp_dbh(dbh);
 
        if (TSTART) TRC(DBILOGFP, "%sBegin pg_db_lo_tell (fd: %d)\n", THEADER, 
fd);
 
+       if (DBIc_has(imp_dbh, DBIcf_AutoCommit)) {
+               croak("Cannot call lo_tell when AutoCommit is on");
+       }
+
+       if (!pg_db_start_txn(aTHX_ dbh,imp_dbh))
+               return -1;
+
        if (TLIBPQ) {
                TRC(DBILOGFP, "%slo_tell\n", THEADER);
        }
+
        return lo_tell(imp_dbh->conn, fd); /* current position, <0 on error */
+
 }
 
 /* ================================================================== */
 int pg_db_lo_unlink (SV * dbh, unsigned int lobjId)
 {
+
        dTHX;
        D_imp_dbh(dbh);
 
        if (TSTART) TRC(DBILOGFP, "%sBegin pg_db_lo_unlink (objectid: %d)\n", 
THEADER, lobjId);
 
+       if (DBIc_has(imp_dbh, DBIcf_AutoCommit)) {
+               croak("Cannot call lo_unlink when AutoCommit is on");
+       }
+
        if (!pg_db_start_txn(aTHX_ dbh,imp_dbh))
-               return -2;
+               return -1;
 
        if (TLIBPQ) {
                TRC(DBILOGFP, "%slo_unlink\n", THEADER);
        }
+
        return lo_unlink(imp_dbh->conn, lobjId); /* 1 on success, -1 on failure 
*/
+
 }
 
 /* ================================================================== */
 unsigned int pg_db_lo_import (SV * dbh, char * filename)
 {
+
+       Oid loid;
        dTHX;
        D_imp_dbh(dbh);
 
        if (TSTART) TRC(DBILOGFP, "%sBegin pg_db_lo_import (filename: %s)\n", 
THEADER, filename);
 
-       if (!pg_db_start_txn(aTHX_ dbh,imp_dbh)) {
-               if (TRACE7) TRC(DBILOGFP, "%sCannot pg_db_log_import unless in 
a transaction\n",
-                                               THEADER);
+       if (!pg_db_start_txn(aTHX_ dbh,imp_dbh))
                return 0; /* No other option, because lo_import returns an Oid 
*/
-       }
 
        if (TLIBPQ) {
                TRC(DBILOGFP, "%slo_import\n", THEADER);
        }
-       return lo_import(imp_dbh->conn, filename); /* 0 on error */
+       loid = lo_import(imp_dbh->conn, filename); /* 0 on error */
+
+       if (DBIc_has(imp_dbh, DBIcf_AutoCommit)) {
+               if (!pg_db_end_txn(aTHX_ dbh, imp_dbh, 0==loid ? 0 : 1))
+                       return 0;
+       }
+
+       return loid;
+
 }
 
 /* ================================================================== */
 int pg_db_lo_export (SV * dbh, unsigned int lobjId, char * filename)
 {
+
+       Oid loid;
        dTHX;
        D_imp_dbh(dbh);
 
@@ -4273,7 +4383,13 @@
        if (TLIBPQ) {
                TRC(DBILOGFP, "%slo_export\n", THEADER);
        }
-       return lo_export(imp_dbh->conn, lobjId, filename); /* 1 on success, -1 
on failure */
+       loid = lo_export(imp_dbh->conn, lobjId, filename); /* 1 on success, -1 
on failure */
+       if (DBIc_has(imp_dbh, DBIcf_AutoCommit)) {
+               if (!pg_db_end_txn(aTHX_ dbh, imp_dbh, -1==loid ? 0 : 1))
+                       return -1;
+       }
+
+       return loid;
 }
 
 

Modified: DBD-Pg/trunk/t/03dbmethod.t
==============================================================================
--- DBD-Pg/trunk/t/03dbmethod.t (original)
+++ DBD-Pg/trunk/t/03dbmethod.t Sat Mar 21 21:01:43 2009
@@ -5,8 +5,6 @@
 ## "data_sources" (see 04misc.t)
 ## "disconnect" (see 01connect.t)
 ## "take_imp_data"
-## "lo_import"
-## "lo_export"
 ## "pg_savepoint", "pg_release", "pg_rollback_to" (see 20savepoints.t)
 ## "pg_putline", "pg_getline", "pg_endcopy" (see 07copy.t)
 
@@ -26,7 +24,7 @@
 if (! defined $dbh) {
        plan skip_all => 'Connection to database failed, cannot continue 
testing';
 }
-plan tests => 479;
+plan tests => 507;
 
 isnt ($dbh, undef, 'Connect to database for database handle method testing');
 
@@ -1297,27 +1295,33 @@
 
 $t='DB handle method "lo_creat" returns a valid descriptor for reading';
 $dbh->{AutoCommit}=1; $dbh->{AutoCommit}=0; ## Catch error where not in begin
+
 my ($R,$W) = ($dbh->{pg_INV_READ}, $dbh->{pg_INV_WRITE});
 my $RW = $R|$W;
 my $object = $dbh->func($R, 'lo_creat');
 like ($object, qr/^\d+$/o, $t);
+isnt ($object, 0, $t);
 
 $t='DB handle method "lo_creat" returns a valid descriptor for writing';
 $object = $dbh->func($W, 'lo_creat');
 like ($object, qr/^\d+$/o, $t);
+isnt ($object, 0, $t);
 
 $t='DB handle method "lo_open" returns a valid descriptor for writing';
 my $handle = $dbh->func($object, $W, 'lo_open');
 like ($handle, qr/^\d+$/o, $t);
+isnt ($object, -1, $t);
 
 $t='DB handle method "lo_lseek" works when writing';
 $result = $dbh->func($handle, 0, 0, 'lo_lseek');
 is ($result, 0, $t);
+isnt ($object, -1, $t);
 
 $t='DB handle method "lo_write" works';
 my $buf = 'tangelo mulberry passionfruit raspberry plantain' x 500;
 $result = $dbh->func($handle, $buf, length($buf), 'lo_write');
 is ($result, length($buf), $t);
+cmp_ok ($object, '>', 0, $t);
 
 $t='DB handle method "lo_close" works after write';
 $result = $dbh->func($handle, 'lo_close');
@@ -1327,6 +1331,7 @@
 $t='DB handle method "lo_open" returns a valid descriptor for reading';
 $handle = $dbh->func($object, $R, 'lo_open');
 like ($handle, qr/^\d+$/o, $t);
+cmp_ok ($handle, 'eq', 0, $t);
 
 $t='DB handle method "lo_lseek" works when reading';
 $result = $dbh->func($handle, 11, 0, 'lo_lseek');
@@ -1350,7 +1355,140 @@
 
 $t='DB handle method "lo_unlink" works';
 $result = $dbh->func($object, 'lo_unlink');
-ok ($result, $t);
+is ($result, 1, $t);
+
+$t='DB handle method "lo_unlink" fails when called second time';
+$result = $dbh->func($object, 'lo_unlink');
+ok (!$result, $t);
+$dbh->rollback();
+
+SKIP: {
+
+       eval {
+               require File::Temp;
+       };
+       $@ and skip ('Must have File::Temp to test lo_import and lo_export', 8);
+
+       $t='DB handle method "lo_import" works';
+       my ($fh,$filename) = File::Temp::tmpnam();
+       print $fh "abc\ndef";
+       close $fh or warn 'Failed to close temporary file';
+       my $handle = $dbh->func($filename, 'lo_import');
+       my $objid = $handle;
+       ok ($handle, $t);
+
+       $t='DB handle method "lo_import" inserts correct data';
+       $SQL = "SELECT data FROM pg_largeobject where loid = $handle";
+       $info = $dbh->selectall_arrayref($SQL)->[0][0];
+       is_deeply($info, "abc\ndef", $t);
+
+       $t='DB handle method "lo_open" works after "lo_insert"';
+       $handle = $dbh->func($handle, $R, 'lo_open');
+       like ($handle, qr/^\d+$/o, $t);
+
+       $t='DB handle method "lo_read" returns correct data after "lo_import"';
+       my $data = '';
+       $result = $dbh->func($handle, $data, 100, 'lo_read');
+       is ($result, 7, $t);
+       is ($data, "abc\ndef", $t);
+
+       $t='DB handle method "lo_export" works';
+       ($fh,$filename) = File::Temp::tmpnam();
+       $result = $dbh->func($objid, $filename, 'lo_export');
+       ok(-e $filename, $t);
+       seek($fh,0,1);
+       seek($fh,0,0);
+       $result = read $fh, $data, 10;
+       is ($result, 7, $t);
+       is ($data, "abc\ndef", $t);
+       close $fh or warn 'Could not close tempfile';
+}
+
+## Same tests, but with AutoCommit on
+$dbh->{AutoCommit}=1;
+
+$t='DB handle method "lo_creat" fails when AutoCommit on';
+eval {
+       $dbh->func($W, 'lo_creat');
+};
+like($@, qr{lo_creat when AutoCommit is on}, $t);
+
+$t='DB handle method "lo_open" fails with AutoCommit on';
+eval {
+       $dbh->func($object, $W, 'lo_open');
+};
+like($@, qr{lo_open when AutoCommit is on}, $t);
+
+$t='DB handle method "lo_read" fails with AutoCommit on';
+eval {
+       $dbh->func($object, $data, 0, 'lo_read');
+};
+like($@, qr{lo_read when AutoCommit is on}, $t);
+
+$t='DB handle method "lo_lseek" fails with AutoCommit on';
+eval {
+       $dbh->func($handle, 0, 0, 'lo_lseek');
+};
+like($@, qr{lo_lseek when AutoCommit is on}, $t);
+
+$t='DB handle method "lo_write" fails with AutoCommit on';
+$buf = 'tangelo mulberry passionfruit raspberry plantain' x 500;
+eval {
+       $dbh->func($handle, $buf, length($buf), 'lo_write');
+};
+like($@, qr{lo_write when AutoCommit is on}, $t);
+
+$t='DB handle method "lo_close" fails with AutoCommit on';
+eval {
+       $dbh->func($handle, 'lo_close');
+};
+like($@, qr{lo_close when AutoCommit is on}, $t);
+
+$t='DB handle method "lo_tell" fails with AutoCommit on';
+eval {
+       $dbh->func($handle, 'lo_tell');
+};
+like($@, qr{lo_tell when AutoCommit is on}, $t);
+
+$t='DB handle method "lo_unlink" fails with AutoCommit on';
+eval {
+       $dbh->func($object, 'lo_unlink');
+};
+like($@, qr{lo_unlink when AutoCommit is on}, $t);
+
+
+SKIP: {
+
+       eval {
+               require File::Temp;
+       };
+       $@ and skip ('Must have File::Temp to test lo_import and lo_export', 2);
+
+       $t='DB handle method "lo_import" works (AutoCommit on)';
+       my ($fh,$filename) = File::Temp::tmpnam();
+       print $fh "abc\ndef";
+       close $fh or warn 'Failed to close temporary file';
+       my $handle = $dbh->func($filename, 'lo_import');
+       my $objid = $handle;
+       ok ($handle, $t);
+
+       $t='DB handle method "lo_import" inserts correct data (AutoCommit on)';
+       $SQL = "SELECT data FROM pg_largeobject where loid = $handle";
+       $info = $dbh->selectall_arrayref($SQL)->[0][0];
+       is_deeply($info, "abc\ndef", $t);
+
+       $t='DB handle method "lo_export" works (AutoCommit on)';
+       ($fh,$filename) = File::Temp::tmpnam();
+       $result = $dbh->func($objid, $filename, 'lo_export');
+       ok(-e $filename, $t);
+       seek($fh,0,1);
+       seek($fh,0,0);
+       $result = read $fh, $data, 10;
+       is ($result, 7, $t);
+       is ($data, "abc\ndef", $t);
+       close $fh or warn 'Could not close tempfile';
+}
+$dbh->{AutoCommit} = 0;
 
 #
 # Test of the "pg_notifies" database handle method

Reply via email to