]> granicus.if.org Git - postgresql/commitdiff
Add API for 64-bit large object access. Now users can access up to
authorTatsuo Ishii <ishii@postgresql.org>
Sat, 6 Oct 2012 23:36:48 +0000 (08:36 +0900)
committerTatsuo Ishii <ishii@postgresql.org>
Sat, 6 Oct 2012 23:36:48 +0000 (08:36 +0900)
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.

16 files changed:
doc/src/sgml/lobj.sgml
src/backend/libpq/be-fsstubs.c
src/backend/storage/large_object/inv_api.c
src/backend/utils/errcodes.txt
src/include/catalog/pg_proc.h
src/include/libpq/be-fsstubs.h
src/include/postgres_ext.h
src/include/storage/large_object.h
src/interfaces/libpq/exports.txt
src/interfaces/libpq/fe-lobj.c
src/interfaces/libpq/libpq-fe.h
src/interfaces/libpq/libpq-int.h
src/test/examples/Makefile
src/test/examples/testlo64.c [new file with mode: 0644]
src/test/regress/input/largeobject.source
src/test/regress/output/largeobject.source

index 291409fde0b4bc3dd393701faeb165912f095a23..66467e00f376f274c1845d7553aa6794ecd85128 100644 (file)
@@ -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 <acronym>TOAST</acronym>ed fields can be at
+    up to 4 TB in size, whereas <acronym>TOAST</acronym>ed 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
     <acronym>TOAST</acronym>.
@@ -237,7 +237,9 @@ int lo_open(PGconn *conn, Oid lobjId, int mode);
      <function>lo_open</function> returns a (non-negative) large object
      descriptor for later use in <function>lo_read</function>,
      <function>lo_write</function>, <function>lo_lseek</function>,
-     <function>lo_tell</function>, and <function>lo_close</function>.
+        <function>lo_lseek64</function>, <function>lo_tell</function>,
+     <function>lo_tell64</function>, <function>lo_truncate</function>,
+        <function>lo_truncate64</function>, and <function>lo_close</function>.
      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
 <synopsis>
 int lo_lseek(PGconn *conn, int fd, int offset, int whence);
+pg_int64 lo_lseek64(PGconn *conn, int fd, pg_int64 offset, int whence);
 </synopsis>
      <indexterm><primary>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);
      <symbol>SEEK_CUR</> (seek from current position), and
      <symbol>SEEK_END</> (seek from object end).  The return value is
      the new location pointer, or -1 on error.
+     <indexterm><primary>lo_lseek64</></> <function>lo_lseek64</function>
+     is a function for large objects larger than 2GB. <symbol>pg_int64</>
+     is defined as 8-byte integer type.
 </para>
+<para>
+     <function>lo_lseek64</> is new as of <productname>PostgreSQL</productname>
+     9.3; if this function is run against an older server version, it will
+     fail and return a negative value.
+</para>
+
 </sect2>
 
 <sect2 id="lo-tell">
@@ -332,9 +344,17 @@ int lo_lseek(PGconn *conn, int fd, int offset, int whence);
      call
 <synopsis>
 int lo_tell(PGconn *conn, int fd);
+pg_int64 lo_tell64(PGconn *conn, int fd);
 </synopsis>
      <indexterm><primary>lo_tell</></> If there is an error, the
      return value is negative.
+     <indexterm><primary>lo_tell64</></> <function>lo_tell64</function> is
+     a function for large objects larger than 2GB.
+</para>
+<para>
+     <function>lo_tell64</> is new as of <productname>PostgreSQL</productname>
+     9.3; if this function is run against an older server version, it will
+     fail and return a negative value.
 </para>
 </sect2>
 
@@ -345,6 +365,7 @@ int lo_tell(PGconn *conn, int fd);
      To truncate a large object to a given length, call
 <synopsis>
 int lo_truncate(PGcon *conn, int fd, size_t len);
+int lo_truncate64(PGcon *conn, int fd, pg_int64 len);
 </synopsis>
      <indexterm><primary>lo_truncate</></> truncates the large object
      descriptor <parameter>fd</> to length <parameter>len</>.  The
@@ -352,6 +373,8 @@ int lo_truncate(PGcon *conn, int fd, size_t len);
      previous <function>lo_open</function>.  If <parameter>len</> is
      greater than the current large object length, the large object
      is extended with null bytes ('\0').
+     <indexterm><primary>lo_truncate64</></> <function>lo_truncate64</function>
+     is a function for large objects larger than 2GB.
 </para>
 
 <para>
@@ -359,7 +382,7 @@ int lo_truncate(PGcon *conn, int fd, size_t len);
 </para>
 
 <para>
-     On success <function>lo_truncate</function> returns
+     On success <function>lo_truncate</function> and <function>lo_truncate64</function> returns
      zero.  On error, the return value is negative.
 </para>
 
@@ -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.
 </para>
+<para>
+     <function>lo_truncate64</> is new as of <productname>PostgreSQL</productname>
+     9.3; if this function is run against an older server version, it will
+     fail and return a negative value.
+</para>
 </sect2>
 
 <sect2 id="lo-close">
index 6f7e474f675485766c7ffca7312d0f1e0adbb83d..4bc81ba9f4d11230c8ea8c003d0b61ec16c8e813 100644 (file)
@@ -39,6 +39,7 @@
 #include "postgres.h"
 
 #include <fcntl.h>
+#include <limits.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
@@ -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
index 3adfb159b8baa49939951da0cecf789575652dbe..3f5688b939b0c09b629b3419f531f1b0dd23d6fb 100644 (file)
@@ -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;
index 3e04164956303f6fcdef84a15c617e3075956b05..db8ab53af26b7536ea45c8151ea6defe832c25b3 100644 (file)
@@ -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
 
index 77a3b413ce525c5c5854f1ce60964c0c6812552c..a2da836ff2cdd02a497571b875ae6d6e66481d44 100644 (file)
@@ -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_ ));
index 0c832da6e43e31cf33057a1f99872a6349b4d5c8..d74ea0eee258b9463174d90e9b17472887f6fa35 100644 (file)
@@ -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
index b6ebb7aac3f0d17ab1629e02d96e89c8f547e047..76502de647b4e94c399904e8f3cd35ec8c522f19 100644 (file)
@@ -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
index 1fe07ee43ac6c32feb0440965f954c177b36ff1c..52f01c6e3c393311dfb852bdd6ff7b944bc87155 100644 (file)
@@ -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 */
index 9d95e262be3fbf26731c0926013db56dbc4e00ab..56d0bb8dc5816ee128fc8234f95585aca20ebdea 100644 (file)
@@ -161,3 +161,6 @@ PQping                    158
 PQpingParams              159
 PQlibVersion              160
 PQsetSingleRowMode        161
+lo_lseek64                162
+lo_tell64                 163
+lo_truncate64             164
index f3a6d0341c13ce644a7f1b2f15919385b85d6a6e..fb17ac8b1e9cc47f9f0f8187754fe7aa92772d13 100644 (file)
 #include "libpq-int.h"
 #include "libpq/libpq-fs.h"            /* must come after sys/stat.h */
 
+/* for ntohl/htonl */
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
 #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;
+}
index 9d05dd20605a84ab4c4ec9d61ef8697fb2f3b77e..73568ca23c38b09a3a42b5d31078f179d687688d 100644 (file)
@@ -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 */
index 4a6c8fedf2b6b7f2f72d588c9c504dcc8d26cc0f..375821e017fd7a93c1abc58a0f7fe7adaa354cd9 100644 (file)
@@ -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;
index bbc6ee1d36631258d11dbc656e6abf16333d8fdf..aee5c044075885e15de65ffef5be0951ed538f99 100644 (file)
@@ -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 (file)
index 0000000..6ab7f52
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#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;
+}
index 40f40f8c76497e4e67c772f5ec6e46035f164b1a..4984d78a0694490cf53621efc854cde1f26d12b2 100644 (file)
@@ -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;
index 55aaf8f2afe07384f262f1c4ace0f672350cce66..74c4772b03aba5cc3cdc75a546f3819bb26b62a5 100644 (file)
@@ -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