/* -------------------------------- * ExecCopySlotHeadTupleTo * Copy heapTuple to a preallocated buffer. Code adapted from ExecCopySlotTuple * * return the copied heaptule if there is enough space, or, if the memorycontext is * not null, which the function will alloc enough space from the context. One can * test if the tuple is alloced (ret == dest) * * return NULL and set *len to space need if there is not enough space and the mem context is null. * return NULL if heap tuple is not valid, and set *len = 0. See slot->tts_tuple case below. * ------------------------------- */ HeapTuple ExecCopySlotHeapTupleTo(TupleTableSlot *slot, MemoryContext pctxt, char* dest, unsigned int *len) { uint32 dumlen; HeapTuple tup = NULL; Assert(!TupIsNull(slot)); Assert(slot->tts_tupleDescriptor); if(!len) len = &dumlen; if (slot->PRIVATE_tts_heaptuple) { tup = heaptuple_copy_to(slot->PRIVATE_tts_heaptuple, (HeapTuple) dest, len); if(tup || !pctxt) return tup; tup = (HeapTuple) ctxt_alloc(pctxt, *len); tup = heaptuple_copy_to(slot->PRIVATE_tts_heaptuple, tup, len); Assert(tup); return tup; } slot_getallattrs(slot); tup = heaptuple_form_to(slot->tts_tupleDescriptor, slot_get_values(slot), slot_get_isnull(slot), (HeapTuple) dest, len); if(tup || !pctxt) return tup; tup = (HeapTuple) ctxt_alloc(pctxt, *len); tup = heaptuple_form_to(slot->tts_tupleDescriptor, slot_get_values(slot), slot_get_isnull(slot), tup, len); Assert(tup); return tup; }
MemTuple ExecCopySlotMemTupleTo(TupleTableSlot *slot, MemoryContext pctxt, char *dest, unsigned int *len) { uint32 dumlen; MemTuple mtup = NULL; Assert(!TupIsNull(slot)); Assert(slot->tts_mt_bind); if(!len) len = &dumlen; if (TupHasMemTuple(slot)) { mtup = memtuple_copy_to(slot->PRIVATE_tts_memtuple, slot->tts_mt_bind, (MemTuple) dest, len); if(mtup || !pctxt) return mtup; mtup = (MemTuple) ctxt_alloc(pctxt, *len); mtup = memtuple_copy_to(slot->PRIVATE_tts_memtuple, slot->tts_mt_bind, mtup, len); Assert(mtup); return mtup; } slot_getallattrs(slot); mtup = memtuple_form_to(slot->tts_mt_bind, slot_get_values(slot), slot_get_isnull(slot), (MemTuple) dest, len, false); if(mtup || !pctxt) return mtup; mtup = (MemTuple) ctxt_alloc(pctxt, *len); mtup = memtuple_form_to(slot->tts_mt_bind, slot_get_values(slot), slot_get_isnull(slot), mtup, len, false); Assert(mtup); return mtup; }
static void AOCSMoveTuple(TupleTableSlot *slot, AOCSInsertDesc insertDesc, ResultRelInfo *resultRelInfo, EState *estate) { AOTupleId *oldAoTupleId; AOTupleId newAoTupleId; Assert(resultRelInfo); Assert(slot); Assert(estate); oldAoTupleId = (AOTupleId *) slot_get_ctid(slot); /* Extract all the values of the tuple */ slot_getallattrs(slot); (void) aocs_insert_values(insertDesc, slot_get_values(slot), slot_get_isnull(slot), &newAoTupleId); /* insert index' tuples if needed */ if (resultRelInfo->ri_NumIndices > 0) { ExecInsertIndexTuples(slot, (ItemPointer) &newAoTupleId, estate); ResetPerTupleExprContext(estate); } elogif(Debug_appendonly_print_compaction, DEBUG5, "Compaction: Moved tuple (%d," INT64_FORMAT ") -> (%d," INT64_FORMAT ")", AOTupleIdGet_segmentFileNum(oldAoTupleId), AOTupleIdGet_rowNum(oldAoTupleId), AOTupleIdGet_segmentFileNum(&newAoTupleId), AOTupleIdGet_rowNum(&newAoTupleId)); }
/* -------------------------------- * ExecFetchSlotMinimalTuple * Fetch the slot's minimal physical tuple. * * If the slot contains a virtual tuple, we convert it to minimal * physical form. The slot retains ownership of the physical tuple. * Likewise, if it contains a regular tuple we convert to minimal form. * * As above, the result must be treated as read-only. * -------------------------------- */ MemTuple ExecFetchSlotMemTuple(TupleTableSlot *slot, bool inline_toast) { MemTuple newTuple; MemTuple oldTuple = NULL; uint32 tuplen; Assert(!TupIsNull(slot)); Assert(slot->tts_mt_bind); if(slot->PRIVATE_tts_memtuple) { if(!inline_toast || !memtuple_get_hasext(slot->PRIVATE_tts_memtuple, slot->tts_mt_bind)) return slot->PRIVATE_tts_memtuple; oldTuple = slot->PRIVATE_tts_mtup_buf; slot->PRIVATE_tts_mtup_buf = NULL; slot->PRIVATE_tts_mtup_buf_len = 0; } slot_getallattrs(slot); tuplen = slot->PRIVATE_tts_mtup_buf_len; newTuple = memtuple_form_to(slot->tts_mt_bind, slot_get_values(slot), slot_get_isnull(slot), (MemTuple) slot->PRIVATE_tts_mtup_buf, &tuplen, inline_toast); if(!newTuple) { if(slot->PRIVATE_tts_mtup_buf) pfree(slot->PRIVATE_tts_mtup_buf); slot->PRIVATE_tts_mtup_buf = MemoryContextAlloc(slot->tts_mcxt, tuplen); slot->PRIVATE_tts_mtup_buf_len = tuplen; newTuple = memtuple_form_to(slot->tts_mt_bind, slot_get_values(slot), slot_get_isnull(slot), (MemTuple) slot->PRIVATE_tts_mtup_buf, &tuplen, inline_toast); } Assert(newTuple); slot->PRIVATE_tts_memtuple = newTuple; if(oldTuple) pfree(oldTuple); return newTuple; }
Oid parquet_insert(ParquetInsertDesc parquetInsertDesc, TupleTableSlot *slot) { Oid oid; AOTupleId aotid; slot_getallattrs(slot); oid = parquet_insert_values(parquetInsertDesc, slot_get_values(slot), slot_get_isnull(slot), &aotid); slot_set_ctid(slot, (ItemPointer)&aotid); return oid; }
/* -------------------------------- * ExecFetchSlotTuple * Fetch the slot's regular physical tuple. * * If the slot contains a virtual tuple, we convert it to physical * form. The slot retains ownership of the physical tuple. * Likewise, if it contains a minimal tuple we convert to regular form. * * The difference between this and ExecMaterializeSlot() is that this * does not guarantee that the contained tuple is local storage. * Hence, the result must be treated as read-only. * -------------------------------- */ HeapTuple ExecFetchSlotHeapTuple(TupleTableSlot *slot) { uint32 tuplen; HeapTuple htup; /* * sanity checks */ Assert(!TupIsNull(slot)); /* * If we have a regular physical tuple then just return it. */ if(slot->PRIVATE_tts_heaptuple) return slot->PRIVATE_tts_heaptuple; slot_getallattrs(slot); Assert(TupHasVirtualTuple(slot)); Assert(slot->PRIVATE_tts_nvalid == slot->tts_tupleDescriptor->natts); tuplen = slot->PRIVATE_tts_htup_buf_len; htup = heaptuple_form_to(slot->tts_tupleDescriptor, slot_get_values(slot), slot_get_isnull(slot), slot->PRIVATE_tts_htup_buf, &tuplen); if(!htup) { if(slot->PRIVATE_tts_htup_buf) pfree(slot->PRIVATE_tts_htup_buf); slot->PRIVATE_tts_htup_buf = (HeapTuple) MemoryContextAlloc(slot->tts_mcxt, tuplen); slot->PRIVATE_tts_htup_buf_len = tuplen; htup = heaptuple_form_to(slot->tts_tupleDescriptor, slot_get_values(slot), slot_get_isnull(slot), slot->PRIVATE_tts_htup_buf, &tuplen); Assert(htup); } slot->PRIVATE_tts_heaptuple = htup; return htup; }
/* * Receive a tuple from the executor and store it in the tuplestore. * This is for the case where we have to detoast any toasted values. */ static void tstoreReceiveSlot_detoast(TupleTableSlot *slot, DestReceiver *self) { TStoreState *myState = (TStoreState *) self; TupleDesc typeinfo = slot->tts_tupleDescriptor; Form_pg_attribute *attrs = typeinfo->attrs; int natts = typeinfo->natts; int nfree; int i; MemoryContext oldcxt; /* Make sure the tuple is fully deconstructed */ slot_getallattrs(slot); /* * Fetch back any out-of-line datums. We build the new datums array in * myState->outvalues[] (but we can re-use the slot's isnull array). Also, * remember the fetched values to free afterwards. */ nfree = 0; for (i = 0; i < natts; i++) { Datum val = slot_get_values(slot)[i]; if (!attrs[i]->attisdropped && attrs[i]->attlen == -1 && !slot_get_isnull(slot)[i]) { if (VARATT_IS_EXTERNAL(DatumGetPointer(val))) { val = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(val))); myState->tofree[nfree++] = val; } } myState->outvalues[i] = val; } /* * Push the modified tuple into the tuplestore. */ oldcxt = MemoryContextSwitchTo(myState->cxt); tuplestore_putvalues(myState->tstore, typeinfo, myState->outvalues, slot_get_isnull(slot)); MemoryContextSwitchTo(oldcxt); /* And release any temporary detoasted values */ for (i = 0; i < nfree; i++) pfree(DatumGetPointer(myState->tofree[i])); }
/* -------------------------------- * ExecCopySlotMinimalTuple * Obtain a copy of a slot's minimal physical tuple. The copy is * palloc'd in the current memory context. * -------------------------------- */ MemTuple ExecCopySlotMemTuple(TupleTableSlot *slot) { /* * sanity checks */ Assert(!TupIsNull(slot)); Assert(slot->tts_mt_bind); /* * If we have a physical tuple then just copy it. */ if (slot->PRIVATE_tts_memtuple) return memtuple_copy_to(slot->PRIVATE_tts_memtuple, slot->tts_mt_bind, NULL, NULL); slot_getallattrs(slot); /* * Otherwise we need to build a tuple from the Datum array. */ return memtuple_form_to(slot->tts_mt_bind, slot_get_values(slot), slot_get_isnull(slot), NULL, 0, false); }
/* -------------------------------- * ExecCopySlotTuple * Obtain a copy of a slot's regular physical tuple. The copy is * palloc'd in the current memory context. * * This works even if the slot contains a virtual or minimal tuple; * however the "system columns" of the result will not be meaningful. * -------------------------------- */ HeapTuple ExecCopySlotHeapTuple(TupleTableSlot *slot) { /* * sanity checks */ Assert(!TupIsNull(slot)); if(slot->PRIVATE_tts_heaptuple) return heap_copytuple(slot->PRIVATE_tts_heaptuple); slot_getallattrs(slot); /* * Otherwise we need to build a tuple from the Datum array. */ return heap_form_tuple(slot->tts_tupleDescriptor, slot_get_values(slot), slot_get_isnull(slot)); }
/* ---------------------------------------------------------------- * ExecPartitionSelector(node) * * Compute and propagate partition table Oids that will be * used by Dynamic table scan. There are two ways of * executing PartitionSelector. * * 1. Constant partition elimination * Plan structure: * Sequence * |--PartitionSelector * |--DynamicTableScan * In this case, PartitionSelector evaluates constant partition * constraints to compute and propagate partition table Oids. * It only need to be called once. * * 2. Join partition elimination * Plan structure: * ...: * |--DynamicTableScan * |--... * |--PartitionSelector * |--... * In this case, PartitionSelector is in the same slice as * DynamicTableScan, DynamicIndexScan or DynamicBitmapHeapScan. * It is executed for each tuple coming from its child node. * It evaluates partition constraints with the input tuple and * propagate matched partition table Oids. * * * Instead of a Dynamic Table Scan, there can be other nodes that use * a PartSelected qual to filter rows, based on which partitions are * selected. Currently, ORCA uses Dynamic Table Scans, while plans * produced by the non-ORCA planner use gating Result nodes with * PartSelected quals, to exclude unwanted partitions. * * ---------------------------------------------------------------- */ TupleTableSlot * ExecPartitionSelector(PartitionSelectorState *node) { PartitionSelector *ps = (PartitionSelector *) node->ps.plan; EState *estate = node->ps.state; ExprContext *econtext = node->ps.ps_ExprContext; TupleTableSlot *inputSlot = NULL; TupleTableSlot *candidateOutputSlot = NULL; if (ps->staticSelection) { /* propagate the part oids obtained via static partition selection */ partition_propagation(estate, ps->staticPartOids, ps->staticScanIds, ps->selectorId); return NULL; } /* Retrieve PartitionNode and access method from root table. * We cannot do it during node initialization as * DynamicTableScanInfo is not properly initialized yet. */ if (NULL == node->rootPartitionNode) { Assert(NULL != estate->dynamicTableScanInfo); getPartitionNodeAndAccessMethod ( ps->relid, estate->dynamicTableScanInfo->partsMetadata, estate->es_query_cxt, &node->rootPartitionNode, &node->accessMethods ); } if (NULL != outerPlanState(node)) { /* Join partition elimination */ /* get tuple from outer children */ PlanState *outerPlan = outerPlanState(node); Assert(outerPlan); inputSlot = ExecProcNode(outerPlan); if (TupIsNull(inputSlot)) { /* no more tuples from outerPlan */ /* * Make sure we have an entry for this scan id in * dynamicTableScanInfo. Normally, this would've been done the * first time a partition is selected, but we must ensure that * there is an entry even if no partitions were selected. * (The traditional Postgres planner uses this method.) */ if (ps->partTabTargetlist) InsertPidIntoDynamicTableScanInfo(estate, ps->scanId, InvalidOid, ps->selectorId); else LogPartitionSelection(estate, ps->selectorId); return NULL; } } /* partition elimination with the given input tuple */ ResetExprContext(econtext); node->ps.ps_OuterTupleSlot = inputSlot; econtext->ecxt_outertuple = inputSlot; econtext->ecxt_scantuple = inputSlot; if (NULL != inputSlot) { candidateOutputSlot = ExecProject(node->ps.ps_ProjInfo, NULL); } /* * If we have a partitioning projection, project the input tuple * into a tuple that looks like tuples from the partitioned table, and use * selectPartitionMulti() to select the partitions. (The traditional * Postgres planner uses this method.) */ if (ps->partTabTargetlist) { TupleTableSlot *slot; List *oids; ListCell *lc; slot = ExecProject(node->partTabProj, NULL); slot_getallattrs(slot); oids = selectPartitionMulti(node->rootPartitionNode, slot_get_values(slot), slot_get_isnull(slot), slot->tts_tupleDescriptor, node->accessMethods); foreach (lc, oids) { InsertPidIntoDynamicTableScanInfo(estate, ps->scanId, lfirst_oid(lc), ps->selectorId); }
/* * Use the supplied ResultRelInfo to create an appropriately restructured * version of the tuple in the supplied slot, if necessary. * * slot -- slot containing the input tuple * resultRelInfo -- info pertaining to the target part of an insert * * If no restructuring is required, the result is the argument slot, else * it is the slot from the argument result info updated to hold the * restructured tuple. */ TupleTableSlot * reconstructMatchingTupleSlot(TupleTableSlot *slot, ResultRelInfo *resultRelInfo) { int natts; Datum *values; bool *isnull; AttrMap *map; TupleTableSlot *partslot; Datum *partvalues; bool *partisnull; map = resultRelInfo->ri_partInsertMap; TupleDesc inputTupDesc = slot->tts_tupleDescriptor; TupleDesc resultTupDesc = resultRelInfo->ri_RelationDesc->rd_att; bool tupleDescMatch = (resultRelInfo->tupdesc_match == 1); if (resultRelInfo->tupdesc_match == 0) { tupleDescMatch = equalTupleDescs(inputTupDesc, resultTupDesc, false); if (tupleDescMatch) { resultRelInfo->tupdesc_match = 1; } else { resultRelInfo->tupdesc_match = -1; } } /* No map and matching tuple descriptor means no restructuring needed. */ if (map == NULL && tupleDescMatch) return slot; /* Put the given tuple into attribute arrays. */ natts = slot->tts_tupleDescriptor->natts; slot_getallattrs(slot); values = slot_get_values(slot); isnull = slot_get_isnull(slot); /* * Get the target slot ready. If this is a child partition table, * set target slot to ri_partSlot. Otherwise, use ri_resultSlot. */ if (map != NULL) { Assert(resultRelInfo->ri_partSlot != NULL); partslot = resultRelInfo->ri_partSlot; } else { if (resultRelInfo->ri_resultSlot == NULL) { resultRelInfo->ri_resultSlot = MakeSingleTupleTableSlot(resultTupDesc); } partslot = resultRelInfo->ri_resultSlot; } partslot = ExecStoreAllNullTuple(partslot); partvalues = slot_get_values(partslot); partisnull = slot_get_isnull(partslot); /* Restructure the input tuple. Non-zero map entries are attribute * numbers in the target tuple, however, not every attribute * number of the input tuple need be present. In particular, * attribute numbers corresponding to dropped attributes will be * missing. */ reconstructTupleValues(map, values, isnull, natts, partvalues, partisnull, partslot->tts_tupleDescriptor->natts); partslot = ExecStoreVirtualTuple(partslot); return partslot; }
/* ---------------------------------------------------------------- * ExecUpdate * * note: we can't run UPDATE queries with transactions * off because UPDATEs are actually INSERTs and our * scan will mistakenly loop forever, updating the tuple * it just inserted.. This should be fixed but until it * is, we don't want to get stuck in an infinite loop * which corrupts your database.. * ---------------------------------------------------------------- */ void ExecUpdate(TupleTableSlot *slot, ItemPointer tupleid, TupleTableSlot *planSlot, DestReceiver *dest, EState *estate) { HeapTuple tuple; ResultRelInfo *resultRelInfo; Relation resultRelationDesc; HTSU_Result result; ItemPointerData update_ctid; TransactionId update_xmax; /* * abort the operation if not running transactions */ if (IsBootstrapProcessingMode()) elog(ERROR, "cannot UPDATE during bootstrap"); /* * get the heap tuple out of the tuple table slot, making sure we have a * writable copy */ tuple = ExecFetchSlotHeapTuple(slot); /* * get information on the (current) result relation */ resultRelInfo = estate->es_result_relation_info; resultRelationDesc = resultRelInfo->ri_RelationDesc; /* see if this update would move the tuple to a different partition */ if (estate->es_result_partitions) { AttrNumber max_attr; Datum *values; bool *nulls; Oid targetid; Assert(estate->es_partition_state != NULL && estate->es_partition_state->accessMethods != NULL); if (!estate->es_partition_state->accessMethods->part_cxt) estate->es_partition_state->accessMethods->part_cxt = GetPerTupleExprContext(estate)->ecxt_per_tuple_memory; Assert(PointerIsValid(estate->es_result_partitions)); max_attr = estate->es_partition_state->max_partition_attr; slot_getsomeattrs(slot, max_attr); values = slot_get_values(slot); nulls = slot_get_isnull(slot); targetid = selectPartition(estate->es_result_partitions, values, nulls, slot->tts_tupleDescriptor, estate->es_partition_state->accessMethods); if (!OidIsValid(targetid)) ereport(ERROR, (errcode(ERRCODE_NO_PARTITION_FOR_PARTITIONING_KEY), errmsg("no partition for partitioning key"))); if (RelationGetRelid(resultRelationDesc) != targetid) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("moving tuple from partition \"%s\" to " "partition \"%s\" not supported", get_rel_name(RelationGetRelid(resultRelationDesc)), get_rel_name(targetid)), errOmitLocation(true))); } } /* BEFORE ROW UPDATE Triggers */ if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_UPDATE] > 0) { HeapTuple newtuple; newtuple = ExecBRUpdateTriggers(estate, resultRelInfo, tupleid, tuple, estate->es_snapshot->curcid); if (newtuple == NULL) /* "do nothing" */ return; if (newtuple != tuple) /* modified by Trigger(s) */ { /* * Put the modified tuple into a slot for convenience of routines * below. We assume the tuple was allocated in per-tuple memory * context, and therefore will go away by itself. The tuple table * slot should not try to clear it. */ TupleTableSlot *newslot = estate->es_trig_tuple_slot; if (newslot->tts_tupleDescriptor != slot->tts_tupleDescriptor) ExecSetSlotDescriptor(newslot, slot->tts_tupleDescriptor); ExecStoreGenericTuple(newtuple, newslot, false); newslot->tts_tableOid = slot->tts_tableOid; /* for constraints */ slot = newslot; tuple = newtuple; } } /* * Check the constraints of the tuple * * If we generate a new candidate tuple after EvalPlanQual testing, we * must loop back here and recheck constraints. (We don't need to redo * triggers, however. If there are any BEFORE triggers then trigger.c * will have done heap_lock_tuple to lock the correct tuple, so there's no * need to do them again.) */ lreplace:; if (resultRelationDesc->rd_att->constr) ExecConstraints(resultRelInfo, slot, estate); if (!GpPersistent_IsPersistentRelation(resultRelationDesc->rd_id)) { /* * Normal UPDATE path. */ /* * replace the heap tuple * * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that * the row to be updated is visible to that snapshot, and throw a can't- * serialize error if not. This is a special-case behavior needed for * referential integrity updates in serializable transactions. */ result = heap_update(resultRelationDesc, tupleid, tuple, &update_ctid, &update_xmax, estate->es_snapshot->curcid, estate->es_crosscheck_snapshot, true /* wait for commit */ ); switch (result) { case HeapTupleSelfUpdated: /* already deleted by self; nothing to do */ return; case HeapTupleMayBeUpdated: break; case HeapTupleUpdated: if (IsXactIsoLevelSerializable) ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("could not serialize access due to concurrent update"))); else if (!ItemPointerEquals(tupleid, &update_ctid)) { TupleTableSlot *epqslot; epqslot = EvalPlanQual(estate, resultRelInfo->ri_RangeTableIndex, &update_ctid, update_xmax, estate->es_snapshot->curcid); if (!TupIsNull(epqslot)) { *tupleid = update_ctid; slot = ExecFilterJunk(estate->es_junkFilter, epqslot); tuple = ExecFetchSlotHeapTuple(slot); goto lreplace; } } /* tuple already deleted; nothing to do */ return; default: elog(ERROR, "unrecognized heap_update status: %u", result); return; } } else { HeapTuple persistentTuple; /* * Persistent metadata path. */ persistentTuple = heap_copytuple(tuple); persistentTuple->t_self = *tupleid; frozen_heap_inplace_update(resultRelationDesc, persistentTuple); heap_freetuple(persistentTuple); } IncrReplaced(); (estate->es_processed)++; /* * 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 * because UPDATEs are actually DELETEs and INSERTs, and index tuple * deletion is done later by VACUUM (see notes in ExecDelete). All we do * here is insert new index tuples. -cim 9/27/89 */ /* * insert index entries for tuple * * Note: heap_update returns the tid (location) of the new tuple in the * t_self field. */ if (resultRelInfo->ri_NumIndices > 0) ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false); /* AFTER ROW UPDATE Triggers */ ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple); }
/* ---------------------------------------------------------------- * ValuesNext * * This is a workhorse for ExecValuesScan * ---------------------------------------------------------------- */ static TupleTableSlot * ValuesNext(ValuesScanState *node) { TupleTableSlot *slot; EState *estate; ExprContext *econtext; ScanDirection direction; List *exprlist; /* * get information from the estate and scan state */ estate = node->ss.ps.state; direction = estate->es_direction; slot = node->ss.ss_ScanTupleSlot; econtext = node->rowcontext; /* * Get the next tuple. Return NULL if no more tuples. */ if (ScanDirectionIsForward(direction)) { if (node->curr_idx < node->array_len) node->curr_idx++; if (node->curr_idx < node->array_len) exprlist = node->exprlists[node->curr_idx]; else exprlist = NIL; } else { if (node->curr_idx >= 0) node->curr_idx--; if (node->curr_idx >= 0) exprlist = node->exprlists[node->curr_idx]; else exprlist = NIL; } /* * Always clear the result slot; this is appropriate if we are at the end * of the data, and if we're not, we still need it as the first step of * the store-virtual-tuple protocol. It seems wise to clear the slot * before we reset the context it might have pointers into. */ ExecClearTuple(slot); if (exprlist) { MemoryContext oldContext; List *exprstatelist; Datum *values; bool *isnull; ListCell *lc; int resind; /* * Get rid of any prior cycle's leftovers. We use ReScanExprContext * not just ResetExprContext because we want any registered shutdown * callbacks to be called. */ ReScanExprContext(econtext); /* * Build the expression eval state in the econtext's per-tuple memory. * This is a tad unusual, but we want to delete the eval state again * when we move to the next row, to avoid growth of memory * requirements over a long values list. */ oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); /* * Pass NULL, not my plan node, because we don't want anything in this * transient state linking into permanent state. The only possibility * is a SubPlan, and there shouldn't be any (any subselects in the * VALUES list should be InitPlans). */ exprstatelist = (List *) ExecInitExpr((Expr *) exprlist, NULL); /* parser should have checked all sublists are the same length */ Assert(list_length(exprstatelist) == slot->tts_tupleDescriptor->natts); /* * Compute the expressions and build a virtual result tuple. We * already did ExecClearTuple(slot). */ ExecClearTuple(slot); values = slot_get_values(slot); isnull = slot_get_isnull(slot); resind = 0; foreach(lc, exprstatelist) { ExprState *estate = (ExprState *) lfirst(lc); values[resind] = ExecEvalExpr(estate, econtext, &isnull[resind], NULL); resind++; } MemoryContextSwitchTo(oldContext); /* * And return the virtual tuple. */ ExecStoreVirtualTuple(slot); /* CDB: Label each row with a synthetic ctid for subquery dedup. */ if (node->cdb_want_ctid) { HeapTuple tuple = ExecFetchSlotHeapTuple(slot); ItemPointerSet(&tuple->t_self, node->curr_idx >> 16, (OffsetNumber)node->curr_idx); }
/* * Check if the tuple being updated will stay in the same part and throw ERROR * if not. This check is especially necessary for default partition that has * no constraint on it. partslot is the tuple being updated, and * resultRelInfo is the target relation of this update. Call this only * estate has valid es_result_partitions. */ static void checkPartitionUpdate(EState *estate, TupleTableSlot *partslot, ResultRelInfo *resultRelInfo) { Relation resultRelationDesc = resultRelInfo->ri_RelationDesc; AttrNumber max_attr; Datum *values = NULL; bool *nulls = NULL; TupleDesc tupdesc = NULL; Oid parentRelid; Oid targetid; Assert(estate->es_partition_state != NULL && estate->es_partition_state->accessMethods != NULL); if (!estate->es_partition_state->accessMethods->part_cxt) estate->es_partition_state->accessMethods->part_cxt = GetPerTupleExprContext(estate)->ecxt_per_tuple_memory; Assert(PointerIsValid(estate->es_result_partitions)); /* * As opposed to INSERT, resultRelation here is the same child part * as scan origin. However, the partition selection is done with the * parent partition's attribute numbers, so if this result (child) part * has physically-different attribute numbers due to dropped columns, * we should map the child attribute numbers to the parent's attribute * numbers to perform the partition selection. * EState doesn't have the parent relation information at the moment, * so we have to do a hard job here by opening it and compare the * tuple descriptors. If we find we need to map attribute numbers, * max_partition_attr could also be bogus for this child part, * so we end up materializing the whole columns using slot_getallattrs(). * The purpose of this code is just to prevent the tuple from * incorrectly staying in default partition that has no constraint * (parts with constraint will throw an error if the tuple is changing * partition keys to out of part value anyway.) It's a bit overkill * to do this complicated logic just for this purpose, which is necessary * with our current partitioning design, but I hope some day we can * change this so that we disallow phyisically-different tuple descriptor * across partition. */ parentRelid = estate->es_result_partitions->part->parrelid; /* * I don't believe this is the case currently, but we check the parent relid * in case the updating partition has changed since the last time we opened it. */ if (resultRelInfo->ri_PartitionParent && parentRelid != RelationGetRelid(resultRelInfo->ri_PartitionParent)) { resultRelInfo->ri_PartCheckTupDescMatch = 0; if (resultRelInfo->ri_PartCheckMap != NULL) pfree(resultRelInfo->ri_PartCheckMap); if (resultRelInfo->ri_PartitionParent) relation_close(resultRelInfo->ri_PartitionParent, AccessShareLock); } /* * Check this at the first pass only to avoid repeated catalog access. */ if (resultRelInfo->ri_PartCheckTupDescMatch == 0 && parentRelid != RelationGetRelid(resultRelInfo->ri_RelationDesc)) { Relation parentRel; TupleDesc resultTupdesc, parentTupdesc; /* * We are on a child part, let's see the tuple descriptor looks like * the parent's one. Probably this won't cause deadlock because * DML should have opened the parent table with appropriate lock. */ parentRel = relation_open(parentRelid, AccessShareLock); resultTupdesc = RelationGetDescr(resultRelationDesc); parentTupdesc = RelationGetDescr(parentRel); if (!equalTupleDescs(resultTupdesc, parentTupdesc, false)) { AttrMap *map; MemoryContext oldcontext; /* Tuple looks different. Construct attribute mapping. */ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); map_part_attrs(resultRelationDesc, parentRel, &map, true); MemoryContextSwitchTo(oldcontext); /* And save it for later use. */ resultRelInfo->ri_PartCheckMap = map; resultRelInfo->ri_PartCheckTupDescMatch = -1; } else resultRelInfo->ri_PartCheckTupDescMatch = 1; resultRelInfo->ri_PartitionParent = parentRel; /* parentRel will be closed as part of ResultRelInfo cleanup */ } if (resultRelInfo->ri_PartCheckMap != NULL) { Datum *parent_values; bool *parent_nulls; Relation parentRel = resultRelInfo->ri_PartitionParent; TupleDesc parentTupdesc; AttrMap *map; Assert(parentRel != NULL); parentTupdesc = RelationGetDescr(parentRel); /* * We need to map the attribute numbers to parent's one, to * select the would-be destination relation, since all partition * rules are based on the parent relation's tuple descriptor. * max_partition_attr can be bogus as well, so don't use it. */ slot_getallattrs(partslot); values = slot_get_values(partslot); nulls = slot_get_isnull(partslot); parent_values = palloc(parentTupdesc->natts * sizeof(Datum)); parent_nulls = palloc0(parentTupdesc->natts * sizeof(bool)); map = resultRelInfo->ri_PartCheckMap; reconstructTupleValues(map, values, nulls, partslot->tts_tupleDescriptor->natts, parent_values, parent_nulls, parentTupdesc->natts); /* Now we have values/nulls in parent's view. */ values = parent_values; nulls = parent_nulls; tupdesc = RelationGetDescr(parentRel); } else { /* * map == NULL means we can just fetch values/nulls from the * current slot. */ Assert(nulls == NULL && tupdesc == NULL); max_attr = estate->es_partition_state->max_partition_attr; slot_getsomeattrs(partslot, max_attr); /* values/nulls pointing to partslot's array. */ values = slot_get_values(partslot); nulls = slot_get_isnull(partslot); tupdesc = partslot->tts_tupleDescriptor; } /* And select the destination relation that this tuple would go to. */ targetid = selectPartition(estate->es_result_partitions, values, nulls, tupdesc, estate->es_partition_state->accessMethods); /* Free up if we allocated mapped attributes. */ if (values != slot_get_values(partslot)) { Assert(nulls != slot_get_isnull(partslot)); pfree(values); pfree(nulls); } if (!OidIsValid(targetid)) ereport(ERROR, (errcode(ERRCODE_NO_PARTITION_FOR_PARTITIONING_KEY), errmsg("no partition for partitioning key"))); if (RelationGetRelid(resultRelationDesc) != targetid) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("moving tuple from partition \"%s\" to " "partition \"%s\" not supported", get_rel_name(RelationGetRelid(resultRelationDesc)), get_rel_name(targetid)))); }