The attached patch implements lo_truncate for truncating large objects to a given length. This is required for implementing Blob.truncate in the JDBC driver[1] and rounds out filesystem like functionality for large objects.

Kris Jurka

[1] http://java.sun.com/javase/6/docs/api/java/sql/Blob.html#truncate(long)
Index: doc/src/sgml/lobj.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql/doc/src/sgml/lobj.sgml,v
retrieving revision 1.44
diff -c -r1.44 lobj.sgml
*** doc/src/sgml/lobj.sgml      1 Feb 2007 19:10:24 -0000       1.44
--- doc/src/sgml/lobj.sgml      22 Feb 2007 20:43:16 -0000
***************
*** 302,307 ****
--- 302,338 ----
  </sect2>
  
  <sect2>
+ <title>Truncating a Large Object</title>
+ 
+ <para>
+      To truncate a large object to a given length, call
+ <synopsis>
+ int lo_truncate(PGcon *conn, int fd, size_t len);
+ </synopsis>
+      <indexterm><primary>lo_truncate</></> truncates the large object
+      descriptor <parameter>fd</> to length <parameter>len</>.  The
+      <parameter>fd</parameter> argument must have been returned by a
+      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').
+ </para>
+ 
+ <para>
+      The file offset is not changed.
+ </para>
+ 
+ <para>
+      On success <function>lo_truncate</function> returns
+      zero.  On error, the return value is negative.
+ </para>
+ 
+ <para>
+      <function>lo_truncate</> is new as of 
<productname>PostgreSQL</productname>
+      8.3; if this function is run against an older server version, it will
+      fail and return a negative value.
+ </para>
+ 
+ <sect2>
  <title>Closing a Large Object Descriptor</title>
  
  <para>
Index: src/backend/libpq/be-fsstubs.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/libpq/be-fsstubs.c,v
retrieving revision 1.84
diff -c -r1.84 be-fsstubs.c
*** src/backend/libpq/be-fsstubs.c      5 Jan 2007 22:19:29 -0000       1.84
--- src/backend/libpq/be-fsstubs.c      22 Feb 2007 20:43:17 -0000
***************
*** 120,131 ****
        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_INT32(-1);
!       }
  #if FSDB
        elog(DEBUG4, "lo_close(%d)", fd);
  #endif
--- 120,129 ----
        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)));
! 
  #if FSDB
        elog(DEBUG4, "lo_close(%d)", fd);
  #endif
***************
*** 152,163 ****
        int                     status;
  
        if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
-       {
                ereport(ERROR,
                                (errcode(ERRCODE_UNDEFINED_OBJECT),
                                 errmsg("invalid large-object descriptor: %d", 
fd)));
-               return -1;
-       }
  
        status = inv_read(cookies[fd], buf, len);
  
--- 150,158 ----
***************
*** 170,181 ****
        int                     status;
  
        if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
-       {
                ereport(ERROR,
                                (errcode(ERRCODE_UNDEFINED_OBJECT),
                                 errmsg("invalid large-object descriptor: %d", 
fd)));
-               return -1;
-       }
  
        if ((cookies[fd]->flags & IFS_WRLOCK) == 0)
                ereport(ERROR,
--- 165,173 ----
***************
*** 198,209 ****
        int                     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_INT32(-1);
-       }
  
        status = inv_seek(cookies[fd], offset, whence);
  
--- 190,198 ----
***************
*** 248,259 ****
        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_INT32(-1);
-       }
  
        PG_RETURN_INT32(inv_tell(cookies[fd]));
  }
--- 237,245 ----
***************
*** 468,473 ****
--- 454,479 ----
  }
  
  /*
+  * lo_truncate -
+  *      truncate a large object to a specified length
+  */
+ Datum
+ lo_truncate(PG_FUNCTION_ARGS)
+ {
+       int32           fd = PG_GETARG_INT32(0);
+       int32           len = PG_GETARG_INT32(1);
+ 
+       if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
+               ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_OBJECT),
+                                errmsg("invalid large-object descriptor: %d", 
fd)));
+ 
+       inv_truncate(cookies[fd], len);
+ 
+       PG_RETURN_INT32(0);
+ }
+ 
+ /*
   * AtEOXact_LargeObject -
   *             prepares large objects for transaction commit
   */
Index: src/backend/storage/large_object/inv_api.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/storage/large_object/inv_api.c,v
retrieving revision 1.121
diff -c -r1.121 inv_api.c
*** src/backend/storage/large_object/inv_api.c  5 Jan 2007 22:19:38 -0000       
1.121
--- src/backend/storage/large_object/inv_api.c  22 Feb 2007 20:43:17 -0000
***************
*** 681,683 ****
--- 681,846 ----
  
        return nwritten;
  }
+ 
+ void
+ inv_truncate(LargeObjectDesc *obj_desc, int len)
+ {
+       int32           pageno = (int32) (len / LOBLKSIZE);
+       int                     off;
+       ScanKeyData     skey[2];
+       IndexScanDesc sd;
+       HeapTuple       oldtuple;
+       Form_pg_largeobject     olddata;
+       struct
+       {
+               bytea           hdr;
+               char            data[LOBLKSIZE];
+       }                       workbuf;
+       char       *workb = VARATT_DATA(&workbuf.hdr);
+       HeapTuple       newtup;
+       Datum           values[Natts_pg_largeobject];
+       char            nulls[Natts_pg_largeobject];
+       char            replace[Natts_pg_largeobject];
+       CatalogIndexState indstate;
+ 
+       Assert(PointerIsValid(obj_desc));
+ 
+       /* enforce writability because snapshot is probably wrong otherwise */
+       if ((obj_desc->flags & IFS_WRLOCK) == 0)
+               ereport(ERROR,
+                               
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                errmsg("large object %u was not opened for 
writing",
+                                               obj_desc->id)));
+ 
+       open_lo_relation();
+ 
+       indstate = CatalogOpenIndexes(lo_heap_r);
+ 
+       ScanKeyInit(&skey[0],
+                               Anum_pg_largeobject_loid,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(obj_desc->id));
+ 
+       ScanKeyInit(&skey[1],
+                               Anum_pg_largeobject_pageno,
+                               BTGreaterEqualStrategyNumber, F_INT4GE,
+                               Int32GetDatum(pageno));
+ 
+       sd = index_beginscan(lo_heap_r, lo_index_r,
+                                                obj_desc->snapshot, 2, skey);
+ 
+       /*
+        * If possible, get the page the truncation point is in.
+        * The truncation point may be beyond the end of the LO or
+        * in a hole.
+        */
+       olddata = NULL;
+       if ((oldtuple = index_getnext(sd, ForwardScanDirection)) != NULL)
+       {
+               olddata = (Form_pg_largeobject) GETSTRUCT(oldtuple);
+               Assert(olddata->pageno >= pageno);
+       }
+ 
+       /*
+        * If we found the page of the truncation point we need to
+        * truncate the data in it.  Otherwise if we're in a hole,
+        * we need to create a page to mark the end of data.
+        */
+       if (olddata != NULL && olddata->pageno == pageno)
+       {
+               /* First, load old data into workbuf */
+               bytea *datafield = &(olddata->data);
+               bool pfreeit = false;
+               int pagelen;
+ 
+               if (VARATT_IS_EXTENDED(datafield))
+               {
+                       datafield = (bytea *)
+                               heap_tuple_untoast_attr((varattrib *) 
datafield);
+                       pfreeit = true;
+               }
+               pagelen = getbytealen(datafield);
+               Assert(pagelen <= LOBLKSIZE);
+               memcpy(workb, VARDATA(datafield), pagelen);
+               if (pfreeit)
+                               pfree(datafield);
+ 
+               /*
+                * Fill any hole
+                */
+               off = len % LOBLKSIZE;
+               if (off > pagelen)
+                               MemSet(workb + pagelen, 0, off - pagelen);
+ 
+               /* compute length of new page */
+               VARATT_SIZEP(&workbuf.hdr) = off + VARHDRSZ;
+ 
+               /*
+                * Form and insert updated tuple
+                */
+               memset(values, 0, sizeof(values));
+               memset(nulls, ' ', sizeof(nulls));
+               memset(replace, ' ', sizeof(replace));
+               values[Anum_pg_largeobject_data - 1] = 
PointerGetDatum(&workbuf);
+               replace[Anum_pg_largeobject_data - 1] = 'r';
+               newtup = heap_modifytuple(oldtuple, RelationGetDescr(lo_heap_r),
+                                                                 values, 
nulls, replace);
+               simple_heap_update(lo_heap_r, &newtup->t_self, newtup);
+               CatalogIndexInsert(indstate, newtup);
+               heap_freetuple(newtup);
+       }
+       else
+       {
+               /*
+                * If the first page we found was after the truncation
+                * point, we're in a hole that we'll fill, but we need to
+                * delete the later page.
+                */
+               if (olddata != NULL && olddata->pageno > pageno)
+                       simple_heap_delete(lo_heap_r, &oldtuple->t_self);
+ 
+               /*
+                * Write a brand new page.
+                * 
+                * Fill the hole up to the truncation point
+                */
+               off = len % LOBLKSIZE;
+               if (off > 0)
+                       MemSet(workb, 0, off);
+ 
+               /* compute length of new page */
+               VARATT_SIZEP(&workbuf.hdr) = off + VARHDRSZ;
+ 
+               /* 
+                * Form and insert new tuple
+                */
+               memset(values, 0, sizeof(values));
+               memset(nulls, ' ', sizeof(nulls));
+               values[Anum_pg_largeobject_loid - 1] = 
ObjectIdGetDatum(obj_desc->id);
+               values[Anum_pg_largeobject_pageno - 1] = Int32GetDatum(pageno);
+               values[Anum_pg_largeobject_data - 1] = 
PointerGetDatum(&workbuf);
+               newtup = heap_formtuple(lo_heap_r->rd_att, values, nulls);
+               simple_heap_insert(lo_heap_r, newtup);
+               CatalogIndexInsert(indstate, newtup);
+               heap_freetuple(newtup);
+       }
+ 
+       /*
+        * Delete any pages after the truncation point
+        */
+       while ((oldtuple = index_getnext(sd, ForwardScanDirection)) != NULL)
+       {
+               simple_heap_delete(lo_heap_r, &oldtuple->t_self);
+       }
+ 
+       index_endscan(sd);
+ 
+       CatalogCloseIndexes(indstate);
+       
+       /*
+        * Advance command counter so that tuple updates will be seen by later
+        * large-object operations in this transaction.
+        */
+       CommandCounterIncrement();
+ }
+ 
Index: src/include/catalog/pg_proc.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/catalog/pg_proc.h,v
retrieving revision 1.446
diff -c -r1.446 pg_proc.h
*** src/include/catalog/pg_proc.h       20 Feb 2007 10:00:25 -0000      1.446
--- src/include/catalog/pg_proc.h       22 Feb 2007 20:43:17 -0000
***************
*** 1233,1238 ****
--- 1233,1240 ----
  DESCR("large object create");
  DATA(insert OID = 958 (  lo_tell                 PGNSP PGUID 12 1 0 f f t f v 
1 23 "23" _null_ _null_ _null_  lo_tell - _null_ ));
  DESCR("large object position");
+ DATA(insert OID = 1004 (  lo_truncate    PGNSP PGUID 12 1 0 f f t f v 2 23 
"23 23" _null_ _null_ _null_ lo_truncate - _null_ ));
+ DESCR("truncate large object");
  
  DATA(insert OID = 959 (  on_pl                           PGNSP PGUID 12 1 0 f 
f t f i 2  16 "600 628" _null_ _null_ _null_    on_pl - _null_ ));
  DESCR("point on line?");
Index: src/include/libpq/be-fsstubs.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/libpq/be-fsstubs.h,v
retrieving revision 1.28
diff -c -r1.28 be-fsstubs.h
*** src/include/libpq/be-fsstubs.h      5 Jan 2007 22:19:55 -0000       1.28
--- src/include/libpq/be-fsstubs.h      22 Feb 2007 20:43:17 -0000
***************
*** 34,39 ****
--- 34,40 ----
  extern Datum lo_lseek(PG_FUNCTION_ARGS);
  extern Datum lo_tell(PG_FUNCTION_ARGS);
  extern Datum lo_unlink(PG_FUNCTION_ARGS);
+ extern Datum lo_truncate(PG_FUNCTION_ARGS);
  
  /*
   * These are not fmgr-callable, but are available to C code.
Index: src/include/storage/large_object.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/storage/large_object.h,v
retrieving revision 1.36
diff -c -r1.36 large_object.h
*** src/include/storage/large_object.h  5 Jan 2007 22:19:58 -0000       1.36
--- src/include/storage/large_object.h  22 Feb 2007 20:43:17 -0000
***************
*** 78,82 ****
--- 78,83 ----
  extern int    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);
  
  #endif   /* LARGE_OBJECT_H */
Index: src/interfaces/libpq/exports.txt
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/exports.txt,v
retrieving revision 1.14
diff -c -r1.14 exports.txt
*** src/interfaces/libpq/exports.txt    18 Aug 2006 19:52:39 -0000      1.14
--- src/interfaces/libpq/exports.txt    22 Feb 2007 20:43:17 -0000
***************
*** 136,138 ****
--- 136,139 ----
  PQdescribePortal          134
  PQsendDescribePrepared    135
  PQsendDescribePortal      136
+ lo_truncate               137
Index: src/interfaces/libpq/fe-lobj.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-lobj.c,v
retrieving revision 1.61
diff -c -r1.61 fe-lobj.c
*** src/interfaces/libpq/fe-lobj.c      5 Jan 2007 22:20:01 -0000       1.61
--- src/interfaces/libpq/fe-lobj.c      22 Feb 2007 20:43:17 -0000
***************
*** 123,128 ****
--- 123,181 ----
  }
  
  /*
+  * lo_truncate
+  *    truncates an existing large object to the given size
+  *
+  * returns 0 upon success
+  * returns -1 upon failure
+  */
+ int
+ lo_truncate(PGconn *conn, int fd, size_t len)
+ {
+       PQArgBlock      argv[2];
+       PGresult   *res;
+       int                     retval;
+       int                     result_len;
+ 
+       if (conn->lobjfuncs == NULL)
+       {
+               if (lo_initialize(conn) < 0)
+                       return -1;
+       }
+ 
+       /* Must check this on-the-fly because it's not there pre-8.3 */
+       if (conn->lobjfuncs->fn_lo_truncate == 0)
+       {
+               printfPQExpBuffer(&conn->errorMessage,
+                         libpq_gettext("cannot determine OID of function 
lo_truncate\n"));
+               return -1;
+       }
+ 
+       argv[0].isint = 1;
+       argv[0].len = 4;
+       argv[0].u.integer = fd;
+       
+       argv[1].isint = 1;
+       argv[1].len = 4;
+       argv[1].u.integer = len;
+ 
+       res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate,
+                          &retval, &result_len, 1, argv, 2);
+ 
+       if (PQresultStatus(res) == PGRES_COMMAND_OK)
+       {
+               PQclear(res);
+               return retval;
+       }
+       else
+       {
+               PQclear(res);
+               return -1;
+       }
+ }
+ 
+ 
+ /*
   * lo_read
   *      read len bytes of the large object into buf
   *
***************
*** 621,626 ****
--- 674,680 ----
        /*
         * Execute the query to get all the functions at once.  In 7.3 and later
         * we need to be schema-safe.  lo_create only exists in 8.1 and up.
+        * lo_truncate only exists in 8.3 and up.
         */
        if (conn->sversion >= 70300)
                query = "select proname, oid from pg_catalog.pg_proc "
***************
*** 632,637 ****
--- 686,692 ----
                        "'lo_unlink', "
                        "'lo_lseek', "
                        "'lo_tell', "
+                       "'lo_truncate', "
                        "'loread', "
                        "'lowrite') "
                        "and pronamespace = (select oid from 
pg_catalog.pg_namespace "
***************
*** 684,689 ****
--- 739,746 ----
                        lobjfuncs->fn_lo_lseek = foid;
                else if (!strcmp(fname, "lo_tell"))
                        lobjfuncs->fn_lo_tell = foid;
+               else if (!strcmp(fname, "lo_truncate"))
+                       lobjfuncs->fn_lo_truncate = foid;
                else if (!strcmp(fname, "loread"))
                        lobjfuncs->fn_lo_read = foid;
                else if (!strcmp(fname, "lowrite"))
***************
*** 694,700 ****
  
        /*
         * Finally check that we really got all large object interface functions
-        * --- except lo_create, which may not exist.
         */
        if (lobjfuncs->fn_lo_open == 0)
        {
--- 751,756 ----
Index: src/interfaces/libpq/libpq-fe.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-fe.h,v
retrieving revision 1.135
diff -c -r1.135 libpq-fe.h
*** src/interfaces/libpq/libpq-fe.h     5 Jan 2007 22:20:01 -0000       1.135
--- src/interfaces/libpq/libpq-fe.h     22 Feb 2007 20:43:17 -0000
***************
*** 489,494 ****
--- 489,495 ----
  extern Oid    lo_creat(PGconn *conn, int mode);
  extern Oid    lo_create(PGconn *conn, Oid lobjId);
  extern int    lo_tell(PGconn *conn, int fd);
+ extern int    lo_truncate(PGconn *conn, int fd, size_t len);
  extern int    lo_unlink(PGconn *conn, Oid lobjId);
  extern Oid    lo_import(PGconn *conn, const char *filename);
  extern int    lo_export(PGconn *conn, Oid lobjId, const char *filename);
Index: src/interfaces/libpq/libpq-int.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-int.h,v
retrieving revision 1.118
diff -c -r1.118 libpq-int.h
*** src/interfaces/libpq/libpq-int.h    26 Jan 2007 17:45:41 -0000      1.118
--- src/interfaces/libpq/libpq-int.h    22 Feb 2007 20:43:17 -0000
***************
*** 239,244 ****
--- 239,245 ----
        Oid                     fn_lo_unlink;   /* OID of backend function 
lo_unlink    */
        Oid                     fn_lo_lseek;    /* OID of backend function 
lo_lseek             */
        Oid                     fn_lo_tell;             /* OID of backend 
function lo_tell              */
+       Oid                     fn_lo_truncate; /* OID of backend function 
lo_truncate  */
        Oid                     fn_lo_read;             /* OID of backend 
function LOread               */
        Oid                     fn_lo_write;    /* OID of backend function 
LOwrite              */
  } PGlobjfuncs;
Index: src/test/regress/input/largeobject.source
===================================================================
RCS file: /projects/cvsroot/pgsql/src/test/regress/input/largeobject.source,v
retrieving revision 1.1
diff -c -r1.1 largeobject.source
*** src/test/regress/input/largeobject.source   20 Jan 2007 17:15:44 -0000      
1.1
--- src/test/regress/input/largeobject.source   22 Feb 2007 20:43:17 -0000
***************
*** 83,88 ****
--- 83,107 ----
  
  END;
  
+ -- Test truncation.
+ BEGIN;
+ UPDATE lotest_stash_values SET fd=lo_open(loid, CAST((2 | 4) * 16^4 AS 
integer));
+ 
+ SELECT lo_truncate(fd, 10) FROM lotest_stash_values;
+ SELECT loread(fd, 15) FROM lotest_stash_values;
+ 
+ SELECT lo_truncate(fd, 10000) FROM lotest_stash_values;
+ SELECT loread(fd, 10) FROM lotest_stash_values;
+ SELECT lo_lseek(fd, 0, 2) FROM lotest_stash_values;
+ SELECT lo_tell(fd) FROM lotest_stash_values;
+ 
+ SELECT lo_truncate(fd, 5000) FROM lotest_stash_values;
+ SELECT lo_lseek(fd, 0, 2) FROM lotest_stash_values;
+ SELECT lo_tell(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: src/test/regress/output/largeobject.source
===================================================================
RCS file: /projects/cvsroot/pgsql/src/test/regress/output/largeobject.source,v
retrieving revision 1.1
diff -c -r1.1 largeobject.source
*** src/test/regress/output/largeobject.source  20 Jan 2007 17:15:44 -0000      
1.1
--- src/test/regress/output/largeobject.source  22 Feb 2007 20:43:17 -0000
***************
*** 116,121 ****
--- 116,185 ----
  (1 row)
  
  END;
+ -- Test truncation.
+ BEGIN;
+ UPDATE lotest_stash_values SET fd=lo_open(loid, CAST((2 | 4) * 16^4 AS 
integer));
+ SELECT lo_truncate(fd, 10) FROM lotest_stash_values;
+  lo_truncate 
+ -------------
+            0
+ (1 row)
+ 
+ SELECT loread(fd, 15) FROM lotest_stash_values;
+     loread     
+ ---------------
+  \012Whose woo
+ (1 row)
+ 
+ SELECT lo_truncate(fd, 10000) FROM lotest_stash_values;
+  lo_truncate 
+ -------------
+            0
+ (1 row)
+ 
+ SELECT loread(fd, 10) FROM lotest_stash_values;
+                   loread                  
+ ------------------------------------------
+  \000\000\000\000\000\000\000\000\000\000
+ (1 row)
+ 
+ SELECT lo_lseek(fd, 0, 2) FROM lotest_stash_values;
+  lo_lseek 
+ ----------
+     10000
+ (1 row)
+ 
+ SELECT lo_tell(fd) FROM lotest_stash_values;
+  lo_tell 
+ ---------
+    10000
+ (1 row)
+ 
+ SELECT lo_truncate(fd, 5000) FROM lotest_stash_values;
+  lo_truncate 
+ -------------
+            0
+ (1 row)
+ 
+ SELECT lo_lseek(fd, 0, 2) FROM lotest_stash_values;
+  lo_lseek 
+ ----------
+      5000
+ (1 row)
+ 
+ SELECT lo_tell(fd) FROM lotest_stash_values;
+  lo_tell 
+ ---------
+     5000
+ (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
  SELECT lo_unlink(loid) from lotest_stash_values;
---------------------------(end of broadcast)---------------------------
TIP 9: In versions below 8.0, the planner will ignore your desire to
       choose an index scan if your joining column's datatypes do not
       match

Reply via email to