diff --git a/doc/src/sgml/lobj.sgml b/doc/src/sgml/lobj.sgml index 291409fde0b4bc3dd393701faeb165912f095a23..66467e00f376f274c1845d7553aa6794ecd85128 100644 --- a/doc/src/sgml/lobj.sgml +++ b/doc/src/sgml/lobj.sgml @@ -41,7 +41,7 @@ larger than a single database page into a secondary storage area per table. This makes the large object facility partially obsolete. One remaining advantage of the large object facility is that it allows values - up to 2 GB in size, whereas TOASTed fields can be at + up to 4 TB in size, whereas TOASTed fields can be at most 1 GB. Also, large objects can be randomly modified using a read/write API that is more efficient than performing such operations using TOAST. @@ -237,7 +237,9 @@ int lo_open(PGconn *conn, Oid lobjId, int mode); lo_open returns a (non-negative) large object descriptor for later use in lo_read, lo_write, lo_lseek, - lo_tell, and lo_close. + lo_lseek64, lo_tell, + lo_tell64, lo_truncate, + lo_truncate64, and lo_close. The descriptor is only valid for the duration of the current transaction. On failure, -1 is returned. @@ -312,6 +314,7 @@ int lo_read(PGconn *conn, int fd, char *buf, size_t len); large object descriptor, call int lo_lseek(PGconn *conn, int fd, int offset, int whence); +pg_int64 lo_lseek64(PGconn *conn, int fd, pg_int64 offset, int whence); lo_lseek This function moves the current location pointer for the large object descriptor identified by @@ -321,7 +324,16 @@ int lo_lseek(PGconn *conn, int fd, int offset, int whence); SEEK_CUR (seek from current position), and SEEK_END (seek from object end). The return value is the new location pointer, or -1 on error. + lo_lseek64 lo_lseek64 + is a function for large objects larger than 2GB. pg_int64 + is defined as 8-byte integer type. + + lo_lseek64 is new as of PostgreSQL + 9.3; if this function is run against an older server version, it will + fail and return a negative value. + + @@ -332,9 +344,17 @@ int lo_lseek(PGconn *conn, int fd, int offset, int whence); call int lo_tell(PGconn *conn, int fd); +pg_int64 lo_tell64(PGconn *conn, int fd); lo_tell If there is an error, the return value is negative. + lo_tell64 lo_tell64 is + a function for large objects larger than 2GB. + + + lo_tell64 is new as of PostgreSQL + 9.3; if this function is run against an older server version, it will + fail and return a negative value. @@ -345,6 +365,7 @@ int lo_tell(PGconn *conn, int fd); To truncate a large object to a given length, call int lo_truncate(PGcon *conn, int fd, size_t len); +int lo_truncate64(PGcon *conn, int fd, pg_int64 len); lo_truncate truncates the large object descriptor fd to length len. The @@ -352,6 +373,8 @@ int lo_truncate(PGcon *conn, int fd, size_t len); previous lo_open. If len is greater than the current large object length, the large object is extended with null bytes ('\0'). + lo_truncate64 lo_truncate64 + is a function for large objects larger than 2GB. @@ -359,7 +382,7 @@ int lo_truncate(PGcon *conn, int fd, size_t len); - On success lo_truncate returns + On success lo_truncate and lo_truncate64 returns zero. On error, the return value is negative. @@ -368,6 +391,11 @@ int lo_truncate(PGcon *conn, int fd, size_t len); 8.3; if this function is run against an older server version, it will fail and return a negative value. + + lo_truncate64 is new as of PostgreSQL + 9.3; if this function is run against an older server version, it will + fail and return a negative value. + diff --git a/src/backend/libpq/be-fsstubs.c b/src/backend/libpq/be-fsstubs.c index 6f7e474f675485766c7ffca7312d0f1e0adbb83d..4bc81ba9f4d11230c8ea8c003d0b61ec16c8e813 100644 --- a/src/backend/libpq/be-fsstubs.c +++ b/src/backend/libpq/be-fsstubs.c @@ -39,6 +39,7 @@ #include "postgres.h" #include +#include #include #include @@ -216,7 +217,7 @@ lo_lseek(PG_FUNCTION_ARGS) int32 fd = PG_GETARG_INT32(0); int32 offset = PG_GETARG_INT32(1); int32 whence = PG_GETARG_INT32(2); - int status; + int64 status; if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) ereport(ERROR, @@ -225,9 +226,45 @@ lo_lseek(PG_FUNCTION_ARGS) status = inv_seek(cookies[fd], offset, whence); + if (INT_MAX < status) + { + ereport(ERROR, + (errcode(ERRCODE_BLOB_OFFSET_OVERFLOW), + errmsg("offset overflow: %d", fd))); + PG_RETURN_INT32(-1); + } + PG_RETURN_INT32(status); } + +Datum +lo_lseek64(PG_FUNCTION_ARGS) +{ + int32 fd = PG_GETARG_INT32(0); + int64 offset = PG_GETARG_INT64(1); + int32 whence = PG_GETARG_INT32(2); + MemoryContext currentContext; + int64 status; + + if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("invalid large-object descriptor: %d", fd))); + PG_RETURN_INT64(-1); + } + + Assert(fscxt != NULL); + currentContext = MemoryContextSwitchTo(fscxt); + + status = inv_seek(cookies[fd], offset, whence); + + MemoryContextSwitchTo(currentContext); + + PG_RETURN_INT64(status); +} + Datum lo_creat(PG_FUNCTION_ARGS) { @@ -262,15 +299,48 @@ lo_create(PG_FUNCTION_ARGS) Datum lo_tell(PG_FUNCTION_ARGS) +{ + int32 fd = PG_GETARG_INT32(0); + int64 offset = 0; + + if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("invalid large-object descriptor: %d", fd))); + + offset = inv_tell(cookies[fd]); + + if (INT_MAX < offset) + { + ereport(ERROR, + (errcode(ERRCODE_BLOB_OFFSET_OVERFLOW), + errmsg("offset overflow: %d", fd))); + PG_RETURN_INT32(-1); + } + + PG_RETURN_INT32(offset); +} + + +Datum +lo_tell64(PG_FUNCTION_ARGS) { int32 fd = PG_GETARG_INT32(0); if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) + { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("invalid large-object descriptor: %d", fd))); + PG_RETURN_INT64(-1); + } - PG_RETURN_INT32(inv_tell(cookies[fd])); + /* + * We assume we do not need to switch contexts for inv_tell. That is + * true for now, but is probably more than this module ought to + * assume... + */ + PG_RETURN_INT64(inv_tell(cookies[fd])); } Datum @@ -533,6 +603,33 @@ lo_truncate(PG_FUNCTION_ARGS) PG_RETURN_INT32(0); } +Datum +lo_truncate64(PG_FUNCTION_ARGS) +{ + int32 fd = PG_GETARG_INT32(0); + int64 len = PG_GETARG_INT64(1); + + if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("invalid large-object descriptor: %d", fd))); + + /* Permission checks */ + if (!lo_compat_privileges && + pg_largeobject_aclcheck_snapshot(cookies[fd]->id, + GetUserId(), + ACL_UPDATE, + cookies[fd]->snapshot) != ACLCHECK_OK) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for large object %u", + cookies[fd]->id))); + + inv_truncate(cookies[fd], len); + + PG_RETURN_INT32(0); +} + /* * AtEOXact_LargeObject - * prepares large objects for transaction commit diff --git a/src/backend/storage/large_object/inv_api.c b/src/backend/storage/large_object/inv_api.c index 3adfb159b8baa49939951da0cecf789575652dbe..3f5688b939b0c09b629b3419f531f1b0dd23d6fb 100644 --- a/src/backend/storage/large_object/inv_api.c +++ b/src/backend/storage/large_object/inv_api.c @@ -324,10 +324,10 @@ inv_drop(Oid lobjId) * NOTE: LOs can contain gaps, just like Unix files. We actually return * the offset of the last byte + 1. */ -static uint32 +static uint64 inv_getsize(LargeObjectDesc *obj_desc) { - uint32 lastbyte = 0; + uint64 lastbyte = 0; ScanKeyData skey[1]; SysScanDesc sd; HeapTuple tuple; @@ -368,7 +368,7 @@ inv_getsize(LargeObjectDesc *obj_desc) heap_tuple_untoast_attr((struct varlena *) datafield); pfreeit = true; } - lastbyte = data->pageno * LOBLKSIZE + getbytealen(datafield); + lastbyte = (uint64) data->pageno * LOBLKSIZE + getbytealen(datafield); if (pfreeit) pfree(datafield); } @@ -378,30 +378,31 @@ inv_getsize(LargeObjectDesc *obj_desc) return lastbyte; } -int -inv_seek(LargeObjectDesc *obj_desc, int offset, int whence) +int64 +inv_seek(LargeObjectDesc *obj_desc, int64 offset, int whence) { Assert(PointerIsValid(obj_desc)); switch (whence) { case SEEK_SET: - if (offset < 0) - elog(ERROR, "invalid seek offset: %d", offset); + if (offset < 0 || offset >= MAX_LARGE_OBJECT_SIZE) + elog(ERROR, "invalid seek offset: " INT64_FORMAT, offset); obj_desc->offset = offset; break; case SEEK_CUR: - if (offset < 0 && obj_desc->offset < ((uint32) (-offset))) - elog(ERROR, "invalid seek offset: %d", offset); + if ((offset + obj_desc->offset) < 0 || + (offset + obj_desc->offset) >= MAX_LARGE_OBJECT_SIZE) + elog(ERROR, "invalid seek offset: " INT64_FORMAT, offset); obj_desc->offset += offset; break; case SEEK_END: { - uint32 size = inv_getsize(obj_desc); + int64 pos = inv_getsize(obj_desc) + offset; - if (offset < 0 && size < ((uint32) (-offset))) - elog(ERROR, "invalid seek offset: %d", offset); - obj_desc->offset = size + offset; + if (pos < 0 || pos >= MAX_LARGE_OBJECT_SIZE) + elog(ERROR, "invalid seek offset: " INT64_FORMAT, offset); + obj_desc->offset = pos; } break; default: @@ -410,7 +411,7 @@ inv_seek(LargeObjectDesc *obj_desc, int offset, int whence) return obj_desc->offset; } -int +int64 inv_tell(LargeObjectDesc *obj_desc) { Assert(PointerIsValid(obj_desc)); @@ -422,11 +423,11 @@ int inv_read(LargeObjectDesc *obj_desc, char *buf, int nbytes) { int nread = 0; - int n; - int off; + int64 n; + int64 off; int len; int32 pageno = (int32) (obj_desc->offset / LOBLKSIZE); - uint32 pageoff; + uint64 pageoff; ScanKeyData skey[2]; SysScanDesc sd; HeapTuple tuple; @@ -437,6 +438,9 @@ inv_read(LargeObjectDesc *obj_desc, char *buf, int nbytes) if (nbytes <= 0) return 0; + if ((nbytes + obj_desc->offset) > MAX_LARGE_OBJECT_SIZE) + elog(ERROR, "invalid read request size: %d", nbytes); + open_lo_relation(); ScanKeyInit(&skey[0], @@ -467,7 +471,7 @@ inv_read(LargeObjectDesc *obj_desc, char *buf, int nbytes) * there may be missing pages if the LO contains unwritten "holes". We * want missing sections to read out as zeroes. */ - pageoff = ((uint32) data->pageno) * LOBLKSIZE; + pageoff = ((uint64) data->pageno) * LOBLKSIZE; if (pageoff > obj_desc->offset) { n = pageoff - obj_desc->offset; @@ -560,6 +564,9 @@ inv_write(LargeObjectDesc *obj_desc, const char *buf, int nbytes) if (nbytes <= 0) return 0; + if ((nbytes + obj_desc->offset) > MAX_LARGE_OBJECT_SIZE) + elog(ERROR, "invalid write request size: %d", nbytes); + open_lo_relation(); indstate = CatalogOpenIndexes(lo_heap_r); @@ -718,10 +725,10 @@ inv_write(LargeObjectDesc *obj_desc, const char *buf, int nbytes) } void -inv_truncate(LargeObjectDesc *obj_desc, int len) +inv_truncate(LargeObjectDesc *obj_desc, int64 len) { int32 pageno = (int32) (len / LOBLKSIZE); - int off; + int32 off; ScanKeyData skey[2]; SysScanDesc sd; HeapTuple oldtuple; diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt index 3e04164956303f6fcdef84a15c617e3075956b05..db8ab53af26b7536ea45c8151ea6defe832c25b3 100644 --- a/src/backend/utils/errcodes.txt +++ b/src/backend/utils/errcodes.txt @@ -199,6 +199,7 @@ Section: Class 22 - Data Exception 2200N E ERRCODE_INVALID_XML_CONTENT invalid_xml_content 2200S E ERRCODE_INVALID_XML_COMMENT invalid_xml_comment 2200T E ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION invalid_xml_processing_instruction +22P07 E ERRCODE_BLOB_OFFSET_OVERFLOW blob_offset_overflow Section: Class 23 - Integrity Constraint Violation diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 77a3b413ce525c5c5854f1ce60964c0c6812552c..a2da836ff2cdd02a497571b875ae6d6e66481d44 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -1040,14 +1040,20 @@ DATA(insert OID = 955 ( lowrite PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 23 DESCR("large object write"); DATA(insert OID = 956 ( lo_lseek PGNSP PGUID 12 1 0 0 0 f f f f t f v 3 0 23 "23 23 23" _null_ _null_ _null_ _null_ lo_lseek _null_ _null_ _null_ )); DESCR("large object seek"); +DATA(insert OID = 3170 ( lo_lseek64 PGNSP PGUID 12 1 0 0 0 f f f f t f v 3 0 20 "23 20 23" _null_ _null_ _null_ _null_ lo_lseek64 _null_ _null_ _null_ )); +DESCR("large object seek (64 bit)"); DATA(insert OID = 957 ( lo_creat PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 26 "23" _null_ _null_ _null_ _null_ lo_creat _null_ _null_ _null_ )); DESCR("large object create"); DATA(insert OID = 715 ( lo_create PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 26 "26" _null_ _null_ _null_ _null_ lo_create _null_ _null_ _null_ )); DESCR("large object create"); DATA(insert OID = 958 ( lo_tell PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 23 "23" _null_ _null_ _null_ _null_ lo_tell _null_ _null_ _null_ )); DESCR("large object position"); +DATA(insert OID = 3171 ( lo_tell64 PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 20 "23" _null_ _null_ _null_ _null_ lo_tell64 _null_ _null_ _null_ )); +DESCR("large object position (64 bit)"); DATA(insert OID = 1004 ( lo_truncate PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 23 "23 23" _null_ _null_ _null_ _null_ lo_truncate _null_ _null_ _null_ )); DESCR("truncate large object"); +DATA(insert OID = 3172 ( lo_truncate64 PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 23 "23 20" _null_ _null_ _null_ _null_ lo_truncate64 _null_ _null_ _null_ )); +DESCR("truncate large object (64 bit)"); DATA(insert OID = 959 ( on_pl PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "600 628" _null_ _null_ _null_ _null_ on_pl _null_ _null_ _null_ )); DATA(insert OID = 960 ( on_sl PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "601 628" _null_ _null_ _null_ _null_ on_sl _null_ _null_ _null_ )); diff --git a/src/include/libpq/be-fsstubs.h b/src/include/libpq/be-fsstubs.h index 0c832da6e43e31cf33057a1f99872a6349b4d5c8..d74ea0eee258b9463174d90e9b17472887f6fa35 100644 --- a/src/include/libpq/be-fsstubs.h +++ b/src/include/libpq/be-fsstubs.h @@ -34,8 +34,11 @@ extern Datum lowrite(PG_FUNCTION_ARGS); extern Datum lo_lseek(PG_FUNCTION_ARGS); extern Datum lo_tell(PG_FUNCTION_ARGS); +extern Datum lo_lseek64(PG_FUNCTION_ARGS); +extern Datum lo_tell64(PG_FUNCTION_ARGS); extern Datum lo_unlink(PG_FUNCTION_ARGS); extern Datum lo_truncate(PG_FUNCTION_ARGS); +extern Datum lo_truncate64(PG_FUNCTION_ARGS); /* * compatibility option for access control diff --git a/src/include/postgres_ext.h b/src/include/postgres_ext.h index b6ebb7aac3f0d17ab1629e02d96e89c8f547e047..76502de647b4e94c399904e8f3cd35ec8c522f19 100644 --- a/src/include/postgres_ext.h +++ b/src/include/postgres_ext.h @@ -56,4 +56,9 @@ typedef unsigned int Oid; #define PG_DIAG_SOURCE_LINE 'L' #define PG_DIAG_SOURCE_FUNCTION 'R' +#ifndef NO_PG_INT64 +#define HAVE_PG_INT64 1 +typedef long long int pg_int64; +#endif + #endif diff --git a/src/include/storage/large_object.h b/src/include/storage/large_object.h index 1fe07ee43ac6c32feb0440965f954c177b36ff1c..52f01c6e3c393311dfb852bdd6ff7b944bc87155 100644 --- a/src/include/storage/large_object.h +++ b/src/include/storage/large_object.h @@ -37,7 +37,7 @@ typedef struct LargeObjectDesc Oid id; /* LO's identifier */ Snapshot snapshot; /* snapshot to use */ SubTransactionId subid; /* owning subtransaction ID */ - uint32 offset; /* current seek pointer */ + uint64 offset; /* current seek pointer */ int flags; /* locking info, etc */ /* flag bits: */ @@ -62,7 +62,10 @@ typedef struct LargeObjectDesc * This avoids unnecessary tuple updates caused by partial-page writes. */ #define LOBLKSIZE (BLCKSZ / 4) - +/* + * Maximum byte length for each large object +*/ +#define MAX_LARGE_OBJECT_SIZE INT64CONST(INT_MAX * LOBLKSIZE) /* * Function definitions... @@ -74,10 +77,10 @@ extern Oid inv_create(Oid lobjId); extern LargeObjectDesc *inv_open(Oid lobjId, int flags, MemoryContext mcxt); extern void inv_close(LargeObjectDesc *obj_desc); extern int inv_drop(Oid lobjId); -extern int inv_seek(LargeObjectDesc *obj_desc, int offset, int whence); -extern int inv_tell(LargeObjectDesc *obj_desc); +extern int64 inv_seek(LargeObjectDesc *obj_desc, int64 offset, int whence); +extern int64 inv_tell(LargeObjectDesc *obj_desc); extern int inv_read(LargeObjectDesc *obj_desc, char *buf, int nbytes); extern int inv_write(LargeObjectDesc *obj_desc, const char *buf, int nbytes); -extern void inv_truncate(LargeObjectDesc *obj_desc, int len); +extern void inv_truncate(LargeObjectDesc *obj_desc, int64 len); #endif /* LARGE_OBJECT_H */ diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index 9d95e262be3fbf26731c0926013db56dbc4e00ab..56d0bb8dc5816ee128fc8234f95585aca20ebdea 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -161,3 +161,6 @@ PQping 158 PQpingParams 159 PQlibVersion 160 PQsetSingleRowMode 161 +lo_lseek64 162 +lo_tell64 163 +lo_truncate64 164 diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index f3a6d0341c13ce644a7f1b2f15919385b85d6a6e..fb17ac8b1e9cc47f9f0f8187754fe7aa92772d13 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -37,10 +37,16 @@ #include "libpq-int.h" #include "libpq/libpq-fs.h" /* must come after sys/stat.h */ +/* for ntohl/htonl */ +#include +#include + #define LO_BUFSIZE 8192 static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); +static pg_int64 lo_hton64(pg_int64 host64); +static pg_int64 lo_ntoh64(pg_int64 net64); /* * lo_open @@ -174,6 +180,59 @@ lo_truncate(PGconn *conn, int fd, size_t len) } } +/* + * lo_truncate64 + * truncates an existing large object to the given size + * + * returns 0 upon success + * returns -1 upon failure + */ +#ifdef HAVE_PG_INT64 +int +lo_truncate64(PGconn *conn, int fd, pg_int64 len) +{ + PQArgBlock argv[2]; + PGresult *res; + int retval; + int result_len; + + if (conn == NULL || conn->lobjfuncs == NULL) + { + if (lo_initialize(conn) < 0) + return -1; + } + + if (conn->lobjfuncs->fn_lo_truncate64 == 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("cannot determine OID of function lo_truncate64\n")); + return -1; + } + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + + len = lo_hton64(len); + argv[1].isint = 0; + argv[1].len = 8; + argv[1].u.ptr = (int *) &len; + + res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, + &retval, &result_len, 1, argv, 2); + + if (PQresultStatus(res) == PGRES_COMMAND_OK) + { + PQclear(res); + return retval; + } + else + { + PQclear(res); + return -1; + } +} +#endif /* * lo_read @@ -310,6 +369,63 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) } } +/* + * lo_lseek64 + * change the current read or write location on a large object + * currently, only L_SET is a legal value for whence + * + */ + +#ifdef HAVE_PG_INT64 +pg_int64 +lo_lseek64(PGconn *conn, int fd, pg_int64 offset, int whence) +{ + PQArgBlock argv[3]; + PGresult *res; + pg_int64 retval; + int result_len; + + if (conn == NULL || conn->lobjfuncs == NULL) + { + if (lo_initialize(conn) < 0) + return -1; + } + + if (conn->lobjfuncs->fn_lo_lseek64 == 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("cannot determine OID of function lo_lseek64\n")); + return -1; + } + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + + offset = lo_hton64(offset); + argv[1].isint = 0; + argv[1].len = 8; + argv[1].u.ptr = (int *) &offset; + + argv[2].isint = 1; + argv[2].len = 4; + argv[2].u.integer = whence; + + res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek64, + (int *)&retval, &result_len, 0, argv, 3); + if (PQresultStatus(res) == PGRES_COMMAND_OK) + { + PQclear(res); + return lo_ntoh64((pg_int64)retval); + } + else + { + PQclear(res); + return -1; + } +} +#endif + /* * lo_creat * create a new large object @@ -435,6 +551,52 @@ lo_tell(PGconn *conn, int fd) } } +/* + * lo_tell64 + * returns the current seek location of the large object + * + */ +#ifdef HAVE_PG_INT64 +pg_int64 +lo_tell64(PGconn *conn, int fd) +{ + pg_int64 retval; + PQArgBlock argv[1]; + PGresult *res; + int result_len; + + if (conn == NULL || conn->lobjfuncs == NULL) + { + if (lo_initialize(conn) < 0) + return -1; + } + + if (conn->lobjfuncs->fn_lo_tell64 == 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("cannot determine OID of function lo_tell64\n")); + return -1; + } + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + + res = PQfn(conn, conn->lobjfuncs->fn_lo_tell64, + (int *) &retval, &result_len, 0, argv, 1); + if (PQresultStatus(res) == PGRES_COMMAND_OK) + { + PQclear(res); + return lo_ntoh64((pg_int64) retval); + } + else + { + PQclear(res); + return -1; + } +} +#endif + /* * lo_unlink * delete a file @@ -713,8 +875,11 @@ lo_initialize(PGconn *conn) "'lo_create', " "'lo_unlink', " "'lo_lseek', " + "'lo_lseek64', " "'lo_tell', " + "'lo_tell64', " "'lo_truncate', " + "'lo_truncate64', " "'loread', " "'lowrite') " "and pronamespace = (select oid from pg_catalog.pg_namespace " @@ -765,10 +930,16 @@ lo_initialize(PGconn *conn) lobjfuncs->fn_lo_unlink = foid; else if (strcmp(fname, "lo_lseek") == 0) lobjfuncs->fn_lo_lseek = foid; + else if (strcmp(fname, "lo_lseek64") == 0) + lobjfuncs->fn_lo_lseek64 = foid; else if (strcmp(fname, "lo_tell") == 0) lobjfuncs->fn_lo_tell = foid; + else if (strcmp(fname, "lo_tell64") == 0) + lobjfuncs->fn_lo_tell64 = foid; else if (strcmp(fname, "lo_truncate") == 0) lobjfuncs->fn_lo_truncate = foid; + else if (strcmp(fname, "lo_truncate64") == 0) + lobjfuncs->fn_lo_truncate64 = foid; else if (strcmp(fname, "loread") == 0) lobjfuncs->fn_lo_read = foid; else if (strcmp(fname, "lowrite") == 0) @@ -836,10 +1007,76 @@ lo_initialize(PGconn *conn) free(lobjfuncs); return -1; } - + if (conn->sversion >= 90300) + { + if (lobjfuncs->fn_lo_lseek64 == 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("cannot determine OID of function lo_lseek64\n")); + free(lobjfuncs); + return -1; + } + if (lobjfuncs->fn_lo_tell64 == 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("cannot determine OID of function lo_tell64\n")); + free(lobjfuncs); + return -1; + } + if (lobjfuncs->fn_lo_truncate64 == 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("cannot determine OID of function lo_truncate64\n")); + free(lobjfuncs); + return -1; + } + } /* * Put the structure into the connection control */ conn->lobjfuncs = lobjfuncs; return 0; } + +/* + * lo_hton64 + * converts an 64-bit integer from host byte order to network byte order + */ +static pg_int64 +lo_hton64(pg_int64 host64) +{ + pg_int64 result; + uint32_t h32, l32; + + /* High order half first, since we're doing MSB-first */ + h32 = (uint32_t) (host64 >> 32); + + /* Now the low order half */ + l32 = (uint32_t) (host64 & 0xffffffff); + + result = htonl(l32); + result <<= 32; + result |= htonl(h32); + + return result; +} + +/* + * lo_ntoh64 + * converts an 64-bit integer from network byte order to host byte order + */ +static pg_int64 +lo_ntoh64(pg_int64 net64) +{ + pg_int64 result; + uint32_t h32, l32; + + l32 = (uint32_t) (net64 >> 32); + h32 = (uint32_t) (net64 & 0xffffffff); + + result = ntohl(h32); + result <<= 32; + result |= ntohl(l32); + + return result; +} diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index 9d05dd20605a84ab4c4ec9d61ef8697fb2f3b77e..73568ca23c38b09a3a42b5d31078f179d687688d 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -548,6 +548,12 @@ extern Oid lo_import(PGconn *conn, const char *filename); extern Oid lo_import_with_oid(PGconn *conn, const char *filename, Oid lobjId); extern int lo_export(PGconn *conn, Oid lobjId, const char *filename); +#ifdef HAVE_PG_INT64 +extern pg_int64 lo_lseek64(PGconn *conn, int fd, pg_int64 offset, int whence); +extern pg_int64 lo_tell64(PGconn *conn, int fd); +extern int lo_truncate64(PGconn *conn, int fd, pg_int64 len); +#endif + /* === in fe-misc.c === */ /* Get the version of the libpq library in use */ diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 4a6c8fedf2b6b7f2f72d588c9c504dcc8d26cc0f..375821e017fd7a93c1abc58a0f7fe7adaa354cd9 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -271,8 +271,11 @@ typedef struct pgLobjfuncs Oid fn_lo_create; /* OID of backend function lo_create */ Oid fn_lo_unlink; /* OID of backend function lo_unlink */ Oid fn_lo_lseek; /* OID of backend function lo_lseek */ + Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ Oid fn_lo_tell; /* OID of backend function lo_tell */ + Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ Oid fn_lo_truncate; /* OID of backend function lo_truncate */ + Oid fn_lo_truncate64; /* OID of backend function lo_truncate64 */ Oid fn_lo_read; /* OID of backend function LOread */ Oid fn_lo_write; /* OID of backend function LOwrite */ } PGlobjfuncs; diff --git a/src/test/examples/Makefile b/src/test/examples/Makefile index bbc6ee1d36631258d11dbc656e6abf16333d8fdf..aee5c044075885e15de65ffef5be0951ed538f99 100644 --- a/src/test/examples/Makefile +++ b/src/test/examples/Makefile @@ -14,7 +14,7 @@ override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS) override LDLIBS := $(libpq_pgport) $(LDLIBS) -PROGS = testlibpq testlibpq2 testlibpq3 testlibpq4 testlo +PROGS = testlibpq testlibpq2 testlibpq3 testlibpq4 testlo testlo64 all: $(PROGS) diff --git a/src/test/examples/testlo64.c b/src/test/examples/testlo64.c new file mode 100644 index 0000000000000000000000000000000000000000..6ab7f524caafabb1591598f6111cd042a634ac8e --- /dev/null +++ b/src/test/examples/testlo64.c @@ -0,0 +1,320 @@ +/*------------------------------------------------------------------------- + * + * testlo64.c + * test using large objects with libpq using 64-bit APIs + * + * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/test/examples/testlo64.c + * + *------------------------------------------------------------------------- + */ +#include +#include + +#include +#include +#include +#include + +#include "libpq-fe.h" +#include "libpq/libpq-fs.h" + +#define BUFSIZE 1024 + +/* + * importFile - + * import file "in_filename" into database as large object "lobjOid" + * + */ +static Oid +importFile(PGconn *conn, char *filename) +{ + Oid lobjId; + int lobj_fd; + char buf[BUFSIZE]; + int nbytes, + tmp; + int fd; + + /* + * open the file to be read in + */ + fd = open(filename, O_RDONLY, 0666); + if (fd < 0) + { /* error */ + fprintf(stderr, "can't open unix file\"%s\"\n", filename); + } + + /* + * create the large object + */ + lobjId = lo_creat(conn, INV_READ | INV_WRITE); + if (lobjId == 0) + fprintf(stderr, "can't create large object"); + + lobj_fd = lo_open(conn, lobjId, INV_WRITE); + + /* + * read in from the Unix file and write to the inversion file + */ + while ((nbytes = read(fd, buf, BUFSIZE)) > 0) + { + tmp = lo_write(conn, lobj_fd, buf, nbytes); + if (tmp < nbytes) + fprintf(stderr, "error while reading \"%s\"", filename); + } + + close(fd); + lo_close(conn, lobj_fd); + + return lobjId; +} + +static void +pickout(PGconn *conn, Oid lobjId, pg_int64 start, int len) +{ + int lobj_fd; + char *buf; + int nbytes; + int nread; + pg_int64 pos; + + lobj_fd = lo_open(conn, lobjId, INV_READ); + if (lobj_fd < 0) + fprintf(stderr, "can't open large object %u", lobjId); + + if (lo_tell64(conn, lobj_fd) < 0) + { + fprintf(stderr, "error lo_tell64: %s\n", PQerrorMessage(conn)); + } + + if ((pos=lo_lseek64(conn, lobj_fd, start, SEEK_SET)) < 0) + { + fprintf(stderr, "error lo_lseek64: %s\n", PQerrorMessage(conn)); + return; + } + + fprintf(stderr, "before read: retval of lo_lseek64 : %lld\n", (long long int) pos); + + buf = malloc(len + 1); + + nread = 0; + while (len - nread > 0) + { + nbytes = lo_read(conn, lobj_fd, buf, len - nread); + buf[nbytes] = '\0'; + fprintf(stderr, ">>> %s", buf); + nread += nbytes; + if (nbytes <= 0) + break; /* no more data? */ + } + free(buf); + fprintf(stderr, "\n"); + + pos = lo_tell64(conn, lobj_fd); + fprintf(stderr, "after read: retval of lo_tell64 : %lld\n\n", (long long int) pos); + + lo_close(conn, lobj_fd); +} + +static void +overwrite(PGconn *conn, Oid lobjId, pg_int64 start, int len) +{ + int lobj_fd; + char *buf; + int nbytes; + int nwritten; + int i; + pg_int64 pos; + + lobj_fd = lo_open(conn, lobjId, INV_READ | INV_WRITE); + if (lobj_fd < 0) + fprintf(stderr, "can't open large object %u", lobjId); + + if ((pos=lo_lseek64(conn, lobj_fd, start, SEEK_SET)) < 0) + { + fprintf(stderr, "error lo_lseek64: %s\n", PQerrorMessage(conn)); + return; + } + fprintf(stderr, "before write: retval of lo_lseek64 : %lld\n", (long long int) pos); + + buf = malloc(len + 1); + + for (i = 0; i < len; i++) + buf[i] = 'X'; + buf[i] = '\0'; + + nwritten = 0; + while (len - nwritten > 0) + { + nbytes = lo_write(conn, lobj_fd, buf + nwritten, len - nwritten); + nwritten += nbytes; + if (nbytes <= 0) + { + fprintf(stderr, "\nWRITE FAILED!\n"); + break; + } + } + free(buf); + + pos = lo_tell64(conn, lobj_fd); + fprintf(stderr, "after write: retval of lo_tell64 : %lld\n\n", (long long int) pos); + + lo_close(conn, lobj_fd); +} + +static void +my_truncate(PGconn *conn, Oid lobjId, size_t len) +{ + int lobj_fd; + + lobj_fd = lo_open(conn, lobjId, INV_READ | INV_WRITE); + if (lobj_fd < 0) + fprintf(stderr, "can't open large object %u", lobjId); + + if (lo_truncate64(conn, lobj_fd, len) < 0) + { + fprintf(stderr, "error lo_truncate64: %s\n", PQerrorMessage(conn)); + return; + } + + + fprintf(stderr, "\n"); + lo_close(conn, lobj_fd); +} + + +/* + * exportFile - + * export large object "lobjOid" to file "out_filename" + * + */ +static void +exportFile(PGconn *conn, Oid lobjId, char *filename) +{ + int lobj_fd; + char buf[BUFSIZE]; + int nbytes, + tmp; + int fd; + + /* + * create an inversion "object" + */ + lobj_fd = lo_open(conn, lobjId, INV_READ); + if (lobj_fd < 0) + fprintf(stderr, "can't open large object %u", lobjId); + + /* + * open the file to be written to + */ + fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666); + if (fd < 0) + { /* error */ + fprintf(stderr, "can't open unix file\"%s\"", + filename); + } + + /* + * read in from the Unix file and write to the inversion file + */ + while ((nbytes = lo_read(conn, lobj_fd, buf, BUFSIZE)) > 0) + { + tmp = write(fd, buf, nbytes); + if (tmp < nbytes) + { + fprintf(stderr, "error while writing \"%s\"", + filename); + } + } + + lo_close(conn, lobj_fd); + close(fd); + + return; +} + +static void +exit_nicely(PGconn *conn) +{ + PQfinish(conn); + exit(1); +} + +int +main(int argc, char **argv) +{ + char *in_filename, + *out_filename, + *out_filename2; + char *database; + Oid lobjOid; + PGconn *conn; + PGresult *res; + + if (argc != 5) + { + fprintf(stderr, "Usage: %s database_name in_filename out_filename out_filename2\n", + argv[0]); + exit(1); + } + + database = argv[1]; + in_filename = argv[2]; + out_filename = argv[3]; + out_filename2 = argv[4]; + + /* + * set up the connection + */ + conn = PQsetdb(NULL, NULL, NULL, NULL, database); + + /* check to see that the backend connection was successfully made */ + if (PQstatus(conn) != CONNECTION_OK) + { + fprintf(stderr, "Connection to database failed: %s", + PQerrorMessage(conn)); + exit_nicely(conn); + } + + res = PQexec(conn, "begin"); + PQclear(res); + printf("importing file \"%s\" ...\n", in_filename); +/* lobjOid = importFile(conn, in_filename); */ + lobjOid = lo_import(conn, in_filename); + if (lobjOid == 0) + fprintf(stderr, "%s\n", PQerrorMessage(conn)); + else + { + printf("\tas large object %u.\n", lobjOid); + + printf("picking out bytes 4294967000-4294968000 of the large object\n"); + pickout(conn, lobjOid, 4294967000ULL, 1000); + + printf("overwriting bytes 4294967000-4294968000 of the large object with X's\n"); + overwrite(conn, lobjOid, 4294967000ULL, 1000); + + + printf("exporting large object to file \"%s\" ...\n", out_filename); +/* exportFile(conn, lobjOid, out_filename); */ + if (!lo_export(conn, lobjOid, out_filename)) + fprintf(stderr, "%s\n", PQerrorMessage(conn)); + + printf("truncating to 3294968000 byte\n"); + my_truncate(conn, lobjOid, 3294968000ULL); + + printf("exporting truncated large object to file \"%s\" ...\n", out_filename2); + if (!lo_export(conn, lobjOid, out_filename2)) + fprintf(stderr, "%s\n", PQerrorMessage(conn)); + + } + + res = PQexec(conn, "end"); + PQclear(res); + PQfinish(conn); + return 0; +} diff --git a/src/test/regress/input/largeobject.source b/src/test/regress/input/largeobject.source index 40f40f8c76497e4e67c772f5ec6e46035f164b1a..4984d78a0694490cf53621efc854cde1f26d12b2 100644 --- a/src/test/regress/input/largeobject.source +++ b/src/test/regress/input/largeobject.source @@ -125,6 +125,29 @@ SELECT lo_tell(fd) FROM lotest_stash_values; SELECT lo_close(fd) FROM lotest_stash_values; END; +-- Test 64-bit largelbject functions. +BEGIN; +UPDATE lotest_stash_values SET fd = lo_open(loid, CAST(x'20000' | x'40000' AS integer)); + +SELECT lo_lseek64(fd, 4294967296, 0) FROM lotest_stash_values; +SELECT lowrite(fd, 'offset:4GB') FROM lotest_stash_values; +SELECT lo_tell64(fd) FROM lotest_stash_values; + +SELECT lo_lseek64(fd, -10, 1) FROM lotest_stash_values; +SELECT lo_tell64(fd) FROM lotest_stash_values; +SELECT loread(fd, 10) FROM lotest_stash_values; + +SELECT lo_truncate64(fd, 5000000000) FROM lotest_stash_values; +SELECT lo_lseek64(fd, 0, 2) FROM lotest_stash_values; +SELECT lo_tell64(fd) FROM lotest_stash_values; + +SELECT lo_truncate64(fd, 3000000000) FROM lotest_stash_values; +SELECT lo_lseek64(fd, 0, 2) FROM lotest_stash_values; +SELECT lo_tell64(fd) FROM lotest_stash_values; + +SELECT lo_close(fd) FROM lotest_stash_values; +END; + -- lo_unlink(lobjId oid) returns integer -- return value appears to always be 1 SELECT lo_unlink(loid) from lotest_stash_values; diff --git a/src/test/regress/output/largeobject.source b/src/test/regress/output/largeobject.source index 55aaf8f2afe07384f262f1c4ace0f672350cce66..74c4772b03aba5cc3cdc75a546f3819bb26b62a5 100644 --- a/src/test/regress/output/largeobject.source +++ b/src/test/regress/output/largeobject.source @@ -209,6 +209,88 @@ SELECT lo_close(fd) FROM lotest_stash_values; 0 (1 row) +END; +-- Test 64-bit largelbject functions. +BEGIN; +UPDATE lotest_stash_values SET fd = lo_open(loid, CAST(x'20000' | x'40000' AS integer)); +SELECT lo_lseek64(fd, 4294967296, 0) FROM lotest_stash_values; + lo_lseek64 +------------ + 4294967296 +(1 row) + +SELECT lowrite(fd, 'offset:4GB') FROM lotest_stash_values; + lowrite +--------- + 10 +(1 row) + +SELECT lo_tell64(fd) FROM lotest_stash_values; + lo_tell64 +------------ + 4294967306 +(1 row) + +SELECT lo_lseek64(fd, -10, 1) FROM lotest_stash_values; + lo_lseek64 +------------ + 4294967296 +(1 row) + +SELECT lo_tell64(fd) FROM lotest_stash_values; + lo_tell64 +------------ + 4294967296 +(1 row) + +SELECT loread(fd, 10) FROM lotest_stash_values; + loread +------------ + offset:4GB +(1 row) + +SELECT lo_truncate64(fd, 5000000000) FROM lotest_stash_values; + lo_truncate64 +--------------- + 0 +(1 row) + +SELECT lo_lseek64(fd, 0, 2) FROM lotest_stash_values; + lo_lseek64 +------------ + 5000000000 +(1 row) + +SELECT lo_tell64(fd) FROM lotest_stash_values; + lo_tell64 +------------ + 5000000000 +(1 row) + +SELECT lo_truncate64(fd, 3000000000) FROM lotest_stash_values; + lo_truncate64 +--------------- + 0 +(1 row) + +SELECT lo_lseek64(fd, 0, 2) FROM lotest_stash_values; + lo_lseek64 +------------ + 3000000000 +(1 row) + +SELECT lo_tell64(fd) FROM lotest_stash_values; + lo_tell64 +------------ + 3000000000 +(1 row) + +SELECT lo_close(fd) FROM lotest_stash_values; + lo_close +---------- + 0 +(1 row) + END; -- lo_unlink(lobjId oid) returns integer -- return value appears to always be 1