From: Tom Lane Date: Mon, 14 Mar 2005 04:41:13 +0000 (+0000) Subject: Avoid O(N^2) overhead in repeated nocachegetattr calls when columns of X-Git-Tag: REL8_1_0BETA1~1214 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=a9b05bdc8330b378cd2df7910ca0beaa500223fa;p=postgresql Avoid O(N^2) overhead in repeated nocachegetattr calls when columns of a tuple are being accessed via ExecEvalVar and the attcacheoff shortcut isn't usable (due to nulls and/or varlena columns). To do this, cache Datums extracted from a tuple in the associated TupleTableSlot. Also some code cleanup in and around the TupleTable handling. Atsushi Ogawa with some kibitzing by Tom Lane. --- diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c index 42643a8752..22d5e44dc3 100644 --- a/src/backend/access/common/heaptuple.c +++ b/src/backend/access/common/heaptuple.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/common/heaptuple.c,v 1.96 2005/01/27 23:23:49 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/access/common/heaptuple.c,v 1.97 2005/03/14 04:41:12 tgl Exp $ * * NOTES * The old interface functions have been converted to macros @@ -23,6 +23,7 @@ #include "access/heapam.h" #include "access/tuptoaster.h" #include "catalog/pg_type.h" +#include "executor/tuptable.h" /* ---------------------------------------------------------------- @@ -751,6 +752,7 @@ heap_deformtuple(HeapTuple tuple, char *nulls) { HeapTupleHeader tup = tuple->t_data; + bool hasnulls = HeapTupleHasNulls(tuple); Form_pg_attribute *att = tupleDesc->attrs; int tdesc_natts = tupleDesc->natts; int natts; /* number of atts to extract */ @@ -775,7 +777,9 @@ heap_deformtuple(HeapTuple tuple, for (attnum = 0; attnum < natts; attnum++) { - if (HeapTupleHasNulls(tuple) && att_isnull(attnum, bp)) + Form_pg_attribute thisatt = att[attnum]; + + if (hasnulls && att_isnull(attnum, bp)) { values[attnum] = (Datum) 0; nulls[attnum] = 'n'; @@ -785,21 +789,21 @@ heap_deformtuple(HeapTuple tuple, nulls[attnum] = ' '; - if (!slow && att[attnum]->attcacheoff >= 0) - off = att[attnum]->attcacheoff; + if (!slow && thisatt->attcacheoff >= 0) + off = thisatt->attcacheoff; else { - off = att_align(off, att[attnum]->attalign); + off = att_align(off, thisatt->attalign); if (!slow) - att[attnum]->attcacheoff = off; + thisatt->attcacheoff = off; } - values[attnum] = fetchatt(att[attnum], tp + off); + values[attnum] = fetchatt(thisatt, tp + off); - off = att_addlength(off, att[attnum]->attlen, tp + off); + off = att_addlength(off, thisatt->attlen, tp + off); - if (att[attnum]->attlen <= 0) + if (thisatt->attlen <= 0) slow = true; /* can't use attcacheoff anymore */ } @@ -814,6 +818,177 @@ heap_deformtuple(HeapTuple tuple, } } +/* ---------------- + * slot_deformtuple + * + * Given a TupleTableSlot, extract data into cache_values array + * from the slot's tuple. + * + * This is essentially an incremental version of heap_deformtuple: + * on each call we extract attributes up to the one needed, without + * re-computing information about previously extracted attributes. + * slot->cache_natts is the number of attributes already extracted. + * + * This only gets called from slot_getattr. Note that slot_getattr + * must check for a null attribute since we don't create an array + * of null indicators. + * ---------------- + */ +static void +slot_deformtuple(TupleTableSlot *slot, int natts) +{ + HeapTuple tuple = slot->val; + TupleDesc tupleDesc = slot->ttc_tupleDescriptor; + Datum *values = slot->cache_values; + HeapTupleHeader tup = tuple->t_data; + bool hasnulls = HeapTupleHasNulls(tuple); + Form_pg_attribute *att = tupleDesc->attrs; + int attnum; + char *tp; /* ptr to tuple data */ + long off; /* offset in tuple data */ + bits8 *bp = tup->t_bits; /* ptr to null bitmask in tuple */ + bool slow; /* can we use/set attcacheoff? */ + + /* + * Check whether the first call for this tuple, and initialize or + * restore loop state. + */ + attnum = slot->cache_natts; + if (attnum == 0) + { + /* Start from the first attribute */ + off = 0; + slow = false; + } + else + { + /* Restore state from previous execution */ + off = slot->cache_off; + slow = slot->cache_slow; + } + + tp = (char *) tup + tup->t_hoff; + + for (; attnum < natts; attnum++) + { + Form_pg_attribute thisatt = att[attnum]; + + if (hasnulls && att_isnull(attnum, bp)) + { + values[attnum] = (Datum) 0; + slow = true; /* can't use attcacheoff anymore */ + continue; + } + + if (!slow && thisatt->attcacheoff >= 0) + off = thisatt->attcacheoff; + else + { + off = att_align(off, thisatt->attalign); + + if (!slow) + thisatt->attcacheoff = off; + } + + values[attnum] = fetchatt(thisatt, tp + off); + + off = att_addlength(off, thisatt->attlen, tp + off); + + if (thisatt->attlen <= 0) + slow = true; /* can't use attcacheoff anymore */ + } + + /* + * Save state for next execution + */ + slot->cache_natts = attnum; + slot->cache_off = off; + slot->cache_slow = slow; +} + +/* -------------------------------- + * slot_getattr + * + * This function fetches an attribute of the slot's current tuple. + * It is functionally equivalent to heap_getattr, but fetches of + * multiple attributes of the same tuple will be optimized better, + * because we avoid O(N^2) behavior from multiple calls of + * nocachegetattr(), even when attcacheoff isn't usable. + * -------------------------------- + */ +Datum +slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull) +{ + HeapTuple tuple = slot->val; + TupleDesc tupleDesc = slot->ttc_tupleDescriptor; + HeapTupleHeader tup; + + /* + * system attributes are handled by heap_getsysattr + */ + if (attnum <= 0) + return heap_getsysattr(tuple, attnum, tupleDesc, isnull); + + /* + * check if attnum is out of range according to either the tupdesc + * or the tuple itself; if so return NULL + */ + tup = tuple->t_data; + + if (attnum > tup->t_natts || attnum > tupleDesc->natts) + { + *isnull = true; + return (Datum) 0; + } + + /* + * check if target attribute is null + */ + if (HeapTupleHasNulls(tuple) && att_isnull(attnum - 1, tup->t_bits)) + { + *isnull = true; + return (Datum) 0; + } + + /* + * If the attribute's column has been dropped, we force a NULL + * result. This case should not happen in normal use, but it could + * happen if we are executing a plan cached before the column was + * dropped. + */ + if (tupleDesc->attrs[attnum - 1]->attisdropped) + { + *isnull = true; + return (Datum) 0; + } + + /* + * If attribute wasn't already extracted, extract it and preceding + * attributes. + */ + if (attnum > slot->cache_natts) + { + /* + * If first time for this TupleTableSlot, allocate the cache + * workspace. It must have the same lifetime as the slot, so allocate + * it in the slot's own context. We size the array according to what + * the tupdesc says, NOT the tuple. + */ + if (slot->cache_values == NULL) + slot->cache_values = (Datum *) + MemoryContextAlloc(slot->ttc_mcxt, + tupleDesc->natts * sizeof(Datum)); + + slot_deformtuple(slot, attnum); + } + + /* + * The result is acquired from cache_values array. + */ + *isnull = false; + return slot->cache_values[attnum - 1]; +} + /* ---------------- * heap_freetuple * ---------------- diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c index c06b3b8d36..44ed126fca 100644 --- a/src/backend/access/heap/tuptoaster.c +++ b/src/backend/access/heap/tuptoaster.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.47 2005/01/01 05:43:06 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.48 2005/03/14 04:41:12 tgl Exp $ * * * INTERFACE ROUTINES @@ -1197,9 +1197,9 @@ toast_fetch_datum(varattrib *attr) /* * Have a chunk, extract the sequence number and the data */ - residx = DatumGetInt32(heap_getattr(ttup, 2, toasttupDesc, &isnull)); + residx = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull)); Assert(!isnull); - chunk = DatumGetPointer(heap_getattr(ttup, 3, toasttupDesc, &isnull)); + chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull)); Assert(!isnull); chunksize = VARATT_SIZE(chunk) - VARHDRSZ; @@ -1372,9 +1372,9 @@ toast_fetch_datum_slice(varattrib *attr, int32 sliceoffset, int32 length) /* * Have a chunk, extract the sequence number and the data */ - residx = DatumGetInt32(heap_getattr(ttup, 2, toasttupDesc, &isnull)); + residx = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull)); Assert(!isnull); - chunk = DatumGetPointer(heap_getattr(ttup, 3, toasttupDesc, &isnull)); + chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull)); Assert(!isnull); chunksize = VARATT_SIZE(chunk) - VARHDRSZ; diff --git a/src/backend/executor/execJunk.c b/src/backend/executor/execJunk.c index 5087c7bfc3..f747976acf 100644 --- a/src/backend/executor/execJunk.c +++ b/src/backend/executor/execJunk.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execJunk.c,v 1.46 2004/12/31 21:59:45 pgsql Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execJunk.c,v 1.47 2005/03/14 04:41:12 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -209,20 +209,13 @@ ExecGetJunkAttribute(JunkFilter *junkfilter, Datum *value, bool *isNull) { - List *targetList; ListCell *t; - AttrNumber resno; - TupleDesc tupType; - HeapTuple tuple; /* - * first look in the junkfilter's target list for an attribute with + * Look in the junkfilter's target list for an attribute with * the given name */ - resno = InvalidAttrNumber; - targetList = junkfilter->jf_targetList; - - foreach(t, targetList) + foreach(t, junkfilter->jf_targetList) { TargetEntry *tle = lfirst(t); Resdom *resdom = tle->resdom; @@ -231,26 +224,13 @@ ExecGetJunkAttribute(JunkFilter *junkfilter, (strcmp(resdom->resname, attrName) == 0)) { /* We found it ! */ - resno = resdom->resno; - break; + *value = slot_getattr(slot, resdom->resno, isNull); + return true; } } - if (resno == InvalidAttrNumber) - { - /* Ooops! We couldn't find this attribute... */ - return false; - } - - /* - * Now extract the attribute value from the tuple. - */ - tuple = slot->val; - tupType = slot->ttc_tupleDescriptor; - - *value = heap_getattr(tuple, resno, tupType, isNull); - - return true; + /* Ooops! We couldn't find this attribute... */ + return false; } /* diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 0199df0f71..87cc201a3e 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.171 2004/12/31 21:59:45 pgsql Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.172 2005/03/14 04:41:12 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -438,11 +438,8 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { Var *variable = (Var *) exprstate->expr; - Datum result; TupleTableSlot *slot; AttrNumber attnum; - HeapTuple heapTuple; - TupleDesc tuple_type; if (isDone) *isDone = ExprSingleResult; @@ -475,35 +472,19 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, break; } - /* - * extract tuple information from the slot - */ - heapTuple = slot->val; - tuple_type = slot->ttc_tupleDescriptor; - +#ifdef USE_ASSERT_CHECKING /* * Some checks that are only applied for user attribute numbers (bogus - * system attnums will be caught inside heap_getattr). + * system attnums will be caught inside slot_getattr). */ if (attnum > 0) { - /* - * This assert checks that the attnum is valid. - */ - Assert(attnum <= tuple_type->natts && - tuple_type->attrs[attnum - 1] != NULL); + TupleDesc tuple_type = slot->ttc_tupleDescriptor; /* - * If the attribute's column has been dropped, we force a NULL - * result. This case should not happen in normal use, but it could - * happen if we are executing a plan cached before the column was - * dropped. + * This assert checks that the attnum is valid. */ - if (tuple_type->attrs[attnum - 1]->attisdropped) - { - *isNull = true; - return (Datum) 0; - } + Assert(attnum <= tuple_type->natts); /* * This assert checks that the datatype the plan expects to get @@ -515,16 +496,12 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, * Note that we can't check dropped columns, since their atttypid has * been zeroed. */ - Assert(variable->vartype == tuple_type->attrs[attnum - 1]->atttypid); + Assert(variable->vartype == tuple_type->attrs[attnum - 1]->atttypid || + tuple_type->attrs[attnum - 1]->attisdropped); } +#endif /* USE_ASSERT_CHECKING */ - result = heap_getattr(heapTuple, /* tuple containing attribute */ - attnum, /* attribute number of desired - * attribute */ - tuple_type, /* tuple descriptor of tuple */ - isNull); /* return: is attribute null? */ - - return result; + return slot_getattr(slot, attnum, isNull); } /* ---------------------------------------------------------------- diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index 2864589ebb..8574efc95d 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -15,7 +15,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execTuples.c,v 1.83 2004/12/31 21:59:45 pgsql Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execTuples.c,v 1.84 2005/03/14 04:41:12 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -31,13 +31,11 @@ * * SLOT ACCESSORS * ExecStoreTuple - store a tuple in the table - * ExecFetchTuple - fetch a tuple from the table * ExecClearTuple - clear contents of a table slot * ExecSetSlotDescriptor - set a slot's tuple descriptor - * ExecSetSlotDescriptorIsNew - diddle the slot-desc-is-new flag * * SLOT STATUS PREDICATES - * TupIsNull - true when slot contains no tuple(Macro) + * TupIsNull - true when slot contains no tuple (macro) * * CONVENIENCE INITIALIZATION ROUTINES * ExecInitResultTupleSlot \ convenience routines to initialize @@ -60,7 +58,7 @@ * - ExecInitSeqScan() calls ExecInitScanTupleSlot() and * ExecInitResultTupleSlot() to reserve places in the tuple * table for the tuples returned by the access methods and the - * tuples resulting from preforming target list projections. + * tuples resulting from performing target list projections. * * During ExecRun() * ---------------- @@ -71,8 +69,8 @@ * tuple from ExecProject() and place it into the result tuple slot. * * - ExecutePlan() calls ExecRetrieve() which gets the tuple out of - * the slot passed to it by calling ExecFetchTuple(). this tuple - * is then returned. + * the slot passed to it (by direct access to slot->val, which is + * ugly but not worth changing). this tuple is then returned. * * At ExecEnd() * ---------------- @@ -87,23 +85,6 @@ * this information is also kept in the ExprContext of each node. * Soon the executor will be redesigned and ExprContext's will contain * only slot pointers. -cim 3/14/91 - * - * NOTES - * The tuple table stuff is relatively new, put here to alleviate - * the process growth problems in the executor. The other routines - * are old (from the original lisp system) and may someday become - * obsolete. -cim 6/23/90 - * - * In the implementation of nested-dot queries such as - * "retrieve (EMP.hobbies.all)", a single scan may return tuples - * of many types, so now we return pointers to tuple descriptors - * along with tuples returned via the tuple table. This means - * we now have a bunch of routines to diddle the slot descriptors - * too. -cim 1/18/90 - * - * The tuple table stuff depends on the executor/tuptable.h macros, - * and the TupleTableSlot node in execnodes.h. - * */ #include "postgres.h" @@ -124,43 +105,52 @@ static TupleDesc ExecTypeFromTLInternal(List *targetList, * tuple table create/delete functions * ---------------------------------------------------------------- */ + /* -------------------------------- * ExecCreateTupleTable * - * This creates a new tuple table of the specified initial - * size. If the size is insufficient, ExecAllocTableSlot() - * will grow the table as necessary. + * This creates a new tuple table of the specified size. * * This should be used by InitPlan() to allocate the table. * The table's address will be stored in the EState structure. * -------------------------------- */ -TupleTable /* return: address of table */ -ExecCreateTupleTable(int initialSize) /* initial number of slots in - * table */ +TupleTable +ExecCreateTupleTable(int tableSize) { - TupleTable newtable; /* newly allocated table */ - TupleTableSlot *array; /* newly allocated slot array */ + TupleTable newtable; + int i; /* * sanity checks */ - Assert(initialSize >= 1); + Assert(tableSize >= 1); /* - * Now allocate our new table along with space for the pointers to the - * tuples. Zero out the slots. + * allocate the table itself */ - - newtable = (TupleTable) palloc(sizeof(TupleTableData)); - array = (TupleTableSlot *) palloc0(initialSize * sizeof(TupleTableSlot)); + newtable = (TupleTable) palloc(sizeof(TupleTableData) + + (tableSize - 1) * sizeof(TupleTableSlot)); + newtable->size = tableSize; + newtable->next = 0; /* - * initialize the new table and return it to the caller. + * initialize all the slots to empty states */ - newtable->size = initialSize; - newtable->next = 0; - newtable->array = array; + for (i = 0; i < tableSize; i++) + { + TupleTableSlot *slot = &(newtable->array[i]); + + slot->type = T_TupleTableSlot; + slot->val = NULL; + slot->ttc_tupleDescriptor = NULL; + slot->ttc_shouldFree = false; + slot->ttc_shouldFreeDesc = false; + slot->ttc_buffer = InvalidBuffer; + slot->ttc_mcxt = CurrentMemoryContext; + slot->cache_values = NULL; + slot->cache_natts = 0; /* mark slot_getattr state invalid */ + } return newtable; } @@ -178,21 +168,11 @@ ExecDropTupleTable(TupleTable table, /* tuple table */ bool shouldFree) /* true if we should free slot * contents */ { - int next; /* next available slot */ - TupleTableSlot *array; /* start of table array */ - int i; /* counter */ - /* * sanity checks */ Assert(table != NULL); - /* - * get information from the table - */ - array = table->array; - next = table->next; - /* * first free all the valid pointers in the tuple array and drop * refcounts of any referenced buffers, if that's what the caller @@ -201,27 +181,60 @@ ExecDropTupleTable(TupleTable table, /* tuple table */ */ if (shouldFree) { + int next = table->next; + int i; + for (i = 0; i < next; i++) { - ExecClearTuple(&array[i]); - if (array[i].ttc_shouldFreeDesc && - array[i].ttc_tupleDescriptor != NULL) - FreeTupleDesc(array[i].ttc_tupleDescriptor); + TupleTableSlot *slot = &(table->array[i]); + + ExecClearTuple(slot); + if (slot->ttc_shouldFreeDesc) + FreeTupleDesc(slot->ttc_tupleDescriptor); + if (slot->cache_values) + pfree(slot->cache_values); } } /* - * finally free the tuple array and the table itself. + * finally free the tuple table itself. */ - pfree(array); pfree(table); } +/* -------------------------------- + * MakeTupleTableSlot + * + * This routine makes an empty standalone TupleTableSlot. + * It really shouldn't exist, but there are a few places + * that do this, so we may as well centralize the knowledge + * of what's in one ... + * -------------------------------- + */ +TupleTableSlot * +MakeTupleTableSlot(void) +{ + TupleTableSlot *slot = makeNode(TupleTableSlot); + + /* This should match ExecCreateTupleTable() */ + slot->val = NULL; + slot->ttc_tupleDescriptor = NULL; + slot->ttc_shouldFree = false; + slot->ttc_shouldFreeDesc = false; + slot->ttc_buffer = InvalidBuffer; + slot->ttc_mcxt = CurrentMemoryContext; + slot->cache_values = NULL; + slot->cache_natts = 0; /* mark slot_getattr state invalid */ + + return slot; +} + /* ---------------------------------------------------------------- * tuple table slot reservation functions * ---------------------------------------------------------------- */ + /* -------------------------------- * ExecAllocTableSlot * @@ -236,7 +249,6 @@ TupleTableSlot * ExecAllocTableSlot(TupleTable table) { int slotnum; /* new slot number */ - TupleTableSlot *slot; /* * sanity checks @@ -244,66 +256,17 @@ ExecAllocTableSlot(TupleTable table) Assert(table != NULL); /* - * if our table is full we have to allocate a larger size table. Since - * ExecAllocTableSlot() is only called before the table is ever used - * to store tuples, we don't have to worry about the contents of the - * old table. If this changes, then we will have to preserve the - * contents. -cim 6/23/90 - * - * Unfortunately, we *cannot* do this. All of the nodes in the plan that - * have already initialized their slots will have pointers into - * _freed_ memory. This leads to bad ends. We now count the number - * of slots we will need and create all the slots we will need ahead - * of time. The if below should never happen now. Fail if it does. - * -mer 4 Aug 1992 + * We expect that the table was made big enough to begin with. + * We cannot reallocate it on the fly since previous plan nodes + * have already got pointers to individual entries. */ if (table->next >= table->size) elog(ERROR, "plan requires more slots than are available"); - /* - * at this point, space in the table is guaranteed so we reserve the - * next slot, initialize and return it. - */ slotnum = table->next; table->next++; - slot = &(table->array[slotnum]); - - /* Make sure the allocated slot is valid (and empty) */ - slot->type = T_TupleTableSlot; - slot->val = NULL; - slot->ttc_shouldFree = true; - slot->ttc_descIsNew = true; - slot->ttc_shouldFreeDesc = true; - slot->ttc_tupleDescriptor = NULL; - slot->ttc_buffer = InvalidBuffer; - - return slot; -} - -/* -------------------------------- - * MakeTupleTableSlot - * - * This routine makes an empty standalone TupleTableSlot. - * It really shouldn't exist, but there are a few places - * that do this, so we may as well centralize the knowledge - * of what's in one ... - * -------------------------------- - */ -TupleTableSlot * -MakeTupleTableSlot(void) -{ - TupleTableSlot *slot = makeNode(TupleTableSlot); - - /* This should match ExecAllocTableSlot() */ - slot->val = NULL; - slot->ttc_shouldFree = true; - slot->ttc_descIsNew = true; - slot->ttc_shouldFreeDesc = true; - slot->ttc_tupleDescriptor = NULL; - slot->ttc_buffer = InvalidBuffer; - - return slot; + return &(table->array[slotnum]); } /* ---------------------------------------------------------------- @@ -356,21 +319,22 @@ ExecStoreTuple(HeapTuple tuple, /* passing shouldFree=true for a tuple on a disk page is not sane */ Assert(BufferIsValid(buffer) ? (!shouldFree) : true); - /* clear out any old contents of the slot */ + /* + * clear out any old contents of the slot + */ ExecClearTuple(slot); /* - * store the new tuple into the specified slot and return the slot - * into which we stored the tuple. + * store the new tuple into the specified slot. */ slot->val = tuple; - slot->ttc_buffer = buffer; slot->ttc_shouldFree = shouldFree; /* * If tuple is on a disk page, keep the page pinned as long as we hold - * a pointer into it. + * a pointer into it. We assume the caller already has such a pin. */ + slot->ttc_buffer = buffer; if (BufferIsValid(buffer)) IncrBufferRefCount(buffer); @@ -388,27 +352,23 @@ ExecStoreTuple(HeapTuple tuple, TupleTableSlot * /* return: slot passed */ ExecClearTuple(TupleTableSlot *slot) /* slot in which to store tuple */ { - HeapTuple oldtuple; /* prior contents of slot */ - /* * sanity checks */ Assert(slot != NULL); /* - * get information from the tuple table - */ - oldtuple = slot->val; - - /* - * free the old contents of the specified slot if necessary. + * Free the old contents of the specified slot if necessary. (Note: + * we allow slot->val to be null even when shouldFree is true, because + * there are a few callers of ExecStoreTuple that are too lazy to + * distinguish whether they are passing a NULL tuple, and always pass + * shouldFree = true.) */ - if (slot->ttc_shouldFree && oldtuple != NULL) - heap_freetuple(oldtuple); + if (slot->ttc_shouldFree && slot->val != NULL) + heap_freetuple(slot->val); slot->val = NULL; - - slot->ttc_shouldFree = true; /* probably useless code... */ + slot->ttc_shouldFree = false; /* * Drop the pin on the referenced buffer, if there is one. @@ -418,6 +378,11 @@ ExecClearTuple(TupleTableSlot *slot) /* slot in which to store tuple */ slot->ttc_buffer = InvalidBuffer; + /* + * mark slot_getattr state invalid + */ + slot->cache_natts = 0; + return slot; } @@ -433,36 +398,31 @@ ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */ TupleDesc tupdesc, /* new tuple descriptor */ bool shouldFree) /* is desc owned by slot? */ { - if (slot->ttc_shouldFreeDesc && - slot->ttc_tupleDescriptor != NULL) + if (slot->ttc_shouldFreeDesc) FreeTupleDesc(slot->ttc_tupleDescriptor); slot->ttc_tupleDescriptor = tupdesc; slot->ttc_shouldFreeDesc = shouldFree; -} -/* -------------------------------- - * ExecSetSlotDescriptorIsNew - * - * This function is used to change the setting of the "isNew" flag - * -------------------------------- - */ -void -ExecSetSlotDescriptorIsNew(TupleTableSlot *slot, /* slot to change */ - bool isNew) /* "isNew" setting */ -{ - slot->ttc_descIsNew = isNew; + /* + * mark slot_getattr state invalid + */ + slot->cache_natts = 0; + + /* + * release any old cache array since tupledesc's natts may have changed + */ + if (slot->cache_values) + pfree(slot->cache_values); + slot->cache_values = NULL; } -/* ---------------------------------------------------------------- - * tuple table slot status predicates - * ---------------------------------------------------------------- - */ /* ---------------------------------------------------------------- * convenience initialization routines * ---------------------------------------------------------------- */ + /* -------------------------------- * ExecInit{Result,Scan,Extra}TupleSlot * diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 6fa3bbf455..3b73d5ea8a 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/access/heapam.h,v 1.94 2005/01/27 23:24:11 neilc Exp $ + * $PostgreSQL: pgsql/src/include/access/heapam.h,v 1.95 2005/03/14 04:41:13 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -40,9 +40,6 @@ * ---------------- */ -extern Datum nocachegetattr(HeapTuple tup, int attnum, - TupleDesc att, bool *isnull); - #if !defined(DISABLE_COMPLEX_MACRO) #define fastgetattr(tup, attnum, tupleDesc, isnull) \ @@ -115,9 +112,6 @@ extern Datum fastgetattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, ) \ ) -extern Datum heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, - bool *isnull); - /* ---------------- * function prototypes for heap access method @@ -191,6 +185,8 @@ extern void DataFill(char *data, TupleDesc tupleDesc, extern int heap_attisnull(HeapTuple tup, int attnum); extern Datum nocachegetattr(HeapTuple tup, int attnum, TupleDesc att, bool *isnull); +extern Datum heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, + bool *isnull); extern HeapTuple heap_copytuple(HeapTuple tuple); extern void heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest); extern HeapTuple heap_formtuple(TupleDesc tupleDescriptor, diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index f07bdd89d1..8eb357636a 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.115 2004/12/31 22:03:29 pgsql Exp $ + * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.116 2005/03/14 04:41:13 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -158,10 +158,10 @@ extern void ExecAssignScanProjectionInfo(ScanState *node); /* * prototypes from functions in execTuples.c */ -extern TupleTable ExecCreateTupleTable(int initialSize); +extern TupleTable ExecCreateTupleTable(int tableSize); extern void ExecDropTupleTable(TupleTable table, bool shouldFree); -extern TupleTableSlot *ExecAllocTableSlot(TupleTable table); extern TupleTableSlot *MakeTupleTableSlot(void); +extern TupleTableSlot *ExecAllocTableSlot(TupleTable table); extern TupleTableSlot *ExecStoreTuple(HeapTuple tuple, TupleTableSlot *slot, Buffer buffer, @@ -169,7 +169,6 @@ extern TupleTableSlot *ExecStoreTuple(HeapTuple tuple, extern TupleTableSlot *ExecClearTuple(TupleTableSlot *slot); extern void ExecSetSlotDescriptor(TupleTableSlot *slot, TupleDesc tupdesc, bool shouldFree); -extern void ExecSetSlotDescriptorIsNew(TupleTableSlot *slot, bool isNew); extern void ExecInitResultTupleSlot(EState *estate, PlanState *planstate); extern void ExecInitScanTupleSlot(EState *estate, ScanState *scanstate); extern TupleTableSlot *ExecInitExtraTupleSlot(EState *estate); diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h index e496a666ce..90791366ff 100644 --- a/src/include/executor/tuptable.h +++ b/src/include/executor/tuptable.h @@ -7,11 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/executor/tuptable.h,v 1.26 2004/12/31 22:03:29 pgsql Exp $ - * - * NOTES - * The tuple table interface is getting pretty ugly. - * It should be redesigned soon. + * $PostgreSQL: pgsql/src/include/executor/tuptable.h,v 1.27 2005/03/14 04:41:13 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,65 +16,60 @@ #include "access/htup.h" -/* ---------------- - * The executor tuple table is managed and manipulated by special - * code in executor/execTuples.c. - * - * TupleTableSlot information - * - * val current tuple, or NULL if no tuple - * shouldFree boolean - should we pfree() tuple - * descIsNew boolean - true when tupleDescriptor changes - * tupleDescriptor type information for the tuple data - * shouldFreeDesc boolean - should we free tupleDescriptor - * buffer the buffer for tuples pointing to disk pages - * - * The executor stores pointers to tuples in a ``tuple table'' - * which is composed of TupleTableSlots. Sometimes the tuples - * are pointers to buffer pages, while others are pointers to - * palloc'ed memory; the shouldFree variable tells us when - * we may call pfree() on a tuple. -cim 9/23/90 - * - * If buffer is not InvalidBuffer, then the slot is holding a pin - * on the indicated buffer page; drop the pin when we release the - * slot's reference to that buffer. - * - * In the implementation of nested-dot queries such as - * "retrieve (EMP.hobbies.all)", a single scan may return tuples - * of many types, so now we return pointers to tuple descriptors - * along with tuples returned via the tuple table. -cim 1/18/90 + +/* + * The executor stores pointers to tuples in a "tuple table" + * which is composed of TupleTableSlots. Sometimes the tuples + * are pointers to buffer pages, while others are pointers to + * palloc'ed memory; the shouldFree variable tells us whether + * we may call pfree() on a tuple. When shouldFree is true, + * the tuple is "owned" by the TupleTableSlot and should be + * freed when the slot's reference to the tuple is dropped. * - * shouldFreeDesc is similar to shouldFree: if it's true, then the - * tupleDescriptor is "owned" by the TupleTableSlot and should be - * freed when the slot's reference to the descriptor is dropped. + * shouldFreeDesc is similar to shouldFree: if it's true, then the + * tupleDescriptor is "owned" by the TupleTableSlot and should be + * freed when the slot's reference to the descriptor is dropped. * - * See executor.h for decls of functions defined in execTuples.c - * -jolly + * If buffer is not InvalidBuffer, then the slot is holding a pin + * on the indicated buffer page; drop the pin when we release the + * slot's reference to that buffer. (shouldFree should always be + * false in such a case, since presumably val is pointing at the + * buffer page.) * - * ---------------- + * The slot_getattr() routine allows extraction of attribute values from + * a TupleTableSlot's current tuple. It is equivalent to heap_getattr() + * except that it can optimize fetching of multiple values more efficiently. + * The cache_xxx fields of TupleTableSlot are support for slot_getattr(). */ typedef struct TupleTableSlot { - NodeTag type; - HeapTuple val; - bool ttc_shouldFree; - bool ttc_descIsNew; - bool ttc_shouldFreeDesc; - TupleDesc ttc_tupleDescriptor; - Buffer ttc_buffer; + NodeTag type; /* vestigial ... allows IsA tests */ + HeapTuple val; /* current tuple, or NULL if none */ + TupleDesc ttc_tupleDescriptor; /* tuple's descriptor */ + bool ttc_shouldFree; /* should pfree tuple? */ + bool ttc_shouldFreeDesc; /* should pfree descriptor? */ + Buffer ttc_buffer; /* tuple's buffer, or InvalidBuffer */ + MemoryContext ttc_mcxt; /* slot itself is in this context */ + Datum *cache_values; /* currently extracted values */ + int cache_natts; /* # of valid values in cache_values */ + bool cache_slow; /* saved state for slot_getattr */ + long cache_off; /* saved state for slot_getattr */ } TupleTableSlot; -/* ---------------- - * tuple table data structure - * ---------------- +/* + * Tuple table data structure: an array of TupleTableSlots. */ typedef struct TupleTableData { - int size; /* size of the table */ + int size; /* size of the table (number of slots) */ int next; /* next available slot number */ - TupleTableSlot *array; /* array of TupleTableSlot's */ -} TupleTableData; + TupleTableSlot array[1]; /* VARIABLE LENGTH ARRAY - must be last */ +} TupleTableData; /* VARIABLE LENGTH STRUCT */ typedef TupleTableData *TupleTable; + +/* in access/common/heaptuple.c */ +extern Datum slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull); + #endif /* TUPTABLE_H */