From 0763a5650147a5573a5a4b81de5dd819f010f8e8 Mon Sep 17 00:00:00 2001
From: Bruce Momjian <bruce@momjian.us>
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 06137e4f270..3619b504f06 100644
--- a/doc/src/sgml/lobj.sgml
+++ b/doc/src/sgml/lobj.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/lobj.sgml,v 1.44 2007/02/01 19:10:24 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/lobj.sgml,v 1.45 2007/03/03 19:52:45 momjian Exp $ -->
 
  <chapter id="largeObjects">
   <title id="largeObjects-title">Large Objects</title>
@@ -301,6 +301,37 @@ int lo_tell(PGconn *conn, int fd);
 </para>
 </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>
 
diff --git a/src/backend/libpq/be-fsstubs.c b/src/backend/libpq/be-fsstubs.c
index 80eddc2821b..8875155129d 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 9e033c436c9..7becf160b49 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 2a7df209c92..a4b0e9c5a39 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 61192203109..cd02538ab65 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 c9873628175..a04b1f876a3 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 606133bd00d..667301aeac9 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 e4c3d66022c..5bd8315193a 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 9aa7449f0d1..f89d3e1a4c8 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 61e36bfba33..676d2db4d1a 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 e89ce02fa99..8386922133c 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 6d6e5cd24cf..f4addeaab5b 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