From 461ef73f0977c95c9452680495bc161618db9227 Mon Sep 17 00:00:00 2001 From: Tatsuo Ishii Date: Sun, 7 Oct 2012 08:36:48 +0900 Subject: [PATCH] Add API for 64-bit large object access. Now users can access up to 4TB large objects (standard 8KB BLCKSZ case). For this purpose new libpq API lo_lseek64, lo_tell64 and lo_truncate64 are added. Also corresponding new backend functions lo_lseek64, lo_tell64 and lo_truncate64 are added. inv_api.c is changed to handle 64-bit offsets. Patch contributed by Nozomi Anzai (backend side) and Yugo Nagata (frontend side, docs, regression tests and example program). Reviewed by Kohei Kaigai. Committed by Tatsuo Ishii with minor editings. --- doc/src/sgml/lobj.sgml | 34 ++- src/backend/libpq/be-fsstubs.c | 101 ++++++- src/backend/storage/large_object/inv_api.c | 47 +-- src/backend/utils/errcodes.txt | 1 + src/include/catalog/pg_proc.h | 6 + src/include/libpq/be-fsstubs.h | 3 + src/include/postgres_ext.h | 5 + src/include/storage/large_object.h | 13 +- src/interfaces/libpq/exports.txt | 3 + src/interfaces/libpq/fe-lobj.c | 239 ++++++++++++++- src/interfaces/libpq/libpq-fe.h | 6 + src/interfaces/libpq/libpq-int.h | 3 + src/test/examples/Makefile | 2 +- src/test/examples/testlo64.c | 320 +++++++++++++++++++++ src/test/regress/input/largeobject.source | 23 ++ src/test/regress/output/largeobject.source | 82 ++++++ 16 files changed, 856 insertions(+), 32 deletions(-) create mode 100644 src/test/examples/testlo64.c diff --git a/doc/src/sgml/lobj.sgml b/doc/src/sgml/lobj.sgml index 291409fde0..66467e00f3 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 6f7e474f67..4bc81ba9f4 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 3adfb159b8..3f5688b939 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 3e04164956..db8ab53af2 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 77a3b413ce..a2da836ff2 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 0c832da6e4..d74ea0eee2 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 b6ebb7aac3..76502de647 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 1fe07ee43a..52f01c6e3c 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 9d95e262be..56d0bb8dc5 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 f3a6d0341c..fb17ac8b1e 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 9d05dd2060..73568ca23c 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 4a6c8fedf2..375821e017 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 bbc6ee1d36..aee5c04407 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 0000000000..6ab7f524ca --- /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 40f40f8c76..4984d78a06 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 55aaf8f2af..74c4772b03 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 -- 2.40.0