From 0763a5650147a5573a5a4b81de5dd819f010f8e8 Mon Sep 17 00:00:00 2001 From: Bruce Momjian Date: Sat, 3 Mar 2007 19:52:47 +0000 Subject: [PATCH] Add lo_truncate() to backend and libpq for large object truncation. Kris Jurka --- doc/src/sgml/lobj.sgml | 33 ++++- src/backend/libpq/be-fsstubs.c | 38 +++-- src/backend/storage/large_object/inv_api.c | 165 ++++++++++++++++++++- src/include/catalog/pg_proc.h | 4 +- src/include/libpq/be-fsstubs.h | 3 +- src/include/storage/large_object.h | 3 +- src/interfaces/libpq/exports.txt | 3 +- src/interfaces/libpq/fe-lobj.c | 60 +++++++- src/interfaces/libpq/libpq-fe.h | 3 +- src/interfaces/libpq/libpq-int.h | 3 +- src/test/regress/input/largeobject.source | 19 +++ src/test/regress/output/largeobject.source | 64 ++++++++ 12 files changed, 372 insertions(+), 26 deletions(-) diff --git a/doc/src/sgml/lobj.sgml b/doc/src/sgml/lobj.sgml index 06137e4f27..3619b504f0 100644 --- a/doc/src/sgml/lobj.sgml +++ b/doc/src/sgml/lobj.sgml @@ -1,4 +1,4 @@ - + Large Objects @@ -301,6 +301,37 @@ int lo_tell(PGconn *conn, int fd); + +Truncating a Large Object + + + To truncate a large object to a given length, call + +int lo_truncate(PGcon *conn, int fd, size_t len); + + lo_truncate truncates the large object + descriptor fd to length len. The + fd argument must have been returned by a + previous lo_open. If len is + greater than the current large object length, the large object + is extended with null bytes ('\0'). + + + + The file offset is not changed. + + + + On success lo_truncate returns + zero. On error, the return value is negative. + + + + lo_truncate is new as of PostgreSQL + 8.3; if this function is run against an older server version, it will + fail and return a negative value. + + Closing a Large Object Descriptor diff --git a/src/backend/libpq/be-fsstubs.c b/src/backend/libpq/be-fsstubs.c index 80eddc2821..8875155129 100644 --- a/src/backend/libpq/be-fsstubs.c +++ b/src/backend/libpq/be-fsstubs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/libpq/be-fsstubs.c,v 1.85 2007/02/27 23:48:07 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/be-fsstubs.c,v 1.86 2007/03/03 19:52:46 momjian Exp $ * * NOTES * This should be moved to a more appropriate place. It is here @@ -120,12 +120,10 @@ lo_close(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_INT32(-1); - } + #if FSDB elog(DEBUG4, "lo_close(%d)", fd); #endif @@ -152,12 +150,9 @@ lo_read(int fd, char *buf, int len) 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); @@ -170,12 +165,9 @@ lo_write(int fd, const char *buf, int len) 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, @@ -198,12 +190,9 @@ lo_lseek(PG_FUNCTION_ARGS) 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); @@ -248,12 +237,9 @@ lo_tell(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_INT32(-1); - } PG_RETURN_INT32(inv_tell(cookies[fd])); } @@ -467,6 +453,26 @@ lo_export(PG_FUNCTION_ARGS) PG_RETURN_INT32(1); } +/* + * 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 diff --git a/src/backend/storage/large_object/inv_api.c b/src/backend/storage/large_object/inv_api.c index 9e033c436c..7becf160b4 100644 --- a/src/backend/storage/large_object/inv_api.c +++ b/src/backend/storage/large_object/inv_api.c @@ -17,7 +17,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/storage/large_object/inv_api.c,v 1.122 2007/02/27 23:48:07 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/storage/large_object/inv_api.c,v 1.123 2007/03/03 19:52:46 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -681,3 +681,166 @@ inv_write(LargeObjectDesc *obj_desc, const char *buf, int nbytes) 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 = VARDATA(&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 */ + SET_VARSIZE(&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 */ + SET_VARSIZE(&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(); +} + diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 2a7df209c9..a4b0e9c5a3 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.446 2007/02/20 10:00:25 petere Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.447 2007/03/03 19:52:46 momjian Exp $ * * NOTES * The script catalog/genbki.sh reads this file and generates .bki @@ -1233,6 +1233,8 @@ DATA(insert OID = 715 ( lo_create PGNSP PGUID 12 1 0 f f t f v 1 26 "26" _n 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?"); diff --git a/src/include/libpq/be-fsstubs.h b/src/include/libpq/be-fsstubs.h index 6119220310..cd02538ab6 100644 --- a/src/include/libpq/be-fsstubs.h +++ b/src/include/libpq/be-fsstubs.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/libpq/be-fsstubs.h,v 1.28 2007/01/05 22:19:55 momjian Exp $ + * $PostgreSQL: pgsql/src/include/libpq/be-fsstubs.h,v 1.29 2007/03/03 19:52:46 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -34,6 +34,7 @@ extern Datum lowrite(PG_FUNCTION_ARGS); 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. diff --git a/src/include/storage/large_object.h b/src/include/storage/large_object.h index c987362817..a04b1f876a 100644 --- a/src/include/storage/large_object.h +++ b/src/include/storage/large_object.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/storage/large_object.h,v 1.36 2007/01/05 22:19:58 momjian Exp $ + * $PostgreSQL: pgsql/src/include/storage/large_object.h,v 1.37 2007/03/03 19:52:46 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -78,5 +78,6 @@ extern int inv_seek(LargeObjectDesc *obj_desc, int offset, int whence); 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 */ diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index 606133bd00..667301aeac 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -1,4 +1,4 @@ -# $PostgreSQL: pgsql/src/interfaces/libpq/exports.txt,v 1.14 2006/08/18 19:52:39 tgl Exp $ +# $PostgreSQL: pgsql/src/interfaces/libpq/exports.txt,v 1.15 2007/03/03 19:52:46 momjian Exp $ # Functions to be exported by libpq DLLs PQconnectdb 1 PQsetdbLogin 2 @@ -136,3 +136,4 @@ PQdescribePrepared 133 PQdescribePortal 134 PQsendDescribePrepared 135 PQsendDescribePortal 136 +lo_truncate 137 diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index e4c3d66022..5bd8315193 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/interfaces/libpq/fe-lobj.c,v 1.61 2007/01/05 22:20:01 momjian Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/fe-lobj.c,v 1.62 2007/03/03 19:52:46 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -122,6 +122,59 @@ lo_close(PGconn *conn, int fd) } } +/* + * 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,6 +674,7 @@ lo_initialize(PGconn *conn) /* * 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,6 +686,7 @@ lo_initialize(PGconn *conn) "'lo_unlink', " "'lo_lseek', " "'lo_tell', " + "'lo_truncate', " "'loread', " "'lowrite') " "and pronamespace = (select oid from pg_catalog.pg_namespace " @@ -684,6 +739,8 @@ lo_initialize(PGconn *conn) 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,7 +751,6 @@ lo_initialize(PGconn *conn) /* * Finally check that we really got all large object interface functions - * --- except lo_create, which may not exist. */ if (lobjfuncs->fn_lo_open == 0) { diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index 9aa7449f0d..f89d3e1a4c 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-fe.h,v 1.135 2007/01/05 22:20:01 momjian Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-fe.h,v 1.136 2007/03/03 19:52:46 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -489,6 +489,7 @@ extern int lo_lseek(PGconn *conn, int fd, int offset, int whence); 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); diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 61e36bfba3..676d2db4d1 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -12,7 +12,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.118 2007/01/26 17:45:41 neilc Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.119 2007/03/03 19:52:47 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -239,6 +239,7 @@ typedef struct pgLobjfuncs 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; diff --git a/src/test/regress/input/largeobject.source b/src/test/regress/input/largeobject.source index e89ce02fa9..8386922133 100644 --- a/src/test/regress/input/largeobject.source +++ b/src/test/regress/input/largeobject.source @@ -83,6 +83,25 @@ SELECT lo_close(fd) FROM lotest_stash_values; 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; diff --git a/src/test/regress/output/largeobject.source b/src/test/regress/output/largeobject.source index 6d6e5cd24c..f4addeaab5 100644 --- a/src/test/regress/output/largeobject.source +++ b/src/test/regress/output/largeobject.source @@ -115,6 +115,70 @@ SELECT lo_close(fd) FROM lotest_stash_values; 0 (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 -- GitLab