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