From 40314f2dac2ecb2974d03c064917a70de74c63d5 Mon Sep 17 00:00:00 2001 From: Tom Lane <tgl@sss.pgh.pa.us> Date: Sun, 20 Nov 2005 18:38:20 +0000 Subject: [PATCH] Modify tuptoaster's API so that it does not try to modify the passed tuple in-place, but instead passes back an all-new tuple structure if any changes are needed. This is a much cleaner and more robust solution for the bug discovered by Alexey Beschiokov; accordingly, revert the quick hack I installed yesterday. With this change, HeapTupleData.t_datamcxt is no longer needed; will remove it in a separate commit in HEAD only. --- src/backend/access/heap/heapam.c | 107 +++++++++++++++++++-------- src/backend/access/heap/tuptoaster.c | 74 +++++++++--------- src/backend/executor/execMain.c | 22 +----- src/include/access/tuptoaster.h | 22 +++--- 4 files changed, 124 insertions(+), 101 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 6c669ed62b4..fc750884c75 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/heap/heapam.c,v 1.200 2005/10/15 02:49:08 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/access/heap/heapam.c,v 1.201 2005/11/20 18:38:20 tgl Exp $ * * * INTERFACE ROUTINES @@ -1085,12 +1085,19 @@ heap_get_latest_tid(Relation relation, * * use_fsm is passed directly to RelationGetBufferForTuple, which see for * more info. + * + * The return value is the OID assigned to the tuple (either here or by the + * caller), or InvalidOid if no OID. The header fields of *tup are updated + * to match the stored tuple; in particular tup->t_self receives the actual + * TID where the tuple was stored. But note that any toasting of fields + * within the tuple data is NOT reflected into *tup. */ Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid, bool use_wal, bool use_fsm) { TransactionId xid = GetCurrentTransactionId(); + HeapTuple heaptup; Buffer buffer; if (relation->rd_rel->relhasoids) @@ -1128,19 +1135,24 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, /* * If the new tuple is too big for storage or contains already toasted * out-of-line attributes from some other relation, invoke the toaster. + * + * Note: below this point, heaptup is the data we actually intend to + * store into the relation; tup is the caller's original untoasted data. */ if (HeapTupleHasExternal(tup) || (MAXALIGN(tup->t_len) > TOAST_TUPLE_THRESHOLD)) - heap_tuple_toast_attrs(relation, tup, NULL); + heaptup = toast_insert_or_update(relation, tup, NULL); + else + heaptup = tup; /* Find buffer to insert this tuple into */ - buffer = RelationGetBufferForTuple(relation, tup->t_len, + buffer = RelationGetBufferForTuple(relation, heaptup->t_len, InvalidBuffer, use_fsm); /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); - RelationPutHeapTuple(relation, buffer, tup); + RelationPutHeapTuple(relation, buffer, heaptup); /* XLOG stuff */ if (relation->rd_istemp) @@ -1158,15 +1170,15 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, uint8 info = XLOG_HEAP_INSERT; xlrec.target.node = relation->rd_node; - xlrec.target.tid = tup->t_self; + xlrec.target.tid = heaptup->t_self; rdata[0].data = (char *) &xlrec; rdata[0].len = SizeOfHeapInsert; rdata[0].buffer = InvalidBuffer; rdata[0].next = &(rdata[1]); - xlhdr.t_natts = tup->t_data->t_natts; - xlhdr.t_infomask = tup->t_data->t_infomask; - xlhdr.t_hoff = tup->t_data->t_hoff; + xlhdr.t_natts = heaptup->t_data->t_natts; + xlhdr.t_infomask = heaptup->t_data->t_infomask; + xlhdr.t_hoff = heaptup->t_data->t_hoff; /* * note we mark rdata[1] as belonging to buffer; if XLogInsert decides @@ -1180,8 +1192,8 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, rdata[1].next = &(rdata[2]); /* PG73FORMAT: write bitmap [+ padding] [+ oid] + data */ - rdata[2].data = (char *) tup->t_data + offsetof(HeapTupleHeaderData, t_bits); - rdata[2].len = tup->t_len - offsetof(HeapTupleHeaderData, t_bits); + rdata[2].data = (char *) heaptup->t_data + offsetof(HeapTupleHeaderData, t_bits); + rdata[2].len = heaptup->t_len - offsetof(HeapTupleHeaderData, t_bits); rdata[2].buffer = buffer; rdata[2].buffer_std = true; rdata[2].next = NULL; @@ -1191,7 +1203,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, * page instead of restoring the whole thing. Set flag, and hide * buffer references from XLogInsert. */ - if (ItemPointerGetOffsetNumber(&(tup->t_self)) == FirstOffsetNumber && + if (ItemPointerGetOffsetNumber(&(heaptup->t_self)) == FirstOffsetNumber && PageGetMaxOffsetNumber(page) == FirstOffsetNumber) { info |= XLOG_HEAP_INIT_PAGE; @@ -1212,13 +1224,23 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, /* * If tuple is cachable, mark it for invalidation from the caches in case * we abort. Note it is OK to do this after WriteBuffer releases the - * buffer, because the "tup" data structure is all in local memory, not in - * the shared buffer. + * buffer, because the heaptup data structure is all in local memory, + * not in the shared buffer. */ - CacheInvalidateHeapTuple(relation, tup); + CacheInvalidateHeapTuple(relation, heaptup); pgstat_count_heap_insert(&relation->pgstat_info); + /* + * If heaptup is a private copy, release it. Don't forget to copy t_self + * back to the caller's image, too. + */ + if (heaptup != tup) + { + tup->t_self = heaptup->t_self; + heap_freetuple(heaptup); + } + return HeapTupleGetOid(tup); } @@ -1469,7 +1491,7 @@ l1: * context lock on the buffer first. */ if (HeapTupleHasExternal(&tp)) - heap_tuple_toast_attrs(relation, NULL, &tp); + toast_delete(relation, &tp); /* * Mark tuple for invalidation from system caches at next command @@ -1553,8 +1575,10 @@ simple_heap_delete(Relation relation, ItemPointer tid) * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated * (the last only possible if wait == false). * - * On success, newtup->t_self is set to the TID where the new tuple - * was inserted. + * On success, the header fields of *newtup are updated to match the new + * stored tuple; in particular, newtup->t_self is set to the TID where the + * new tuple was inserted. However, any TOAST changes in the new tuple's + * data are not reflected into *newtup. * * In the failure cases, the routine returns the tuple's t_ctid and t_xmax. * If t_ctid is the same as otid, the tuple was deleted; if different, the @@ -1570,6 +1594,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, TransactionId xid = GetCurrentTransactionId(); ItemId lp; HeapTupleData oldtup; + HeapTuple heaptup; PageHeader dp; Buffer buffer, newbuf; @@ -1760,11 +1785,12 @@ l2: * We need to invoke the toaster if there are already any out-of-line toasted * values present, or if the new tuple is over-threshold. */ + newtupsize = MAXALIGN(newtup->t_len); + need_toast = (HeapTupleHasExternal(&oldtup) || HeapTupleHasExternal(newtup) || - (MAXALIGN(newtup->t_len) > TOAST_TUPLE_THRESHOLD)); + newtupsize > TOAST_TUPLE_THRESHOLD); - newtupsize = MAXALIGN(newtup->t_len); pagefree = PageGetFreeSpace((Page) dp); if (need_toast || newtupsize > pagefree) @@ -1776,15 +1802,25 @@ l2: HEAP_MOVED); HeapTupleHeaderSetXmax(oldtup.t_data, xid); HeapTupleHeaderSetCmax(oldtup.t_data, cid); + /* temporarily make it look not-updated */ + oldtup.t_data->t_ctid = oldtup.t_self; already_marked = true; LockBuffer(buffer, BUFFER_LOCK_UNLOCK); - /* Let the toaster do its thing */ + /* + * Let the toaster do its thing, if needed. + * + * Note: below this point, heaptup is the data we actually intend to + * store into the relation; newtup is the caller's original untoasted + * data. + */ if (need_toast) { - heap_tuple_toast_attrs(relation, newtup, &oldtup); - newtupsize = MAXALIGN(newtup->t_len); + heaptup = toast_insert_or_update(relation, newtup, &oldtup); + newtupsize = MAXALIGN(heaptup->t_len); } + else + heaptup = newtup; /* * Now, do we need a new page for the tuple, or not? This is a bit @@ -1805,8 +1841,8 @@ l2: */ if (newtupsize > pagefree) { - /* Assume there's no chance to put newtup on same page. */ - newbuf = RelationGetBufferForTuple(relation, newtup->t_len, + /* Assume there's no chance to put heaptup on same page. */ + newbuf = RelationGetBufferForTuple(relation, heaptup->t_len, buffer, true); } else @@ -1823,7 +1859,7 @@ l2: * seldom be taken. */ LockBuffer(buffer, BUFFER_LOCK_UNLOCK); - newbuf = RelationGetBufferForTuple(relation, newtup->t_len, + newbuf = RelationGetBufferForTuple(relation, heaptup->t_len, buffer, true); } else @@ -1838,6 +1874,7 @@ l2: /* No TOAST work needed, and it'll fit on same page */ already_marked = false; newbuf = buffer; + heaptup = newtup; } /* @@ -1849,7 +1886,7 @@ l2: /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); - RelationPutHeapTuple(relation, newbuf, newtup); /* insert new tuple */ + RelationPutHeapTuple(relation, newbuf, heaptup); /* insert new tuple */ if (!already_marked) { @@ -1863,13 +1900,13 @@ l2: } /* record address of new tuple in t_ctid of old one */ - oldtup.t_data->t_ctid = newtup->t_self; + oldtup.t_data->t_ctid = heaptup->t_self; /* XLOG stuff */ if (!relation->rd_istemp) { XLogRecPtr recptr = log_heap_update(relation, buffer, oldtup.t_self, - newbuf, newtup, false); + newbuf, heaptup, false); if (newbuf != buffer) { @@ -1905,10 +1942,10 @@ l2: /* * If new tuple is cachable, mark it for invalidation from the caches in * case we abort. Note it is OK to do this after WriteBuffer releases the - * buffer, because the "newtup" data structure is all in local memory, not + * buffer, because the heaptup data structure is all in local memory, not * in the shared buffer. */ - CacheInvalidateHeapTuple(relation, newtup); + CacheInvalidateHeapTuple(relation, heaptup); /* * Release the lmgr tuple lock, if we had it. @@ -1918,6 +1955,16 @@ l2: pgstat_count_heap_update(&relation->pgstat_info); + /* + * If heaptup is a private copy, release it. Don't forget to copy t_self + * back to the caller's image, too. + */ + if (heaptup != newtup) + { + newtup->t_self = heaptup->t_self; + heap_freetuple(heaptup); + } + return HeapTupleMayBeUpdated; } diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c index fd20f111b80..99d725c27cc 100644 --- a/src/backend/access/heap/tuptoaster.c +++ b/src/backend/access/heap/tuptoaster.c @@ -8,14 +8,17 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.53 2005/10/15 02:49:09 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.54 2005/11/20 18:38:20 tgl Exp $ * * * INTERFACE ROUTINES - * heap_tuple_toast_attrs - + * toast_insert_or_update - * Try to make a given tuple fit into one page by compressing * or moving off attributes * + * toast_delete - + * Reclaim toast storage when a tuple is deleted + * * heap_tuple_untoast_attr - * Fetch back a given value from the "secondary" relation * @@ -40,34 +43,13 @@ #undef TOAST_DEBUG -static void toast_delete(Relation rel, HeapTuple oldtup); static void toast_delete_datum(Relation rel, Datum value); -static void toast_insert_or_update(Relation rel, HeapTuple newtup, - HeapTuple oldtup); static Datum toast_save_datum(Relation rel, Datum value); static varattrib *toast_fetch_datum(varattrib *attr); static varattrib *toast_fetch_datum_slice(varattrib *attr, int32 sliceoffset, int32 length); -/* ---------- - * heap_tuple_toast_attrs - - * - * This is the central public entry point for toasting from heapam. - * - * Calls the appropriate event specific action. - * ---------- - */ -void -heap_tuple_toast_attrs(Relation rel, HeapTuple newtup, HeapTuple oldtup) -{ - if (newtup == NULL) - toast_delete(rel, oldtup); - else - toast_insert_or_update(rel, newtup, oldtup); -} - - /* ---------- * heap_tuple_fetch_attr - * @@ -305,7 +287,7 @@ toast_datum_size(Datum value) * Cascaded delete toast-entries on DELETE * ---------- */ -static void +void toast_delete(Relation rel, HeapTuple oldtup) { TupleDesc tupleDesc; @@ -355,11 +337,22 @@ toast_delete(Relation rel, HeapTuple oldtup) * * Delete no-longer-used toast-entries and create new ones to * make the new tuple fit on INSERT or UPDATE + * + * Inputs: + * newtup: the candidate new tuple to be inserted + * oldtup: the old row version for UPDATE, or NULL for INSERT + * Result: + * either newtup if no toasting is needed, or a palloc'd modified tuple + * that is what should actually get stored + * + * NOTE: neither newtup nor oldtup will be modified. This is a change + * from the pre-8.1 API of this routine. * ---------- */ -static void +HeapTuple toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup) { + HeapTuple result_tuple; TupleDesc tupleDesc; Form_pg_attribute *att; int numAttrs; @@ -757,7 +750,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup) if (need_change) { HeapTupleHeader olddata = newtup->t_data; - char *new_data; + HeapTupleHeader new_data; int32 new_len; /* @@ -775,14 +768,18 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup) toast_values, toast_isnull); /* - * Allocate new tuple in same context as old one. + * Allocate and zero the space needed, and fill HeapTupleData fields. */ - new_data = (char *) MemoryContextAlloc(newtup->t_datamcxt, new_len); - newtup->t_data = (HeapTupleHeader) new_data; - newtup->t_len = new_len; + result_tuple = (HeapTuple) palloc0(HEAPTUPLESIZE + new_len); + result_tuple->t_len = new_len; + result_tuple->t_self = newtup->t_self; + result_tuple->t_tableOid = newtup->t_tableOid; + result_tuple->t_datamcxt = CurrentMemoryContext; + new_data = (HeapTupleHeader) ((char *) result_tuple + HEAPTUPLESIZE); + result_tuple->t_data = new_data; /* - * Put the tuple header and the changed values into place + * Put the existing tuple header and the changed values into place */ memcpy(new_data, olddata, olddata->t_hoff); @@ -790,16 +787,11 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup) toast_values, toast_isnull, (char *) new_data + olddata->t_hoff, - &(newtup->t_data->t_infomask), - has_nulls ? newtup->t_data->t_bits : NULL); - - /* - * In the case we modified a previously modified tuple again, free the - * memory from the previous run - */ - if ((char *) olddata != ((char *) newtup + HEAPTUPLESIZE)) - pfree(olddata); + &(new_data->t_infomask), + has_nulls ? new_data->t_bits : NULL); } + else + result_tuple = newtup; /* * Free allocated temp values @@ -816,6 +808,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup) for (i = 0; i < numAttrs; i++) if (toast_delold[i]) toast_delete_datum(rel, toast_oldvalues[i]); + + return result_tuple; } diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 6f79c4d58b0..c0c6cfbe500 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -26,7 +26,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.259 2005/11/19 20:57:44 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.260 2005/11/20 18:38:20 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1445,16 +1445,6 @@ ExecInsert(TupleTableSlot *slot, estate->es_lastoid = newId; setLastTid(&(tuple->t_self)); - /* - * KLUGE SOLUTION for bug found post 8.1 release: if the tuple toaster - * fired on the tuple then it changed the physical tuple inside the - * tuple slot, leaving any extracted information invalid. Mark the - * extracted state invalid just in case. Need to fix things so that - * the toaster gets to run against the tuple before we materialize it, - * but that's way too invasive for a stable branch. - */ - slot->tts_nvalid = 0; - /* * insert index entries for tuple */ @@ -1709,16 +1699,6 @@ lreplace:; IncrReplaced(); (estate->es_processed)++; - /* - * KLUGE SOLUTION for bug found post 8.1 release: if the tuple toaster - * fired on the tuple then it changed the physical tuple inside the - * tuple slot, leaving any extracted information invalid. Mark the - * extracted state invalid just in case. Need to fix things so that - * the toaster gets to run against the tuple before we materialize it, - * but that's way too invasive for a stable branch. - */ - slot->tts_nvalid = 0; - /* * Note: instead of having to update the old index tuples associated with * the heap tuple, all we do is form and insert new index tuples. This is diff --git a/src/include/access/tuptoaster.h b/src/include/access/tuptoaster.h index b45df277b1e..b5870e69dcb 100644 --- a/src/include/access/tuptoaster.h +++ b/src/include/access/tuptoaster.h @@ -6,7 +6,7 @@ * * Copyright (c) 2000-2005, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/include/access/tuptoaster.h,v 1.23 2005/07/06 19:02:53 momjian Exp $ + * $PostgreSQL: pgsql/src/include/access/tuptoaster.h,v 1.24 2005/11/20 18:38:20 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -66,19 +66,21 @@ /* ---------- - * heap_tuple_toast_attrs() - + * toast_insert_or_update - * - * Called by heap_insert(), heap_update() and heap_delete(). - * Outdates any no-longer-needed toast entries referenced - * by oldtup and creates new ones until newtup is no more than - * TOAST_TUPLE_TARGET (or we run out of toastable values). - * Possibly modifies newtup by replacing the t_data part! + * Called by heap_insert() and heap_update(). + * ---------- + */ +extern HeapTuple toast_insert_or_update(Relation rel, + HeapTuple newtup, HeapTuple oldtup); + +/* ---------- + * toast_delete - * - * oldtup is NULL if insert, newtup is NULL if delete. + * Called by heap_delete(). * ---------- */ -extern void heap_tuple_toast_attrs(Relation rel, - HeapTuple newtup, HeapTuple oldtup); +extern void toast_delete(Relation rel, HeapTuple oldtup); /* ---------- * heap_tuple_fetch_attr() - -- GitLab