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 ---- + 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 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;