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