# # # patch "ChangeLog" # from [f2398cc87118e5bb9531dcf39044817c64c2cb57] # to [ed657d244d792cf6b0d6f12628214f813ef9bec2] # # patch "tests/test_a_merge_8/correct" # from [11d1b0fc244c106e4b1e615cf8a9d47996cb1752] # to [73bfad77615751a3f83839df482b5bce395784e7] # # patch "tests/test_a_merge_8/left" # from [2218e9827eff22bde55cac4e48749c33d8231876] # to [4dd9089ffd37bbaf4a678815e4c1a2ffd15de547] # # patch "tests/test_a_merge_8/parent" # from [635502b5dd25e4891825e8f584e1f423c8432223] # to [f629245675b8b8758dc7e7ea2b791cbc14c49de0] # # patch "tests/test_a_merge_8/right" # from [e2c3c1e5c4db85c53581122fad7cf83d835635e6] # to [dd8a37d2beb981759342a31933ba131ab58f6dd3] # ============================================================ --- ChangeLog f2398cc87118e5bb9531dcf39044817c64c2cb57 +++ ChangeLog ed657d244d792cf6b0d6f12628214f813ef9bec2 @@ -1,3 +1,8 @@ +2007-02-09 Markus Schiltknecht + + * tests/test_a_merge_8/{parent,left,right,correct}: simplified + the test case even more. That's about as small as it gets. + 2007-02-09 Lapo Luchini * netio.hh: Removed the word 'uleb128' from printed text. ============================================================ --- tests/test_a_merge_8/correct 11d1b0fc244c106e4b1e615cf8a9d47996cb1752 +++ tests/test_a_merge_8/correct 73bfad77615751a3f83839df482b5bce395784e7 @@ -1,1128 +1,19 @@ -/* ---------------------------------------------------------------- - * InitPlan - * - * Initializes the query plan: open files, allocate storage - * and start up the rule manager - * ---------------------------------------------------------------- - */ -static void -InitPlan(QueryDesc *queryDesc, int eflags) -{ - CmdType operation = queryDesc->operation; - Query *parseTree = queryDesc->parsetree; - Plan *plan = queryDesc->plantree; - EState *estate = queryDesc->estate; - PlanState *planstate; - List *rangeTable; - TupleDesc tupType; - ListCell *l; +// AN INITIAL COMMON LINE +// A LINE FROM THE LEFT +// ANOTHER COMMON LINE - /* - * Do permissions checks. It's sufficient to examine the query's top - * rangetable here --- subplan RTEs will be checked during - * ExecInitSubPlan(). - */ - ExecCheckRTPerms(parseTree->rtable); +// SOME STUFF FROM THE RIGHT /* - * get information from query descriptor - */ - rangeTable = parseTree->rtable; - - /* - * initialize the node's execution state - */ - estate->es_range_table = rangeTable; - - /* - * if there is a result relation, initialize result relation stuff - */ - if (parseTree->resultRelation) - { - List *resultRelations = parseTree->resultRelations; - int numResultRelations; - ResultRelInfo *resultRelInfos; - - if (resultRelations != NIL) - { - /* - * Multiple result relations (due to inheritance) - * parseTree->resultRelations identifies them all - */ - ResultRelInfo *resultRelInfo; - - numResultRelations = list_length(resultRelations); - resultRelInfos = (ResultRelInfo *) - palloc(numResultRelations * sizeof(ResultRelInfo)); - resultRelInfo = resultRelInfos; - foreach(l, resultRelations) - { - initResultRelInfo(resultRelInfo, - lfirst_int(l), - rangeTable, - operation, - estate->es_instrument); - resultRelInfo++; - } - } - else - { - /* - * Single result relation identified by parseTree->resultRelation - */ - numResultRelations = 1; - resultRelInfos = (ResultRelInfo *) palloc(sizeof(ResultRelInfo)); - initResultRelInfo(resultRelInfos, - parseTree->resultRelation, - rangeTable, - operation, - estate->es_instrument); - } - - estate->es_result_relations = resultRelInfos; - estate->es_num_result_relations = numResultRelations; - /* Initialize to first or only result rel */ - estate->es_result_relation_info = resultRelInfos; - } - else - { - /* - * if no result relation, then set state appropriately - */ - estate->es_result_relations = NULL; - estate->es_num_result_relations = 0; - estate->es_result_relation_info = NULL; - } - - /* - * Detect whether we're doing SELECT INTO. If so, set the es_into_oids - * flag appropriately so that the plan tree will be initialized with the - * correct tuple descriptors. (Other SELECT INTO stuff comes later.) - */ - estate->es_select_into = false; - if (operation == CMD_SELECT && parseTree->into != NULL) - { - estate->es_select_into = true; - estate->es_into_oids = interpretOidsOption(parseTree->intoOptions); - } - - /* - * Have to lock relations selected FOR UPDATE/FOR SHARE before we - * initialize the plan tree, else we'd be doing a lock upgrade. - * While we are at it, build the ExecRowMark list. - */ - estate->es_rowMarks = NIL; - foreach(l, parseTree->rowMarks) - { - RowMarkClause *rc = (RowMarkClause *) lfirst(l); - Oid relid = getrelid(rc->rti, rangeTable); - Relation relation; - ExecRowMark *erm; - - relation = heap_open(relid, RowShareLock); - erm = (ExecRowMark *) palloc(sizeof(ExecRowMark)); - erm->relation = relation; - erm->rti = rc->rti; - erm->forUpdate = rc->forUpdate; - erm->noWait = rc->noWait; - /* We'll set up ctidAttno below */ - erm->ctidAttNo = InvalidAttrNumber; - estate->es_rowMarks = lappend(estate->es_rowMarks, erm); - } - - /* - * initialize the executor "tuple" table. We need slots for all the plan - * nodes, plus possibly output slots for the junkfilter(s). At this point - * we aren't sure if we need junkfilters, so just add slots for them - * unconditionally. Also, if it's not a SELECT, set up a slot for use for - * trigger output tuples. - */ - { - int nSlots = ExecCountSlotsNode(plan); - - if (parseTree->resultRelations != NIL) - nSlots += list_length(parseTree->resultRelations); - else - nSlots += 1; - if (operation != CMD_SELECT) - nSlots++; /* for es_trig_tuple_slot */ - if (parseTree->returningLists) - nSlots++; /* for RETURNING projection */ - - estate->es_tupleTable = ExecCreateTupleTable(nSlots); - - if (operation != CMD_SELECT) - estate->es_trig_tuple_slot = - ExecAllocTableSlot(estate->es_tupleTable); - } - - /* mark EvalPlanQual not active */ - estate->es_topPlan = plan; - estate->es_evalPlanQual = NULL; - estate->es_evTupleNull = NULL; - estate->es_evTuple = NULL; - estate->es_useEvalPlan = false; - - /* - * initialize the private state information for all the nodes in the query - * tree. This opens files, allocates storage and leaves us ready to start - * processing tuples. - */ - planstate = ExecInitNode(plan, estate, eflags); - - /* - * Get the tuple descriptor describing the type of tuples to return. (this - * is especially important if we are creating a relation with "SELECT - * INTO") - */ - tupType = ExecGetResultType(planstate); - - /* - * Initialize the junk filter if needed. SELECT and INSERT queries need a - * filter if there are any junk attrs in the tlist. INSERT and SELECT - * INTO also need a filter if the plan may return raw disk tuples (else - * heap_insert will be scribbling on the source relation!). UPDATE and - * DELETE always need a filter, since there's always a junk 'ctid' - * attribute present --- no need to look first. - */ - { - bool junk_filter_needed = false; - ListCell *tlist; - - switch (operation) - { - case CMD_SELECT: - case CMD_INSERT: - foreach(tlist, plan->targetlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tlist); - - if (tle->resjunk) - { - junk_filter_needed = true; - break; - } - } - if (!junk_filter_needed && - (operation == CMD_INSERT || estate->es_select_into) && - ExecMayReturnRawTuples(planstate)) - junk_filter_needed = true; - break; - case CMD_UPDATE: - case CMD_DELETE: - junk_filter_needed = true; - break; - default: - break; - } - - if (junk_filter_needed) - { - /* - * If there are multiple result relations, each one needs its own - * junk filter. Note this is only possible for UPDATE/DELETE, so - * we can't be fooled by some needing a filter and some not. - */ - if (parseTree->resultRelations != NIL) - { - PlanState **appendplans; - int as_nplans; - ResultRelInfo *resultRelInfo; - int i; - - /* Top plan had better be an Append here. */ - Assert(IsA(plan, Append)); - Assert(((Append *) plan)->isTarget); - Assert(IsA(planstate, AppendState)); - appendplans = ((AppendState *) planstate)->appendplans; - as_nplans = ((AppendState *) planstate)->as_nplans; - Assert(as_nplans == estate->es_num_result_relations); - resultRelInfo = estate->es_result_relations; - for (i = 0; i < as_nplans; i++) - { - PlanState *subplan = appendplans[i]; - JunkFilter *j; - - j = ExecInitJunkFilter(subplan->plan->targetlist, - resultRelInfo->ri_RelationDesc->rd_att->tdhasoid, - ExecAllocTableSlot(estate->es_tupleTable)); - /* - * Since it must be UPDATE/DELETE, there had better be - * a "ctid" junk attribute in the tlist ... but ctid could - * be at a different resno for each result relation. - * We look up the ctid resnos now and save them in the - * junkfilters. - */ - j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); - if (!AttributeNumberIsValid(j->jf_junkAttNo)) - elog(ERROR, "could not find junk ctid column"); - resultRelInfo->ri_junkFilter = j; - resultRelInfo++; - } - - /* - * Set active junkfilter too; at this point ExecInitAppend has - * already selected an active result relation... - */ - estate->es_junkFilter = - estate->es_result_relation_info->ri_junkFilter; - } - else - { - /* Normal case with just one JunkFilter */ - JunkFilter *j; - - j = ExecInitJunkFilter(planstate->plan->targetlist, - tupType->tdhasoid, - ExecAllocTableSlot(estate->es_tupleTable)); - estate->es_junkFilter = j; - if (estate->es_result_relation_info) - estate->es_result_relation_info->ri_junkFilter = j; - - if (operation == CMD_SELECT) - { - /* For SELECT, want to return the cleaned tuple type */ - tupType = j->jf_cleanTupType; - /* For SELECT FOR UPDATE/SHARE, find the ctid attrs now */ - foreach(l, estate->es_rowMarks) - { - ExecRowMark *erm = (ExecRowMark *) lfirst(l); - char resname[32]; - - snprintf(resname, sizeof(resname), "ctid%u", erm->rti); - erm->ctidAttNo = ExecFindJunkAttribute(j, resname); - if (!AttributeNumberIsValid(erm->ctidAttNo)) - elog(ERROR, "could not find junk \"%s\" column", - resname); - } - } - else if (operation == CMD_UPDATE || operation == CMD_DELETE) - { - /* For UPDATE/DELETE, find the ctid junk attr now */ - j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); - if (!AttributeNumberIsValid(j->jf_junkAttNo)) - elog(ERROR, "could not find junk ctid column"); - } - } - } - else - estate->es_junkFilter = NULL; - } - - /* - * Initialize RETURNING projections if needed. - */ - if (parseTree->returningLists) - { - TupleTableSlot *slot; - ExprContext *econtext; - ResultRelInfo *resultRelInfo; - - /* - * We set QueryDesc.tupDesc to be the RETURNING rowtype in this case. - * We assume all the sublists will generate the same output tupdesc. - */ - tupType = ExecTypeFromTL((List *) linitial(parseTree->returningLists), - false); - - /* Set up a slot for the output of the RETURNING projection(s) */ - slot = ExecAllocTableSlot(estate->es_tupleTable); - ExecSetSlotDescriptor(slot, tupType); - /* Need an econtext too */ - econtext = CreateExprContext(estate); - - /* - * Build a projection for each result rel. Note that any SubPlans in - * the RETURNING lists get attached to the topmost plan node. - */ - Assert(list_length(parseTree->returningLists) == estate->es_num_result_relations); - resultRelInfo = estate->es_result_relations; - foreach(l, parseTree->returningLists) - { - List *rlist = (List *) lfirst(l); - List *rliststate; - - rliststate = (List *) ExecInitExpr((Expr *) rlist, planstate); - resultRelInfo->ri_projectReturning = - ExecBuildProjectionInfo(rliststate, econtext, slot, - resultRelInfo->ri_RelationDesc->rd_att); - resultRelInfo++; - } - - /* - * Because we already ran ExecInitNode() for the top plan node, any - * subplans we just attached to it won't have been initialized; so we - * have to do it here. (Ugly, but the alternatives seem worse.) - */ - foreach(l, planstate->subPlan) - { - SubPlanState *sstate = (SubPlanState *) lfirst(l); - - Assert(IsA(sstate, SubPlanState)); - if (sstate->planstate == NULL) /* already inited? */ - ExecInitSubPlan(sstate, estate, eflags); - } - } - - queryDesc->tupDesc = tupType; - queryDesc->planstate = planstate; - - /* - * If doing SELECT INTO, initialize the "into" relation. We must wait - * till now so we have the "clean" result tuple type to create the new - * table from. - * - * If EXPLAIN, skip creating the "into" relation. - */ - if (estate->es_select_into && !(eflags & EXEC_FLAG_EXPLAIN_ONLY)) - OpenIntoRel(queryDesc); -} - -/* - * Initialize ResultRelInfo data for one result relation - */ -static void -initResultRelInfo(ResultRelInfo *resultRelInfo, - Index resultRelationIndex, - List *rangeTable, - CmdType operation, - bool doInstrument) -{ - Oid resultRelationOid; - Relation resultRelationDesc; - - resultRelationOid = getrelid(resultRelationIndex, rangeTable); - resultRelationDesc = heap_open(resultRelationOid, RowExclusiveLock); - - switch (resultRelationDesc->rd_rel->relkind) - { - case RELKIND_SEQUENCE: - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change sequence \"%s\"", - RelationGetRelationName(resultRelationDesc)))); - break; - case RELKIND_TOASTVALUE: - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change TOAST relation \"%s\"", - RelationGetRelationName(resultRelationDesc)))); - break; - case RELKIND_VIEW: - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change view \"%s\"", - RelationGetRelationName(resultRelationDesc)))); - break; - } - - MemSet(resultRelInfo, 0, sizeof(ResultRelInfo)); - resultRelInfo->type = T_ResultRelInfo; - resultRelInfo->ri_RangeTableIndex = resultRelationIndex; - resultRelInfo->ri_RelationDesc = resultRelationDesc; - resultRelInfo->ri_NumIndices = 0; - resultRelInfo->ri_IndexRelationDescs = NULL; - resultRelInfo->ri_IndexRelationInfo = NULL; - /* make a copy so as not to depend on relcache info not changing... */ - resultRelInfo->ri_TrigDesc = CopyTriggerDesc(resultRelationDesc->trigdesc); - if (resultRelInfo->ri_TrigDesc) - { - int n = resultRelInfo->ri_TrigDesc->numtriggers; - - resultRelInfo->ri_TrigFunctions = (FmgrInfo *) - palloc0(n * sizeof(FmgrInfo)); - if (doInstrument) - resultRelInfo->ri_TrigInstrument = InstrAlloc(n); - else - resultRelInfo->ri_TrigInstrument = NULL; - } - else - { - resultRelInfo->ri_TrigFunctions = NULL; - resultRelInfo->ri_TrigInstrument = NULL; - } - resultRelInfo->ri_ConstraintExprs = NULL; - resultRelInfo->ri_junkFilter = NULL; - resultRelInfo->ri_projectReturning = NULL; - - /* - * If there are indices on the result relation, open them and save - * descriptors in the result relation info, so that we can add new index - * entries for the tuples we add/update. We need not do this for a - * DELETE, however, since deletion doesn't affect indexes. - */ - if (resultRelationDesc->rd_rel->relhasindex && - operation != CMD_DELETE) - ExecOpenIndices(resultRelInfo); -} - -/* - * ExecContextForcesOids - * - * This is pretty grotty: when doing INSERT, UPDATE, or SELECT INTO, - * we need to ensure that result tuples have space for an OID iff they are - * going to be stored into a relation that has OIDs. In other contexts - * we are free to choose whether to leave space for OIDs in result tuples - * (we generally don't want to, but we do if a physical-tlist optimization - * is possible). This routine checks the plan context and returns TRUE if the - * choice is forced, FALSE if the choice is not forced. In the TRUE case, - * *hasoids is set to the required value. - * - * One reason this is ugly is that all plan nodes in the plan tree will emit - * tuples with space for an OID, though we really only need the topmost node - * to do so. However, node types like Sort don't project new tuples but just - * return their inputs, and in those cases the requirement propagates down - * to the input node. Eventually we might make this code smart enough to - * recognize how far down the requirement really goes, but for now we just - * make all plan nodes do the same thing if the top level forces the choice. - * - * We assume that estate->es_result_relation_info is already set up to - * describe the target relation. Note that in an UPDATE that spans an - * inheritance tree, some of the target relations may have OIDs and some not. - * We have to make the decisions on a per-relation basis as we initialize - * each of the child plans of the topmost Append plan. - * - * SELECT INTO is even uglier, because we don't have the INTO relation's - * descriptor available when this code runs; we have to look aside at a - * flag set by InitPlan(). - */ -bool -ExecContextForcesOids(PlanState *planstate, bool *hasoids) -{ - if (planstate->state->es_select_into) - { - *hasoids = planstate->state->es_into_oids; - return true; - } - else - { - ResultRelInfo *ri = planstate->state->es_result_relation_info; - - if (ri != NULL) - { - Relation rel = ri->ri_RelationDesc; - - if (rel != NULL) - { - *hasoids = rel->rd_rel->relhasoids; - return true; - } - } - } - - return false; -} - -/* ---------------------------------------------------------------- - * ExecEndPlan - * - * Cleans up the query plan -- closes files and frees up storage - * - * NOTE: we are no longer very worried about freeing storage per se - * in this code; FreeExecutorState should be guaranteed to release all - * memory that needs to be released. What we are worried about doing - * is closing relations and dropping buffer pins. Thus, for example, - * tuple tables must be cleared or dropped to ensure pins are released. - * ---------------------------------------------------------------- - */ -void -ExecEndPlan(PlanState *planstate, EState *estate) -{ - ResultRelInfo *resultRelInfo; - int i; - ListCell *l; - - /* - * shut down any PlanQual processing we were doing - */ - if (estate->es_evalPlanQual != NULL) - EndEvalPlanQual(estate); - - /* - * shut down the node-type-specific query processing - */ - ExecEndNode(planstate); - - /* - * destroy the executor "tuple" table. - */ - ExecDropTupleTable(estate->es_tupleTable, true); - estate->es_tupleTable = NULL; - - /* - * close the result relation(s) if any, but hold locks until xact commit. - */ - resultRelInfo = estate->es_result_relations; - for (i = estate->es_num_result_relations; i > 0; i--) - { - /* Close indices and then the relation itself */ - ExecCloseIndices(resultRelInfo); - heap_close(resultRelInfo->ri_RelationDesc, NoLock); - resultRelInfo++; - } - - /* - * close any relations selected FOR UPDATE/FOR SHARE, again keeping locks - */ - foreach(l, estate->es_rowMarks) - { - ExecRowMark *erm = lfirst(l); - - heap_close(erm->relation, NoLock); - } -} - -/* ---------------------------------------------------------------- - * ExecutePlan - * - * processes the query plan to retrieve 'numberTuples' tuples in the - * direction specified. - * - * Retrieves all tuples if numberTuples is 0 - * - * result is either a slot containing the last tuple in the case - * of a SELECT or NULL otherwise. - * - * Note: the ctid attribute is a 'junk' attribute that is removed before the - * user can see it - * ---------------------------------------------------------------- - */ -static TupleTableSlot * -ExecutePlan(EState *estate, - PlanState *planstate, - CmdType operation, - long numberTuples, - ScanDirection direction, - DestReceiver *dest) -{ - JunkFilter *junkfilter; - TupleTableSlot *planSlot; - TupleTableSlot *slot; - ItemPointer tupleid = NULL; - ItemPointerData tuple_ctid; - long current_tuple_count; - TupleTableSlot *result; - - /* - * initialize local variables - */ - current_tuple_count = 0; - result = NULL; - - /* - * Set the direction. - */ - estate->es_direction = direction; - - /* - * Process BEFORE EACH STATEMENT triggers - */ - switch (operation) - { - case CMD_UPDATE: - ExecBSUpdateTriggers(estate, estate->es_result_relation_info); - break; - case CMD_DELETE: - ExecBSDeleteTriggers(estate, estate->es_result_relation_info); - break; - case CMD_INSERT: - ExecBSInsertTriggers(estate, estate->es_result_relation_info); - break; - default: - /* do nothing */ - break; - } - - /* - * Loop until we've processed the proper number of tuples from the plan. - */ - - for (;;) - { - /* Reset the per-output-tuple exprcontext */ - ResetPerTupleExprContext(estate); - - /* - * Execute the plan and obtain a tuple - */ -lnext: ; - if (estate->es_useEvalPlan) - { - planSlot = EvalPlanQualNext(estate); - if (TupIsNull(planSlot)) - planSlot = ExecProcNode(planstate); - } - else - planSlot = ExecProcNode(planstate); - - /* - * if the tuple is null, then we assume there is nothing more to - * process so we just return null... - */ - if (TupIsNull(planSlot)) - { - result = NULL; - break; - } - slot = planSlot; - - /* - * if we have a junk filter, then project a new tuple with the junk - * removed. - * - * Store this new "clean" tuple in the junkfilter's resultSlot. - * (Formerly, we stored it back over the "dirty" tuple, which is WRONG - * because that tuple slot has the wrong descriptor.) - * - * Also, extract all the junk information we need. - */ - if ((junkfilter = estate->es_junkFilter) != NULL) - { - Datum datum; - bool isNull; - - /* - * extract the 'ctid' junk attribute. - */ - if (operation == CMD_UPDATE || operation == CMD_DELETE) - { - datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo, - &isNull); - /* shouldn't ever get a null result... */ - if (isNull) - elog(ERROR, "ctid is NULL"); - - tupleid = (ItemPointer) DatumGetPointer(datum); - tuple_ctid = *tupleid; /* make sure we don't free the ctid!! */ - tupleid = &tuple_ctid; - } - - /* - * Process any FOR UPDATE or FOR SHARE locking requested. - */ - else if (estate->es_rowMarks != NIL) - { - ListCell *l; - - lmark: ; - foreach(l, estate->es_rowMarks) - { - ExecRowMark *erm = lfirst(l); - HeapTupleData tuple; - Buffer buffer; - ItemPointerData update_ctid; - TransactionId update_xmax; - TupleTableSlot *newSlot; - LockTupleMode lockmode; - HTSU_Result test; - - datum = ExecGetJunkAttribute(slot, - erm->ctidAttNo, - &isNull); - /* shouldn't ever get a null result... */ - if (isNull) - elog(ERROR, "ctid is NULL"); - - tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); - - if (erm->forUpdate) - lockmode = LockTupleExclusive; - else - lockmode = LockTupleShared; - - test = heap_lock_tuple(erm->relation, &tuple, &buffer, - &update_ctid, &update_xmax, - estate->es_snapshot->curcid, - lockmode, erm->noWait); - ReleaseBuffer(buffer); - switch (test) - { - case HeapTupleSelfUpdated: - /* treat it as deleted; do not process */ - goto lnext; - - case HeapTupleMayBeUpdated: - break; - - case HeapTupleUpdated: - if (IsXactIsoLevelSerializable) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("could not serialize access due to concurrent update"))); - if (!ItemPointerEquals(&update_ctid, - &tuple.t_self)) - { - /* updated, so look at updated version */ - newSlot = EvalPlanQual(estate, - erm->rti, - &update_ctid, - update_xmax, - estate->es_snapshot->curcid); - if (!TupIsNull(newSlot)) - { - slot = planSlot = newSlot; - estate->es_useEvalPlan = true; - goto lmark; - } - } - - /* - * if tuple was deleted or PlanQual failed for - * updated tuple - we must not return this tuple! - */ - goto lnext; - - default: - elog(ERROR, "unrecognized heap_lock_tuple status: %u", - test); - return NULL; - } - } - } - - /* - * Create a new "clean" tuple with all junk attributes removed. We - * don't need to do this for DELETE, however (there will in fact - * be no non-junk attributes in a DELETE!) - */ - if (operation != CMD_DELETE) - slot = ExecFilterJunk(junkfilter, slot); - } - - /* - * now that we have a tuple, do the appropriate thing with it.. either - * return it to the user, add it to a relation someplace, delete it - * from a relation, or modify some of its attributes. - */ - switch (operation) - { - case CMD_SELECT: - ExecSelect(slot, dest, estate); - result = slot; - break; - - case CMD_INSERT: - ExecInsert(slot, tupleid, planSlot, dest, estate); - result = NULL; - break; - - case CMD_DELETE: - ExecDelete(tupleid, planSlot, dest, estate); - result = NULL; - break; - - case CMD_UPDATE: - ExecUpdate(slot, tupleid, planSlot, dest, estate); - result = NULL; - break; - - default: - elog(ERROR, "unrecognized operation code: %d", - (int) operation); - result = NULL; - break; - } - - /* - * check our tuple count.. if we've processed the proper number then - * quit, else loop again and process more tuples. Zero numberTuples - * means no limit. - */ - current_tuple_count++; - if (numberTuples && numberTuples == current_tuple_count) - break; - } - - /* - * Process AFTER EACH STATEMENT triggers - */ - switch (operation) - { - case CMD_UPDATE: - ExecASUpdateTriggers(estate, estate->es_result_relation_info); - break; - case CMD_DELETE: - ExecASDeleteTriggers(estate, estate->es_result_relation_info); - break; - case CMD_INSERT: - ExecASInsertTriggers(estate, estate->es_result_relation_info); - break; - default: - /* do nothing */ - break; - } - - /* - * here, result is either a slot containing a tuple in the case of a - * SELECT or NULL otherwise. - */ - return result; -} - -/* ---------------------------------------------------------------- - * ExecSelect - * - * SELECTs are easy.. we just pass the tuple to the appropriate - * output function. - * ---------------------------------------------------------------- - */ -static void -ExecSelect(TupleTableSlot *slot, - DestReceiver *dest, - EState *estate) -{ - (*dest->receiveSlot) (slot, dest); - IncrRetrieved(); - (estate->es_processed)++; -} - -/* ---------------------------------------------------------------- - * ExecInsert - * - * INSERTs are trickier.. we have to insert the tuple into - * the base relation and insert appropriate tuples into the - * index relations. - * ---------------------------------------------------------------- - */ -static void -ExecInsert(TupleTableSlot *slot, - ItemPointer tupleid, - TupleTableSlot *planSlot, - DestReceiver *dest, - EState *estate) -{ - HeapTuple tuple; - ResultRelInfo *resultRelInfo; - Relation resultRelationDesc; - Oid newId; - - /* - * get the heap tuple out of the tuple table slot, making sure we have a - * writable copy - */ - tuple = ExecMaterializeSlot(slot); - - /* - * get information on the (current) result relation - */ - resultRelInfo = estate->es_result_relation_info; - resultRelationDesc = resultRelInfo->ri_RelationDesc; - - /* BEFORE ROW INSERT Triggers */ - if (resultRelInfo->ri_TrigDesc && - resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0) - { - HeapTuple newtuple; - - newtuple = ExecBRInsertTriggers(estate, resultRelInfo, tuple); - - 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); - ExecStoreTuple(newtuple, newslot, InvalidBuffer, false); - slot = newslot; - tuple = newtuple; - } - } - - /* - * Check the constraints of the tuple - */ - if (resultRelationDesc->rd_att->constr) - ExecConstraints(resultRelInfo, slot, estate); - - /* - * insert the tuple - * - * Note: heap_insert returns the tid (location) of the new tuple in the - * t_self field. - */ - newId = heap_insert(resultRelationDesc, tuple, - estate->es_snapshot->curcid, - true, true); - - IncrAppended(); - (estate->es_processed)++; - estate->es_lastoid = newId; - setLastTid(&(tuple->t_self)); - - /* - * insert index entries for tuple - */ - if (resultRelInfo->ri_NumIndices > 0) - ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false); - - /* AFTER ROW INSERT Triggers */ - ExecARInsertTriggers(estate, resultRelInfo, tuple); - - /* Process RETURNING if present */ - if (resultRelInfo->ri_projectReturning) - ExecProcessReturning(resultRelInfo->ri_projectReturning, - slot, planSlot, dest); -} - -/* ---------------------------------------------------------------- - * ExecDelete - * - * DELETE is like UPDATE, except that we delete the tuple and no - * index modifications are needed - * ---------------------------------------------------------------- - */ -static void -ExecDelete(ItemPointer tupleid, - TupleTableSlot *planSlot, - DestReceiver *dest, - EState *estate) -{ - ResultRelInfo *resultRelInfo; - Relation resultRelationDesc; - HTSU_Result result; - ItemPointerData update_ctid; - TransactionId update_xmax; - - /* - * get information on the (current) result relation - */ - resultRelInfo = estate->es_result_relation_info; - resultRelationDesc = resultRelInfo->ri_RelationDesc; - - /* BEFORE ROW DELETE Triggers */ - if (resultRelInfo->ri_TrigDesc && - resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_DELETE] > 0) - { - bool dodelete; - - dodelete = ExecBRDeleteTriggers(estate, resultRelInfo, tupleid, - estate->es_snapshot->curcid); - - if (!dodelete) /* "do nothing" */ - return; - } - -#ifdef REPLICATION - /* initialize the TxnToAbort return value */ - TxnToAbort = InvalidTransactionId; - - /* * Add the tuple info to the WriteSet. */ if ( txn_type == REPLICATED_LOCAL ) { - Oid resultRelationOid; - TupleCollection *tcoll; - - tcoll = &(((QueryInfo *) CurrentWriteSet->currQuery)->tcoll); - resultRelationOid = RelationGetRelid(resultRelationDesc); - if (resultRelationOid == tcoll->rel->relOid) - WriteSetCollectTuple(tupleid, planSlot, - CurrentWriteSet->currQuery, - estate->es_snapshot); } -#endif +// END OF STUFF FROM THE RIGHT /* * delete the tuple - * - * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that - * the row to be deleted 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. */ +// AN ADDITIONAL LINE FROM THE RIGHT +// A LAST COMMON LINE -ldelete:; - result = heap_delete(resultRelationDesc, tupleid, - &update_ctid, &update_xmax, - estate->es_snapshot->curcid, - estate->es_crosscheck_snapshot, - true /* wait for commit */ ); - //THIS LINE IS NEEDED TO TRIGGER THE BUG - 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; - goto ldelete; - } - } - /* tuple already deleted; nothing to do */ - return; - - default: - elog(ERROR, "unrecognized heap_delete status: %u", result); - return; - } - - IncrDeleted(); - (estate->es_processed)++; - - /* - * Note: Normally one would think that we have to delete index tuples - * associated with the heap tuple now... - * - * ... but in POSTGRES, we have no need to do this because VACUUM will - * take care of it later. We can't delete index tuples immediately - * anyway, since the tuple is still visible to other transactions. - */ - - /* AFTER ROW DELETE Triggers */ - ExecARDeleteTriggers(estate, resultRelInfo, tupleid); - - /* Process RETURNING if present */ - if (resultRelInfo->ri_projectReturning) - { - /* - * We have to put the target tuple into a slot, which means first we - * gotta fetch it. We can use the trigger tuple slot. - */ - TupleTableSlot *slot = estate->es_trig_tuple_slot; - HeapTupleData deltuple; - Buffer delbuffer; - - deltuple.t_self = *tupleid; - if (!heap_fetch(resultRelationDesc, SnapshotAny, - &deltuple, &delbuffer, false, NULL)) - elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING"); - - if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc)) - ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc)); - ExecStoreTuple(&deltuple, slot, InvalidBuffer, false); - - ExecProcessReturning(resultRelInfo->ri_projectReturning, - slot, planSlot, dest); - - ExecClearTuple(slot); - ReleaseBuffer(delbuffer); - } -} ============================================================ --- tests/test_a_merge_8/left 2218e9827eff22bde55cac4e48749c33d8231876 +++ tests/test_a_merge_8/left 4dd9089ffd37bbaf4a678815e4c1a2ffd15de547 @@ -1,1106 +1,8 @@ -/* ---------------------------------------------------------------- - * InitPlan - * - * Initializes the query plan: open files, allocate storage - * and start up the rule manager - * ---------------------------------------------------------------- - */ -static void -InitPlan(QueryDesc *queryDesc, int eflags) -{ - CmdType operation = queryDesc->operation; - Query *parseTree = queryDesc->parsetree; - Plan *plan = queryDesc->plantree; - EState *estate = queryDesc->estate; - PlanState *planstate; - List *rangeTable; - TupleDesc tupType; - ListCell *l; +// AN INITIAL COMMON LINE +// A LINE FROM THE LEFT +// ANOTHER COMMON LINE /* - * Do permissions checks. It's sufficient to examine the query's top - * rangetable here --- subplan RTEs will be checked during - * ExecInitSubPlan(). - */ - ExecCheckRTPerms(parseTree->rtable); - - /* - * get information from query descriptor - */ - rangeTable = parseTree->rtable; - - /* - * initialize the node's execution state - */ - estate->es_range_table = rangeTable; - - /* - * if there is a result relation, initialize result relation stuff - */ - if (parseTree->resultRelation) - { - List *resultRelations = parseTree->resultRelations; - int numResultRelations; - ResultRelInfo *resultRelInfos; - - if (resultRelations != NIL) - { - /* - * Multiple result relations (due to inheritance) - * parseTree->resultRelations identifies them all - */ - ResultRelInfo *resultRelInfo; - - numResultRelations = list_length(resultRelations); - resultRelInfos = (ResultRelInfo *) - palloc(numResultRelations * sizeof(ResultRelInfo)); - resultRelInfo = resultRelInfos; - foreach(l, resultRelations) - { - initResultRelInfo(resultRelInfo, - lfirst_int(l), - rangeTable, - operation, - estate->es_instrument); - resultRelInfo++; - } - } - else - { - /* - * Single result relation identified by parseTree->resultRelation - */ - numResultRelations = 1; - resultRelInfos = (ResultRelInfo *) palloc(sizeof(ResultRelInfo)); - initResultRelInfo(resultRelInfos, - parseTree->resultRelation, - rangeTable, - operation, - estate->es_instrument); - } - - estate->es_result_relations = resultRelInfos; - estate->es_num_result_relations = numResultRelations; - /* Initialize to first or only result rel */ - estate->es_result_relation_info = resultRelInfos; - } - else - { - /* - * if no result relation, then set state appropriately - */ - estate->es_result_relations = NULL; - estate->es_num_result_relations = 0; - estate->es_result_relation_info = NULL; - } - - /* - * Detect whether we're doing SELECT INTO. If so, set the es_into_oids - * flag appropriately so that the plan tree will be initialized with the - * correct tuple descriptors. (Other SELECT INTO stuff comes later.) - */ - estate->es_select_into = false; - if (operation == CMD_SELECT && parseTree->into != NULL) - { - estate->es_select_into = true; - estate->es_into_oids = interpretOidsOption(parseTree->intoOptions); - } - - /* - * Have to lock relations selected FOR UPDATE/FOR SHARE before we - * initialize the plan tree, else we'd be doing a lock upgrade. - * While we are at it, build the ExecRowMark list. - */ - estate->es_rowMarks = NIL; - foreach(l, parseTree->rowMarks) - { - RowMarkClause *rc = (RowMarkClause *) lfirst(l); - Oid relid = getrelid(rc->rti, rangeTable); - Relation relation; - ExecRowMark *erm; - - relation = heap_open(relid, RowShareLock); - erm = (ExecRowMark *) palloc(sizeof(ExecRowMark)); - erm->relation = relation; - erm->rti = rc->rti; - erm->forUpdate = rc->forUpdate; - erm->noWait = rc->noWait; - /* We'll set up ctidAttno below */ - erm->ctidAttNo = InvalidAttrNumber; - estate->es_rowMarks = lappend(estate->es_rowMarks, erm); - } - - /* - * initialize the executor "tuple" table. We need slots for all the plan - * nodes, plus possibly output slots for the junkfilter(s). At this point - * we aren't sure if we need junkfilters, so just add slots for them - * unconditionally. Also, if it's not a SELECT, set up a slot for use for - * trigger output tuples. - */ - { - int nSlots = ExecCountSlotsNode(plan); - - if (parseTree->resultRelations != NIL) - nSlots += list_length(parseTree->resultRelations); - else - nSlots += 1; - if (operation != CMD_SELECT) - nSlots++; /* for es_trig_tuple_slot */ - if (parseTree->returningLists) - nSlots++; /* for RETURNING projection */ - - estate->es_tupleTable = ExecCreateTupleTable(nSlots); - - if (operation != CMD_SELECT) - estate->es_trig_tuple_slot = - ExecAllocTableSlot(estate->es_tupleTable); - } - - /* mark EvalPlanQual not active */ - estate->es_topPlan = plan; - estate->es_evalPlanQual = NULL; - estate->es_evTupleNull = NULL; - estate->es_evTuple = NULL; - estate->es_useEvalPlan = false; - - /* - * initialize the private state information for all the nodes in the query - * tree. This opens files, allocates storage and leaves us ready to start - * processing tuples. - */ - planstate = ExecInitNode(plan, estate, eflags); - - /* - * Get the tuple descriptor describing the type of tuples to return. (this - * is especially important if we are creating a relation with "SELECT - * INTO") - */ - tupType = ExecGetResultType(planstate); - - /* - * Initialize the junk filter if needed. SELECT and INSERT queries need a - * filter if there are any junk attrs in the tlist. INSERT and SELECT - * INTO also need a filter if the plan may return raw disk tuples (else - * heap_insert will be scribbling on the source relation!). UPDATE and - * DELETE always need a filter, since there's always a junk 'ctid' - * attribute present --- no need to look first. - */ - { - bool junk_filter_needed = false; - ListCell *tlist; - - switch (operation) - { - case CMD_SELECT: - case CMD_INSERT: - foreach(tlist, plan->targetlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tlist); - - if (tle->resjunk) - { - junk_filter_needed = true; - break; - } - } - if (!junk_filter_needed && - (operation == CMD_INSERT || estate->es_select_into) && - ExecMayReturnRawTuples(planstate)) - junk_filter_needed = true; - break; - case CMD_UPDATE: - case CMD_DELETE: - junk_filter_needed = true; - break; - default: - break; - } - - if (junk_filter_needed) - { - /* - * If there are multiple result relations, each one needs its own - * junk filter. Note this is only possible for UPDATE/DELETE, so - * we can't be fooled by some needing a filter and some not. - */ - if (parseTree->resultRelations != NIL) - { - PlanState **appendplans; - int as_nplans; - ResultRelInfo *resultRelInfo; - int i; - - /* Top plan had better be an Append here. */ - Assert(IsA(plan, Append)); - Assert(((Append *) plan)->isTarget); - Assert(IsA(planstate, AppendState)); - appendplans = ((AppendState *) planstate)->appendplans; - as_nplans = ((AppendState *) planstate)->as_nplans; - Assert(as_nplans == estate->es_num_result_relations); - resultRelInfo = estate->es_result_relations; - for (i = 0; i < as_nplans; i++) - { - PlanState *subplan = appendplans[i]; - JunkFilter *j; - - j = ExecInitJunkFilter(subplan->plan->targetlist, - resultRelInfo->ri_RelationDesc->rd_att->tdhasoid, - ExecAllocTableSlot(estate->es_tupleTable)); - /* - * Since it must be UPDATE/DELETE, there had better be - * a "ctid" junk attribute in the tlist ... but ctid could - * be at a different resno for each result relation. - * We look up the ctid resnos now and save them in the - * junkfilters. - */ - j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); - if (!AttributeNumberIsValid(j->jf_junkAttNo)) - elog(ERROR, "could not find junk ctid column"); - resultRelInfo->ri_junkFilter = j; - resultRelInfo++; - } - - /* - * Set active junkfilter too; at this point ExecInitAppend has - * already selected an active result relation... - */ - estate->es_junkFilter = - estate->es_result_relation_info->ri_junkFilter; - } - else - { - /* Normal case with just one JunkFilter */ - JunkFilter *j; - - j = ExecInitJunkFilter(planstate->plan->targetlist, - tupType->tdhasoid, - ExecAllocTableSlot(estate->es_tupleTable)); - estate->es_junkFilter = j; - if (estate->es_result_relation_info) - estate->es_result_relation_info->ri_junkFilter = j; - - if (operation == CMD_SELECT) - { - /* For SELECT, want to return the cleaned tuple type */ - tupType = j->jf_cleanTupType; - /* For SELECT FOR UPDATE/SHARE, find the ctid attrs now */ - foreach(l, estate->es_rowMarks) - { - ExecRowMark *erm = (ExecRowMark *) lfirst(l); - char resname[32]; - - snprintf(resname, sizeof(resname), "ctid%u", erm->rti); - erm->ctidAttNo = ExecFindJunkAttribute(j, resname); - if (!AttributeNumberIsValid(erm->ctidAttNo)) - elog(ERROR, "could not find junk \"%s\" column", - resname); - } - } - else if (operation == CMD_UPDATE || operation == CMD_DELETE) - { - /* For UPDATE/DELETE, find the ctid junk attr now */ - j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); - if (!AttributeNumberIsValid(j->jf_junkAttNo)) - elog(ERROR, "could not find junk ctid column"); - } - } - } - else - estate->es_junkFilter = NULL; - } - - /* - * Initialize RETURNING projections if needed. - */ - if (parseTree->returningLists) - { - TupleTableSlot *slot; - ExprContext *econtext; - ResultRelInfo *resultRelInfo; - - /* - * We set QueryDesc.tupDesc to be the RETURNING rowtype in this case. - * We assume all the sublists will generate the same output tupdesc. - */ - tupType = ExecTypeFromTL((List *) linitial(parseTree->returningLists), - false); - - /* Set up a slot for the output of the RETURNING projection(s) */ - slot = ExecAllocTableSlot(estate->es_tupleTable); - ExecSetSlotDescriptor(slot, tupType); - /* Need an econtext too */ - econtext = CreateExprContext(estate); - - /* - * Build a projection for each result rel. Note that any SubPlans in - * the RETURNING lists get attached to the topmost plan node. - */ - Assert(list_length(parseTree->returningLists) == estate->es_num_result_relations); - resultRelInfo = estate->es_result_relations; - foreach(l, parseTree->returningLists) - { - List *rlist = (List *) lfirst(l); - List *rliststate; - - rliststate = (List *) ExecInitExpr((Expr *) rlist, planstate); - resultRelInfo->ri_projectReturning = - ExecBuildProjectionInfo(rliststate, econtext, slot, - resultRelInfo->ri_RelationDesc->rd_att); - resultRelInfo++; - } - - /* - * Because we already ran ExecInitNode() for the top plan node, any - * subplans we just attached to it won't have been initialized; so we - * have to do it here. (Ugly, but the alternatives seem worse.) - */ - foreach(l, planstate->subPlan) - { - SubPlanState *sstate = (SubPlanState *) lfirst(l); - - Assert(IsA(sstate, SubPlanState)); - if (sstate->planstate == NULL) /* already inited? */ - ExecInitSubPlan(sstate, estate, eflags); - } - } - - queryDesc->tupDesc = tupType; - queryDesc->planstate = planstate; - - /* - * If doing SELECT INTO, initialize the "into" relation. We must wait - * till now so we have the "clean" result tuple type to create the new - * table from. - * - * If EXPLAIN, skip creating the "into" relation. - */ - if (estate->es_select_into && !(eflags & EXEC_FLAG_EXPLAIN_ONLY)) - OpenIntoRel(queryDesc); -} - -/* - * Initialize ResultRelInfo data for one result relation - */ -static void -initResultRelInfo(ResultRelInfo *resultRelInfo, - Index resultRelationIndex, - List *rangeTable, - CmdType operation, - bool doInstrument) -{ - Oid resultRelationOid; - Relation resultRelationDesc; - - resultRelationOid = getrelid(resultRelationIndex, rangeTable); - resultRelationDesc = heap_open(resultRelationOid, RowExclusiveLock); - - switch (resultRelationDesc->rd_rel->relkind) - { - case RELKIND_SEQUENCE: - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change sequence \"%s\"", - RelationGetRelationName(resultRelationDesc)))); - break; - case RELKIND_TOASTVALUE: - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change TOAST relation \"%s\"", - RelationGetRelationName(resultRelationDesc)))); - break; - case RELKIND_VIEW: - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change view \"%s\"", - RelationGetRelationName(resultRelationDesc)))); - break; - } - - MemSet(resultRelInfo, 0, sizeof(ResultRelInfo)); - resultRelInfo->type = T_ResultRelInfo; - resultRelInfo->ri_RangeTableIndex = resultRelationIndex; - resultRelInfo->ri_RelationDesc = resultRelationDesc; - resultRelInfo->ri_NumIndices = 0; - resultRelInfo->ri_IndexRelationDescs = NULL; - resultRelInfo->ri_IndexRelationInfo = NULL; - /* make a copy so as not to depend on relcache info not changing... */ - resultRelInfo->ri_TrigDesc = CopyTriggerDesc(resultRelationDesc->trigdesc); - if (resultRelInfo->ri_TrigDesc) - { - int n = resultRelInfo->ri_TrigDesc->numtriggers; - - resultRelInfo->ri_TrigFunctions = (FmgrInfo *) - palloc0(n * sizeof(FmgrInfo)); - if (doInstrument) - resultRelInfo->ri_TrigInstrument = InstrAlloc(n); - else - resultRelInfo->ri_TrigInstrument = NULL; - } - else - { - resultRelInfo->ri_TrigFunctions = NULL; - resultRelInfo->ri_TrigInstrument = NULL; - } - resultRelInfo->ri_ConstraintExprs = NULL; - resultRelInfo->ri_junkFilter = NULL; - resultRelInfo->ri_projectReturning = NULL; - - /* - * If there are indices on the result relation, open them and save - * descriptors in the result relation info, so that we can add new index - * entries for the tuples we add/update. We need not do this for a - * DELETE, however, since deletion doesn't affect indexes. - */ - if (resultRelationDesc->rd_rel->relhasindex && - operation != CMD_DELETE) - ExecOpenIndices(resultRelInfo); -} - -/* - * ExecContextForcesOids - * - * This is pretty grotty: when doing INSERT, UPDATE, or SELECT INTO, - * we need to ensure that result tuples have space for an OID iff they are - * going to be stored into a relation that has OIDs. In other contexts - * we are free to choose whether to leave space for OIDs in result tuples - * (we generally don't want to, but we do if a physical-tlist optimization - * is possible). This routine checks the plan context and returns TRUE if the - * choice is forced, FALSE if the choice is not forced. In the TRUE case, - * *hasoids is set to the required value. - * - * One reason this is ugly is that all plan nodes in the plan tree will emit - * tuples with space for an OID, though we really only need the topmost node - * to do so. However, node types like Sort don't project new tuples but just - * return their inputs, and in those cases the requirement propagates down - * to the input node. Eventually we might make this code smart enough to - * recognize how far down the requirement really goes, but for now we just - * make all plan nodes do the same thing if the top level forces the choice. - * - * We assume that estate->es_result_relation_info is already set up to - * describe the target relation. Note that in an UPDATE that spans an - * inheritance tree, some of the target relations may have OIDs and some not. - * We have to make the decisions on a per-relation basis as we initialize - * each of the child plans of the topmost Append plan. - * - * SELECT INTO is even uglier, because we don't have the INTO relation's - * descriptor available when this code runs; we have to look aside at a - * flag set by InitPlan(). - */ -bool -ExecContextForcesOids(PlanState *planstate, bool *hasoids) -{ - if (planstate->state->es_select_into) - { - *hasoids = planstate->state->es_into_oids; - return true; - } - else - { - ResultRelInfo *ri = planstate->state->es_result_relation_info; - - if (ri != NULL) - { - Relation rel = ri->ri_RelationDesc; - - if (rel != NULL) - { - *hasoids = rel->rd_rel->relhasoids; - return true; - } - } - } - - return false; -} - -/* ---------------------------------------------------------------- - * ExecEndPlan - * - * Cleans up the query plan -- closes files and frees up storage - * - * NOTE: we are no longer very worried about freeing storage per se - * in this code; FreeExecutorState should be guaranteed to release all - * memory that needs to be released. What we are worried about doing - * is closing relations and dropping buffer pins. Thus, for example, - * tuple tables must be cleared or dropped to ensure pins are released. - * ---------------------------------------------------------------- - */ -void -ExecEndPlan(PlanState *planstate, EState *estate) -{ - ResultRelInfo *resultRelInfo; - int i; - ListCell *l; - - /* - * shut down any PlanQual processing we were doing - */ - if (estate->es_evalPlanQual != NULL) - EndEvalPlanQual(estate); - - /* - * shut down the node-type-specific query processing - */ - ExecEndNode(planstate); - - /* - * destroy the executor "tuple" table. - */ - ExecDropTupleTable(estate->es_tupleTable, true); - estate->es_tupleTable = NULL; - - /* - * close the result relation(s) if any, but hold locks until xact commit. - */ - resultRelInfo = estate->es_result_relations; - for (i = estate->es_num_result_relations; i > 0; i--) - { - /* Close indices and then the relation itself */ - ExecCloseIndices(resultRelInfo); - heap_close(resultRelInfo->ri_RelationDesc, NoLock); - resultRelInfo++; - } - - /* - * close any relations selected FOR UPDATE/FOR SHARE, again keeping locks - */ - foreach(l, estate->es_rowMarks) - { - ExecRowMark *erm = lfirst(l); - - heap_close(erm->relation, NoLock); - } -} - -/* ---------------------------------------------------------------- - * ExecutePlan - * - * processes the query plan to retrieve 'numberTuples' tuples in the - * direction specified. - * - * Retrieves all tuples if numberTuples is 0 - * - * result is either a slot containing the last tuple in the case - * of a SELECT or NULL otherwise. - * - * Note: the ctid attribute is a 'junk' attribute that is removed before the - * user can see it - * ---------------------------------------------------------------- - */ -static TupleTableSlot * -ExecutePlan(EState *estate, - PlanState *planstate, - CmdType operation, - long numberTuples, - ScanDirection direction, - DestReceiver *dest) -{ - JunkFilter *junkfilter; - TupleTableSlot *planSlot; - TupleTableSlot *slot; - ItemPointer tupleid = NULL; - ItemPointerData tuple_ctid; - long current_tuple_count; - TupleTableSlot *result; - - /* - * initialize local variables - */ - current_tuple_count = 0; - result = NULL; - - /* - * Set the direction. - */ - estate->es_direction = direction; - - /* - * Process BEFORE EACH STATEMENT triggers - */ - switch (operation) - { - case CMD_UPDATE: - ExecBSUpdateTriggers(estate, estate->es_result_relation_info); - break; - case CMD_DELETE: - ExecBSDeleteTriggers(estate, estate->es_result_relation_info); - break; - case CMD_INSERT: - ExecBSInsertTriggers(estate, estate->es_result_relation_info); - break; - default: - /* do nothing */ - break; - } - - /* - * Loop until we've processed the proper number of tuples from the plan. - */ - - for (;;) - { - /* Reset the per-output-tuple exprcontext */ - ResetPerTupleExprContext(estate); - - /* - * Execute the plan and obtain a tuple - */ -lnext: ; - if (estate->es_useEvalPlan) - { - planSlot = EvalPlanQualNext(estate); - if (TupIsNull(planSlot)) - planSlot = ExecProcNode(planstate); - } - else - planSlot = ExecProcNode(planstate); - - /* - * if the tuple is null, then we assume there is nothing more to - * process so we just return null... - */ - if (TupIsNull(planSlot)) - { - result = NULL; - break; - } - slot = planSlot; - - /* - * if we have a junk filter, then project a new tuple with the junk - * removed. - * - * Store this new "clean" tuple in the junkfilter's resultSlot. - * (Formerly, we stored it back over the "dirty" tuple, which is WRONG - * because that tuple slot has the wrong descriptor.) - * - * Also, extract all the junk information we need. - */ - if ((junkfilter = estate->es_junkFilter) != NULL) - { - Datum datum; - bool isNull; - - /* - * extract the 'ctid' junk attribute. - */ - if (operation == CMD_UPDATE || operation == CMD_DELETE) - { - datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo, - &isNull); - /* shouldn't ever get a null result... */ - if (isNull) - elog(ERROR, "ctid is NULL"); - - tupleid = (ItemPointer) DatumGetPointer(datum); - tuple_ctid = *tupleid; /* make sure we don't free the ctid!! */ - tupleid = &tuple_ctid; - } - - /* - * Process any FOR UPDATE or FOR SHARE locking requested. - */ - else if (estate->es_rowMarks != NIL) - { - ListCell *l; - - lmark: ; - foreach(l, estate->es_rowMarks) - { - ExecRowMark *erm = lfirst(l); - HeapTupleData tuple; - Buffer buffer; - ItemPointerData update_ctid; - TransactionId update_xmax; - TupleTableSlot *newSlot; - LockTupleMode lockmode; - HTSU_Result test; - - datum = ExecGetJunkAttribute(slot, - erm->ctidAttNo, - &isNull); - /* shouldn't ever get a null result... */ - if (isNull) - elog(ERROR, "ctid is NULL"); - - tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); - - if (erm->forUpdate) - lockmode = LockTupleExclusive; - else - lockmode = LockTupleShared; - - test = heap_lock_tuple(erm->relation, &tuple, &buffer, - &update_ctid, &update_xmax, - estate->es_snapshot->curcid, - lockmode, erm->noWait); - ReleaseBuffer(buffer); - switch (test) - { - case HeapTupleSelfUpdated: - /* treat it as deleted; do not process */ - goto lnext; - - case HeapTupleMayBeUpdated: - break; - - case HeapTupleUpdated: - if (IsXactIsoLevelSerializable) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("could not serialize access due to concurrent update"))); - if (!ItemPointerEquals(&update_ctid, - &tuple.t_self)) - { - /* updated, so look at updated version */ - newSlot = EvalPlanQual(estate, - erm->rti, - &update_ctid, - update_xmax, - estate->es_snapshot->curcid); - if (!TupIsNull(newSlot)) - { - slot = planSlot = newSlot; - estate->es_useEvalPlan = true; - goto lmark; - } - } - - /* - * if tuple was deleted or PlanQual failed for - * updated tuple - we must not return this tuple! - */ - goto lnext; - - default: - elog(ERROR, "unrecognized heap_lock_tuple status: %u", - test); - return NULL; - } - } - } - - /* - * Create a new "clean" tuple with all junk attributes removed. We - * don't need to do this for DELETE, however (there will in fact - * be no non-junk attributes in a DELETE!) - */ - if (operation != CMD_DELETE) - slot = ExecFilterJunk(junkfilter, slot); - } - - /* - * now that we have a tuple, do the appropriate thing with it.. either - * return it to the user, add it to a relation someplace, delete it - * from a relation, or modify some of its attributes. - */ - switch (operation) - { - case CMD_SELECT: - ExecSelect(slot, dest, estate); - result = slot; - break; - - case CMD_INSERT: - ExecInsert(slot, tupleid, planSlot, dest, estate); - result = NULL; - break; - - case CMD_DELETE: - ExecDelete(tupleid, planSlot, dest, estate); - result = NULL; - break; - - case CMD_UPDATE: - ExecUpdate(slot, tupleid, planSlot, dest, estate); - result = NULL; - break; - - default: - elog(ERROR, "unrecognized operation code: %d", - (int) operation); - result = NULL; - break; - } - - /* - * check our tuple count.. if we've processed the proper number then - * quit, else loop again and process more tuples. Zero numberTuples - * means no limit. - */ - current_tuple_count++; - if (numberTuples && numberTuples == current_tuple_count) - break; - } - - /* - * Process AFTER EACH STATEMENT triggers - */ - switch (operation) - { - case CMD_UPDATE: - ExecASUpdateTriggers(estate, estate->es_result_relation_info); - break; - case CMD_DELETE: - ExecASDeleteTriggers(estate, estate->es_result_relation_info); - break; - case CMD_INSERT: - ExecASInsertTriggers(estate, estate->es_result_relation_info); - break; - default: - /* do nothing */ - break; - } - - /* - * here, result is either a slot containing a tuple in the case of a - * SELECT or NULL otherwise. - */ - return result; -} - -/* ---------------------------------------------------------------- - * ExecSelect - * - * SELECTs are easy.. we just pass the tuple to the appropriate - * output function. - * ---------------------------------------------------------------- - */ -static void -ExecSelect(TupleTableSlot *slot, - DestReceiver *dest, - EState *estate) -{ - (*dest->receiveSlot) (slot, dest); - IncrRetrieved(); - (estate->es_processed)++; -} - -/* ---------------------------------------------------------------- - * ExecInsert - * - * INSERTs are trickier.. we have to insert the tuple into - * the base relation and insert appropriate tuples into the - * index relations. - * ---------------------------------------------------------------- - */ -static void -ExecInsert(TupleTableSlot *slot, - ItemPointer tupleid, - TupleTableSlot *planSlot, - DestReceiver *dest, - EState *estate) -{ - HeapTuple tuple; - ResultRelInfo *resultRelInfo; - Relation resultRelationDesc; - Oid newId; - - /* - * get the heap tuple out of the tuple table slot, making sure we have a - * writable copy - */ - tuple = ExecMaterializeSlot(slot); - - /* - * get information on the (current) result relation - */ - resultRelInfo = estate->es_result_relation_info; - resultRelationDesc = resultRelInfo->ri_RelationDesc; - - /* BEFORE ROW INSERT Triggers */ - if (resultRelInfo->ri_TrigDesc && - resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0) - { - HeapTuple newtuple; - - newtuple = ExecBRInsertTriggers(estate, resultRelInfo, tuple); - - 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); - ExecStoreTuple(newtuple, newslot, InvalidBuffer, false); - slot = newslot; - tuple = newtuple; - } - } - - /* - * Check the constraints of the tuple - */ - if (resultRelationDesc->rd_att->constr) - ExecConstraints(resultRelInfo, slot, estate); - - /* - * insert the tuple - * - * Note: heap_insert returns the tid (location) of the new tuple in the - * t_self field. - */ - newId = heap_insert(resultRelationDesc, tuple, - estate->es_snapshot->curcid, - true, true); - - IncrAppended(); - (estate->es_processed)++; - estate->es_lastoid = newId; - setLastTid(&(tuple->t_self)); - - /* - * insert index entries for tuple - */ - if (resultRelInfo->ri_NumIndices > 0) - ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false); - - /* AFTER ROW INSERT Triggers */ - ExecARInsertTriggers(estate, resultRelInfo, tuple); - - /* Process RETURNING if present */ - if (resultRelInfo->ri_projectReturning) - ExecProcessReturning(resultRelInfo->ri_projectReturning, - slot, planSlot, dest); -} - -/* ---------------------------------------------------------------- - * ExecDelete - * - * DELETE is like UPDATE, except that we delete the tuple and no - * index modifications are needed - * ---------------------------------------------------------------- - */ -static void -ExecDelete(ItemPointer tupleid, - TupleTableSlot *planSlot, - DestReceiver *dest, - EState *estate) -{ - ResultRelInfo *resultRelInfo; - Relation resultRelationDesc; - HTSU_Result result; - ItemPointerData update_ctid; - TransactionId update_xmax; - - /* - * get information on the (current) result relation - */ - resultRelInfo = estate->es_result_relation_info; - resultRelationDesc = resultRelInfo->ri_RelationDesc; - - /* BEFORE ROW DELETE Triggers */ - if (resultRelInfo->ri_TrigDesc && - resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_DELETE] > 0) - { - bool dodelete; - - dodelete = ExecBRDeleteTriggers(estate, resultRelInfo, tupleid, - estate->es_snapshot->curcid); - - if (!dodelete) /* "do nothing" */ - return; - } - - /* * delete the tuple - * - * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that - * the row to be deleted 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. */ +// A LAST COMMON LINE -ldelete:; - result = heap_delete(resultRelationDesc, tupleid, - &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; - goto ldelete; - } - } - /* tuple already deleted; nothing to do */ - return; - - default: - elog(ERROR, "unrecognized heap_delete status: %u", result); - return; - } - - IncrDeleted(); - (estate->es_processed)++; - - /* - * Note: Normally one would think that we have to delete index tuples - * associated with the heap tuple now... - * - * ... but in POSTGRES, we have no need to do this because VACUUM will - * take care of it later. We can't delete index tuples immediately - * anyway, since the tuple is still visible to other transactions. - */ - - /* AFTER ROW DELETE Triggers */ - ExecARDeleteTriggers(estate, resultRelInfo, tupleid); - - /* Process RETURNING if present */ - if (resultRelInfo->ri_projectReturning) - { - /* - * We have to put the target tuple into a slot, which means first we - * gotta fetch it. We can use the trigger tuple slot. - */ - TupleTableSlot *slot = estate->es_trig_tuple_slot; - HeapTupleData deltuple; - Buffer delbuffer; - - deltuple.t_self = *tupleid; - if (!heap_fetch(resultRelationDesc, SnapshotAny, - &deltuple, &delbuffer, false, NULL)) - elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING"); - - if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc)) - ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc)); - ExecStoreTuple(&deltuple, slot, InvalidBuffer, false); - - ExecProcessReturning(resultRelInfo->ri_projectReturning, - slot, planSlot, dest); - - ExecClearTuple(slot); - ReleaseBuffer(delbuffer); - } -} ============================================================ --- tests/test_a_merge_8/parent 635502b5dd25e4891825e8f584e1f423c8432223 +++ tests/test_a_merge_8/parent f629245675b8b8758dc7e7ea2b791cbc14c49de0 @@ -1,1105 +1,7 @@ -/* ---------------------------------------------------------------- - * InitPlan - * - * Initializes the query plan: open files, allocate storage - * and start up the rule manager - * ---------------------------------------------------------------- - */ -static void -InitPlan(QueryDesc *queryDesc, int eflags) -{ - CmdType operation = queryDesc->operation; - Query *parseTree = queryDesc->parsetree; - Plan *plan = queryDesc->plantree; - EState *estate = queryDesc->estate; - PlanState *planstate; - List *rangeTable; - TupleDesc tupType; - ListCell *l; +// AN INITIAL COMMON LINE +// ANOTHER COMMON LINE /* - * Do permissions checks. It's sufficient to examine the query's top - * rangetable here --- subplan RTEs will be checked during - * ExecInitSubPlan(). - */ - ExecCheckRTPerms(parseTree->rtable); - - /* - * get information from query descriptor - */ - rangeTable = parseTree->rtable; - - /* - * initialize the node's execution state - */ - estate->es_range_table = rangeTable; - - /* - * if there is a result relation, initialize result relation stuff - */ - if (parseTree->resultRelation) - { - List *resultRelations = parseTree->resultRelations; - int numResultRelations; - ResultRelInfo *resultRelInfos; - - if (resultRelations != NIL) - { - /* - * Multiple result relations (due to inheritance) - * parseTree->resultRelations identifies them all - */ - ResultRelInfo *resultRelInfo; - - numResultRelations = list_length(resultRelations); - resultRelInfos = (ResultRelInfo *) - palloc(numResultRelations * sizeof(ResultRelInfo)); - resultRelInfo = resultRelInfos; - foreach(l, resultRelations) - { - initResultRelInfo(resultRelInfo, - lfirst_int(l), - rangeTable, - operation, - estate->es_instrument); - resultRelInfo++; - } - } - else - { - /* - * Single result relation identified by parseTree->resultRelation - */ - numResultRelations = 1; - resultRelInfos = (ResultRelInfo *) palloc(sizeof(ResultRelInfo)); - initResultRelInfo(resultRelInfos, - parseTree->resultRelation, - rangeTable, - operation, - estate->es_instrument); - } - - estate->es_result_relations = resultRelInfos; - estate->es_num_result_relations = numResultRelations; - /* Initialize to first or only result rel */ - estate->es_result_relation_info = resultRelInfos; - } - else - { - /* - * if no result relation, then set state appropriately - */ - estate->es_result_relations = NULL; - estate->es_num_result_relations = 0; - estate->es_result_relation_info = NULL; - } - - /* - * Detect whether we're doing SELECT INTO. If so, set the es_into_oids - * flag appropriately so that the plan tree will be initialized with the - * correct tuple descriptors. (Other SELECT INTO stuff comes later.) - */ - estate->es_select_into = false; - if (operation == CMD_SELECT && parseTree->into != NULL) - { - estate->es_select_into = true; - estate->es_into_oids = interpretOidsOption(parseTree->intoOptions); - } - - /* - * Have to lock relations selected FOR UPDATE/FOR SHARE before we - * initialize the plan tree, else we'd be doing a lock upgrade. - * While we are at it, build the ExecRowMark list. - */ - estate->es_rowMarks = NIL; - foreach(l, parseTree->rowMarks) - { - RowMarkClause *rc = (RowMarkClause *) lfirst(l); - Oid relid = getrelid(rc->rti, rangeTable); - Relation relation; - ExecRowMark *erm; - - relation = heap_open(relid, RowShareLock); - erm = (ExecRowMark *) palloc(sizeof(ExecRowMark)); - erm->relation = relation; - erm->rti = rc->rti; - erm->forUpdate = rc->forUpdate; - erm->noWait = rc->noWait; - /* We'll set up ctidAttno below */ - erm->ctidAttNo = InvalidAttrNumber; - estate->es_rowMarks = lappend(estate->es_rowMarks, erm); - } - - /* - * initialize the executor "tuple" table. We need slots for all the plan - * nodes, plus possibly output slots for the junkfilter(s). At this point - * we aren't sure if we need junkfilters, so just add slots for them - * unconditionally. Also, if it's not a SELECT, set up a slot for use for - * trigger output tuples. - */ - { - int nSlots = ExecCountSlotsNode(plan); - - if (parseTree->resultRelations != NIL) - nSlots += list_length(parseTree->resultRelations); - else - nSlots += 1; - if (operation != CMD_SELECT) - nSlots++; /* for es_trig_tuple_slot */ - if (parseTree->returningLists) - nSlots++; /* for RETURNING projection */ - - estate->es_tupleTable = ExecCreateTupleTable(nSlots); - - if (operation != CMD_SELECT) - estate->es_trig_tuple_slot = - ExecAllocTableSlot(estate->es_tupleTable); - } - - /* mark EvalPlanQual not active */ - estate->es_topPlan = plan; - estate->es_evalPlanQual = NULL; - estate->es_evTupleNull = NULL; - estate->es_evTuple = NULL; - estate->es_useEvalPlan = false; - - /* - * initialize the private state information for all the nodes in the query - * tree. This opens files, allocates storage and leaves us ready to start - * processing tuples. - */ - planstate = ExecInitNode(plan, estate, eflags); - - /* - * Get the tuple descriptor describing the type of tuples to return. (this - * is especially important if we are creating a relation with "SELECT - * INTO") - */ - tupType = ExecGetResultType(planstate); - - /* - * Initialize the junk filter if needed. SELECT and INSERT queries need a - * filter if there are any junk attrs in the tlist. INSERT and SELECT - * INTO also need a filter if the plan may return raw disk tuples (else - * heap_insert will be scribbling on the source relation!). UPDATE and - * DELETE always need a filter, since there's always a junk 'ctid' - * attribute present --- no need to look first. - */ - { - bool junk_filter_needed = false; - ListCell *tlist; - - switch (operation) - { - case CMD_SELECT: - case CMD_INSERT: - foreach(tlist, plan->targetlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tlist); - - if (tle->resjunk) - { - junk_filter_needed = true; - break; - } - } - if (!junk_filter_needed && - (operation == CMD_INSERT || estate->es_select_into) && - ExecMayReturnRawTuples(planstate)) - junk_filter_needed = true; - break; - case CMD_UPDATE: - case CMD_DELETE: - junk_filter_needed = true; - break; - default: - break; - } - - if (junk_filter_needed) - { - /* - * If there are multiple result relations, each one needs its own - * junk filter. Note this is only possible for UPDATE/DELETE, so - * we can't be fooled by some needing a filter and some not. - */ - if (parseTree->resultRelations != NIL) - { - PlanState **appendplans; - int as_nplans; - ResultRelInfo *resultRelInfo; - int i; - - /* Top plan had better be an Append here. */ - Assert(IsA(plan, Append)); - Assert(((Append *) plan)->isTarget); - Assert(IsA(planstate, AppendState)); - appendplans = ((AppendState *) planstate)->appendplans; - as_nplans = ((AppendState *) planstate)->as_nplans; - Assert(as_nplans == estate->es_num_result_relations); - resultRelInfo = estate->es_result_relations; - for (i = 0; i < as_nplans; i++) - { - PlanState *subplan = appendplans[i]; - JunkFilter *j; - - j = ExecInitJunkFilter(subplan->plan->targetlist, - resultRelInfo->ri_RelationDesc->rd_att->tdhasoid, - ExecAllocTableSlot(estate->es_tupleTable)); - /* - * Since it must be UPDATE/DELETE, there had better be - * a "ctid" junk attribute in the tlist ... but ctid could - * be at a different resno for each result relation. - * We look up the ctid resnos now and save them in the - * junkfilters. - */ - j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); - if (!AttributeNumberIsValid(j->jf_junkAttNo)) - elog(ERROR, "could not find junk ctid column"); - resultRelInfo->ri_junkFilter = j; - resultRelInfo++; - } - - /* - * Set active junkfilter too; at this point ExecInitAppend has - * already selected an active result relation... - */ - estate->es_junkFilter = - estate->es_result_relation_info->ri_junkFilter; - } - else - { - /* Normal case with just one JunkFilter */ - JunkFilter *j; - - j = ExecInitJunkFilter(planstate->plan->targetlist, - tupType->tdhasoid, - ExecAllocTableSlot(estate->es_tupleTable)); - estate->es_junkFilter = j; - if (estate->es_result_relation_info) - estate->es_result_relation_info->ri_junkFilter = j; - - if (operation == CMD_SELECT) - { - /* For SELECT, want to return the cleaned tuple type */ - tupType = j->jf_cleanTupType; - /* For SELECT FOR UPDATE/SHARE, find the ctid attrs now */ - foreach(l, estate->es_rowMarks) - { - ExecRowMark *erm = (ExecRowMark *) lfirst(l); - char resname[32]; - - snprintf(resname, sizeof(resname), "ctid%u", erm->rti); - erm->ctidAttNo = ExecFindJunkAttribute(j, resname); - if (!AttributeNumberIsValid(erm->ctidAttNo)) - elog(ERROR, "could not find junk \"%s\" column", - resname); - } - } - else if (operation == CMD_UPDATE || operation == CMD_DELETE) - { - /* For UPDATE/DELETE, find the ctid junk attr now */ - j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); - if (!AttributeNumberIsValid(j->jf_junkAttNo)) - elog(ERROR, "could not find junk ctid column"); - } - } - } - else - estate->es_junkFilter = NULL; - } - - /* - * Initialize RETURNING projections if needed. - */ - if (parseTree->returningLists) - { - TupleTableSlot *slot; - ExprContext *econtext; - ResultRelInfo *resultRelInfo; - - /* - * We set QueryDesc.tupDesc to be the RETURNING rowtype in this case. - * We assume all the sublists will generate the same output tupdesc. - */ - tupType = ExecTypeFromTL((List *) linitial(parseTree->returningLists), - false); - - /* Set up a slot for the output of the RETURNING projection(s) */ - slot = ExecAllocTableSlot(estate->es_tupleTable); - ExecSetSlotDescriptor(slot, tupType); - /* Need an econtext too */ - econtext = CreateExprContext(estate); - - /* - * Build a projection for each result rel. Note that any SubPlans in - * the RETURNING lists get attached to the topmost plan node. - */ - Assert(list_length(parseTree->returningLists) == estate->es_num_result_relations); - resultRelInfo = estate->es_result_relations; - foreach(l, parseTree->returningLists) - { - List *rlist = (List *) lfirst(l); - List *rliststate; - - rliststate = (List *) ExecInitExpr((Expr *) rlist, planstate); - resultRelInfo->ri_projectReturning = - ExecBuildProjectionInfo(rliststate, econtext, slot); - resultRelInfo++; - } - - /* - * Because we already ran ExecInitNode() for the top plan node, any - * subplans we just attached to it won't have been initialized; so we - * have to do it here. (Ugly, but the alternatives seem worse.) - */ - foreach(l, planstate->subPlan) - { - SubPlanState *sstate = (SubPlanState *) lfirst(l); - - Assert(IsA(sstate, SubPlanState)); - if (sstate->planstate == NULL) /* already inited? */ - ExecInitSubPlan(sstate, estate, eflags); - } - } - - queryDesc->tupDesc = tupType; - queryDesc->planstate = planstate; - - /* - * If doing SELECT INTO, initialize the "into" relation. We must wait - * till now so we have the "clean" result tuple type to create the new - * table from. - * - * If EXPLAIN, skip creating the "into" relation. - */ - if (estate->es_select_into && !(eflags & EXEC_FLAG_EXPLAIN_ONLY)) - OpenIntoRel(queryDesc); -} - -/* - * Initialize ResultRelInfo data for one result relation - */ -static void -initResultRelInfo(ResultRelInfo *resultRelInfo, - Index resultRelationIndex, - List *rangeTable, - CmdType operation, - bool doInstrument) -{ - Oid resultRelationOid; - Relation resultRelationDesc; - - resultRelationOid = getrelid(resultRelationIndex, rangeTable); - resultRelationDesc = heap_open(resultRelationOid, RowExclusiveLock); - - switch (resultRelationDesc->rd_rel->relkind) - { - case RELKIND_SEQUENCE: - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change sequence \"%s\"", - RelationGetRelationName(resultRelationDesc)))); - break; - case RELKIND_TOASTVALUE: - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change TOAST relation \"%s\"", - RelationGetRelationName(resultRelationDesc)))); - break; - case RELKIND_VIEW: - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change view \"%s\"", - RelationGetRelationName(resultRelationDesc)))); - break; - } - - MemSet(resultRelInfo, 0, sizeof(ResultRelInfo)); - resultRelInfo->type = T_ResultRelInfo; - resultRelInfo->ri_RangeTableIndex = resultRelationIndex; - resultRelInfo->ri_RelationDesc = resultRelationDesc; - resultRelInfo->ri_NumIndices = 0; - resultRelInfo->ri_IndexRelationDescs = NULL; - resultRelInfo->ri_IndexRelationInfo = NULL; - /* make a copy so as not to depend on relcache info not changing... */ - resultRelInfo->ri_TrigDesc = CopyTriggerDesc(resultRelationDesc->trigdesc); - if (resultRelInfo->ri_TrigDesc) - { - int n = resultRelInfo->ri_TrigDesc->numtriggers; - - resultRelInfo->ri_TrigFunctions = (FmgrInfo *) - palloc0(n * sizeof(FmgrInfo)); - if (doInstrument) - resultRelInfo->ri_TrigInstrument = InstrAlloc(n); - else - resultRelInfo->ri_TrigInstrument = NULL; - } - else - { - resultRelInfo->ri_TrigFunctions = NULL; - resultRelInfo->ri_TrigInstrument = NULL; - } - resultRelInfo->ri_ConstraintExprs = NULL; - resultRelInfo->ri_junkFilter = NULL; - resultRelInfo->ri_projectReturning = NULL; - - /* - * If there are indices on the result relation, open them and save - * descriptors in the result relation info, so that we can add new index - * entries for the tuples we add/update. We need not do this for a - * DELETE, however, since deletion doesn't affect indexes. - */ - if (resultRelationDesc->rd_rel->relhasindex && - operation != CMD_DELETE) - ExecOpenIndices(resultRelInfo); -} - -/* - * ExecContextForcesOids - * - * This is pretty grotty: when doing INSERT, UPDATE, or SELECT INTO, - * we need to ensure that result tuples have space for an OID iff they are - * going to be stored into a relation that has OIDs. In other contexts - * we are free to choose whether to leave space for OIDs in result tuples - * (we generally don't want to, but we do if a physical-tlist optimization - * is possible). This routine checks the plan context and returns TRUE if the - * choice is forced, FALSE if the choice is not forced. In the TRUE case, - * *hasoids is set to the required value. - * - * One reason this is ugly is that all plan nodes in the plan tree will emit - * tuples with space for an OID, though we really only need the topmost node - * to do so. However, node types like Sort don't project new tuples but just - * return their inputs, and in those cases the requirement propagates down - * to the input node. Eventually we might make this code smart enough to - * recognize how far down the requirement really goes, but for now we just - * make all plan nodes do the same thing if the top level forces the choice. - * - * We assume that estate->es_result_relation_info is already set up to - * describe the target relation. Note that in an UPDATE that spans an - * inheritance tree, some of the target relations may have OIDs and some not. - * We have to make the decisions on a per-relation basis as we initialize - * each of the child plans of the topmost Append plan. - * - * SELECT INTO is even uglier, because we don't have the INTO relation's - * descriptor available when this code runs; we have to look aside at a - * flag set by InitPlan(). - */ -bool -ExecContextForcesOids(PlanState *planstate, bool *hasoids) -{ - if (planstate->state->es_select_into) - { - *hasoids = planstate->state->es_into_oids; - return true; - } - else - { - ResultRelInfo *ri = planstate->state->es_result_relation_info; - - if (ri != NULL) - { - Relation rel = ri->ri_RelationDesc; - - if (rel != NULL) - { - *hasoids = rel->rd_rel->relhasoids; - return true; - } - } - } - - return false; -} - -/* ---------------------------------------------------------------- - * ExecEndPlan - * - * Cleans up the query plan -- closes files and frees up storage - * - * NOTE: we are no longer very worried about freeing storage per se - * in this code; FreeExecutorState should be guaranteed to release all - * memory that needs to be released. What we are worried about doing - * is closing relations and dropping buffer pins. Thus, for example, - * tuple tables must be cleared or dropped to ensure pins are released. - * ---------------------------------------------------------------- - */ -void -ExecEndPlan(PlanState *planstate, EState *estate) -{ - ResultRelInfo *resultRelInfo; - int i; - ListCell *l; - - /* - * shut down any PlanQual processing we were doing - */ - if (estate->es_evalPlanQual != NULL) - EndEvalPlanQual(estate); - - /* - * shut down the node-type-specific query processing - */ - ExecEndNode(planstate); - - /* - * destroy the executor "tuple" table. - */ - ExecDropTupleTable(estate->es_tupleTable, true); - estate->es_tupleTable = NULL; - - /* - * close the result relation(s) if any, but hold locks until xact commit. - */ - resultRelInfo = estate->es_result_relations; - for (i = estate->es_num_result_relations; i > 0; i--) - { - /* Close indices and then the relation itself */ - ExecCloseIndices(resultRelInfo); - heap_close(resultRelInfo->ri_RelationDesc, NoLock); - resultRelInfo++; - } - - /* - * close any relations selected FOR UPDATE/FOR SHARE, again keeping locks - */ - foreach(l, estate->es_rowMarks) - { - ExecRowMark *erm = lfirst(l); - - heap_close(erm->relation, NoLock); - } -} - -/* ---------------------------------------------------------------- - * ExecutePlan - * - * processes the query plan to retrieve 'numberTuples' tuples in the - * direction specified. - * - * Retrieves all tuples if numberTuples is 0 - * - * result is either a slot containing the last tuple in the case - * of a SELECT or NULL otherwise. - * - * Note: the ctid attribute is a 'junk' attribute that is removed before the - * user can see it - * ---------------------------------------------------------------- - */ -static TupleTableSlot * -ExecutePlan(EState *estate, - PlanState *planstate, - CmdType operation, - long numberTuples, - ScanDirection direction, - DestReceiver *dest) -{ - JunkFilter *junkfilter; - TupleTableSlot *planSlot; - TupleTableSlot *slot; - ItemPointer tupleid = NULL; - ItemPointerData tuple_ctid; - long current_tuple_count; - TupleTableSlot *result; - - /* - * initialize local variables - */ - current_tuple_count = 0; - result = NULL; - - /* - * Set the direction. - */ - estate->es_direction = direction; - - /* - * Process BEFORE EACH STATEMENT triggers - */ - switch (operation) - { - case CMD_UPDATE: - ExecBSUpdateTriggers(estate, estate->es_result_relation_info); - break; - case CMD_DELETE: - ExecBSDeleteTriggers(estate, estate->es_result_relation_info); - break; - case CMD_INSERT: - ExecBSInsertTriggers(estate, estate->es_result_relation_info); - break; - default: - /* do nothing */ - break; - } - - /* - * Loop until we've processed the proper number of tuples from the plan. - */ - - for (;;) - { - /* Reset the per-output-tuple exprcontext */ - ResetPerTupleExprContext(estate); - - /* - * Execute the plan and obtain a tuple - */ -lnext: ; - if (estate->es_useEvalPlan) - { - planSlot = EvalPlanQualNext(estate); - if (TupIsNull(planSlot)) - planSlot = ExecProcNode(planstate); - } - else - planSlot = ExecProcNode(planstate); - - /* - * if the tuple is null, then we assume there is nothing more to - * process so we just return null... - */ - if (TupIsNull(planSlot)) - { - result = NULL; - break; - } - slot = planSlot; - - /* - * if we have a junk filter, then project a new tuple with the junk - * removed. - * - * Store this new "clean" tuple in the junkfilter's resultSlot. - * (Formerly, we stored it back over the "dirty" tuple, which is WRONG - * because that tuple slot has the wrong descriptor.) - * - * Also, extract all the junk information we need. - */ - if ((junkfilter = estate->es_junkFilter) != NULL) - { - Datum datum; - bool isNull; - - /* - * extract the 'ctid' junk attribute. - */ - if (operation == CMD_UPDATE || operation == CMD_DELETE) - { - datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo, - &isNull); - /* shouldn't ever get a null result... */ - if (isNull) - elog(ERROR, "ctid is NULL"); - - tupleid = (ItemPointer) DatumGetPointer(datum); - tuple_ctid = *tupleid; /* make sure we don't free the ctid!! */ - tupleid = &tuple_ctid; - } - - /* - * Process any FOR UPDATE or FOR SHARE locking requested. - */ - else if (estate->es_rowMarks != NIL) - { - ListCell *l; - - lmark: ; - foreach(l, estate->es_rowMarks) - { - ExecRowMark *erm = lfirst(l); - HeapTupleData tuple; - Buffer buffer; - ItemPointerData update_ctid; - TransactionId update_xmax; - TupleTableSlot *newSlot; - LockTupleMode lockmode; - HTSU_Result test; - - datum = ExecGetJunkAttribute(slot, - erm->ctidAttNo, - &isNull); - /* shouldn't ever get a null result... */ - if (isNull) - elog(ERROR, "ctid is NULL"); - - tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); - - if (erm->forUpdate) - lockmode = LockTupleExclusive; - else - lockmode = LockTupleShared; - - test = heap_lock_tuple(erm->relation, &tuple, &buffer, - &update_ctid, &update_xmax, - estate->es_snapshot->curcid, - lockmode, erm->noWait); - ReleaseBuffer(buffer); - switch (test) - { - case HeapTupleSelfUpdated: - /* treat it as deleted; do not process */ - goto lnext; - - case HeapTupleMayBeUpdated: - break; - - case HeapTupleUpdated: - if (IsXactIsoLevelSerializable) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("could not serialize access due to concurrent update"))); - if (!ItemPointerEquals(&update_ctid, - &tuple.t_self)) - { - /* updated, so look at updated version */ - newSlot = EvalPlanQual(estate, - erm->rti, - &update_ctid, - update_xmax, - estate->es_snapshot->curcid); - if (!TupIsNull(newSlot)) - { - slot = planSlot = newSlot; - estate->es_useEvalPlan = true; - goto lmark; - } - } - - /* - * if tuple was deleted or PlanQual failed for - * updated tuple - we must not return this tuple! - */ - goto lnext; - - default: - elog(ERROR, "unrecognized heap_lock_tuple status: %u", - test); - return NULL; - } - } - } - - /* - * Create a new "clean" tuple with all junk attributes removed. We - * don't need to do this for DELETE, however (there will in fact - * be no non-junk attributes in a DELETE!) - */ - if (operation != CMD_DELETE) - slot = ExecFilterJunk(junkfilter, slot); - } - - /* - * now that we have a tuple, do the appropriate thing with it.. either - * return it to the user, add it to a relation someplace, delete it - * from a relation, or modify some of its attributes. - */ - switch (operation) - { - case CMD_SELECT: - ExecSelect(slot, dest, estate); - result = slot; - break; - - case CMD_INSERT: - ExecInsert(slot, tupleid, planSlot, dest, estate); - result = NULL; - break; - - case CMD_DELETE: - ExecDelete(tupleid, planSlot, dest, estate); - result = NULL; - break; - - case CMD_UPDATE: - ExecUpdate(slot, tupleid, planSlot, dest, estate); - result = NULL; - break; - - default: - elog(ERROR, "unrecognized operation code: %d", - (int) operation); - result = NULL; - break; - } - - /* - * check our tuple count.. if we've processed the proper number then - * quit, else loop again and process more tuples. Zero numberTuples - * means no limit. - */ - current_tuple_count++; - if (numberTuples && numberTuples == current_tuple_count) - break; - } - - /* - * Process AFTER EACH STATEMENT triggers - */ - switch (operation) - { - case CMD_UPDATE: - ExecASUpdateTriggers(estate, estate->es_result_relation_info); - break; - case CMD_DELETE: - ExecASDeleteTriggers(estate, estate->es_result_relation_info); - break; - case CMD_INSERT: - ExecASInsertTriggers(estate, estate->es_result_relation_info); - break; - default: - /* do nothing */ - break; - } - - /* - * here, result is either a slot containing a tuple in the case of a - * SELECT or NULL otherwise. - */ - return result; -} - -/* ---------------------------------------------------------------- - * ExecSelect - * - * SELECTs are easy.. we just pass the tuple to the appropriate - * output function. - * ---------------------------------------------------------------- - */ -static void -ExecSelect(TupleTableSlot *slot, - DestReceiver *dest, - EState *estate) -{ - (*dest->receiveSlot) (slot, dest); - IncrRetrieved(); - (estate->es_processed)++; -} - -/* ---------------------------------------------------------------- - * ExecInsert - * - * INSERTs are trickier.. we have to insert the tuple into - * the base relation and insert appropriate tuples into the - * index relations. - * ---------------------------------------------------------------- - */ -static void -ExecInsert(TupleTableSlot *slot, - ItemPointer tupleid, - TupleTableSlot *planSlot, - DestReceiver *dest, - EState *estate) -{ - HeapTuple tuple; - ResultRelInfo *resultRelInfo; - Relation resultRelationDesc; - Oid newId; - - /* - * get the heap tuple out of the tuple table slot, making sure we have a - * writable copy - */ - tuple = ExecMaterializeSlot(slot); - - /* - * get information on the (current) result relation - */ - resultRelInfo = estate->es_result_relation_info; - resultRelationDesc = resultRelInfo->ri_RelationDesc; - - /* BEFORE ROW INSERT Triggers */ - if (resultRelInfo->ri_TrigDesc && - resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0) - { - HeapTuple newtuple; - - newtuple = ExecBRInsertTriggers(estate, resultRelInfo, tuple); - - 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); - ExecStoreTuple(newtuple, newslot, InvalidBuffer, false); - slot = newslot; - tuple = newtuple; - } - } - - /* - * Check the constraints of the tuple - */ - if (resultRelationDesc->rd_att->constr) - ExecConstraints(resultRelInfo, slot, estate); - - /* - * insert the tuple - * - * Note: heap_insert returns the tid (location) of the new tuple in the - * t_self field. - */ - newId = heap_insert(resultRelationDesc, tuple, - estate->es_snapshot->curcid, - true, true); - - IncrAppended(); - (estate->es_processed)++; - estate->es_lastoid = newId; - setLastTid(&(tuple->t_self)); - - /* - * insert index entries for tuple - */ - if (resultRelInfo->ri_NumIndices > 0) - ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false); - - /* AFTER ROW INSERT Triggers */ - ExecARInsertTriggers(estate, resultRelInfo, tuple); - - /* Process RETURNING if present */ - if (resultRelInfo->ri_projectReturning) - ExecProcessReturning(resultRelInfo->ri_projectReturning, - slot, planSlot, dest); -} - -/* ---------------------------------------------------------------- - * ExecDelete - * - * DELETE is like UPDATE, except that we delete the tuple and no - * index modifications are needed - * ---------------------------------------------------------------- - */ -static void -ExecDelete(ItemPointer tupleid, - TupleTableSlot *planSlot, - DestReceiver *dest, - EState *estate) -{ - ResultRelInfo *resultRelInfo; - Relation resultRelationDesc; - HTSU_Result result; - ItemPointerData update_ctid; - TransactionId update_xmax; - - /* - * get information on the (current) result relation - */ - resultRelInfo = estate->es_result_relation_info; - resultRelationDesc = resultRelInfo->ri_RelationDesc; - - /* BEFORE ROW DELETE Triggers */ - if (resultRelInfo->ri_TrigDesc && - resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_DELETE] > 0) - { - bool dodelete; - - dodelete = ExecBRDeleteTriggers(estate, resultRelInfo, tupleid, - estate->es_snapshot->curcid); - - if (!dodelete) /* "do nothing" */ - return; - } - - /* * delete the tuple - * - * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that - * the row to be deleted 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. */ +// A LAST COMMON LINE -ldelete:; - result = heap_delete(resultRelationDesc, tupleid, - &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; - goto ldelete; - } - } - /* tuple already deleted; nothing to do */ - return; - - default: - elog(ERROR, "unrecognized heap_delete status: %u", result); - return; - } - - IncrDeleted(); - (estate->es_processed)++; - - /* - * Note: Normally one would think that we have to delete index tuples - * associated with the heap tuple now... - * - * ... but in POSTGRES, we have no need to do this because VACUUM will - * take care of it later. We can't delete index tuples immediately - * anyway, since the tuple is still visible to other transactions. - */ - - /* AFTER ROW DELETE Triggers */ - ExecARDeleteTriggers(estate, resultRelInfo, tupleid); - - /* Process RETURNING if present */ - if (resultRelInfo->ri_projectReturning) - { - /* - * We have to put the target tuple into a slot, which means first we - * gotta fetch it. We can use the trigger tuple slot. - */ - TupleTableSlot *slot = estate->es_trig_tuple_slot; - HeapTupleData deltuple; - Buffer delbuffer; - - deltuple.t_self = *tupleid; - if (!heap_fetch(resultRelationDesc, SnapshotAny, - &deltuple, &delbuffer, false, NULL)) - elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING"); - - if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc)) - ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc)); - ExecStoreTuple(&deltuple, slot, InvalidBuffer, false); - - ExecProcessReturning(resultRelInfo->ri_projectReturning, - slot, planSlot, dest); - - ExecClearTuple(slot); - ReleaseBuffer(delbuffer); - } -} ============================================================ --- tests/test_a_merge_8/right e2c3c1e5c4db85c53581122fad7cf83d835635e6 +++ tests/test_a_merge_8/right dd8a37d2beb981759342a31933ba131ab58f6dd3 @@ -1,1127 +1,18 @@ -/* ---------------------------------------------------------------- - * InitPlan - * - * Initializes the query plan: open files, allocate storage - * and start up the rule manager - * ---------------------------------------------------------------- - */ -static void -InitPlan(QueryDesc *queryDesc, int eflags) -{ - CmdType operation = queryDesc->operation; - Query *parseTree = queryDesc->parsetree; - Plan *plan = queryDesc->plantree; - EState *estate = queryDesc->estate; - PlanState *planstate; - List *rangeTable; - TupleDesc tupType; - ListCell *l; +// AN INITIAL COMMON LINE +// ANOTHER COMMON LINE - /* - * Do permissions checks. It's sufficient to examine the query's top - * rangetable here --- subplan RTEs will be checked during - * ExecInitSubPlan(). - */ - ExecCheckRTPerms(parseTree->rtable); +// SOME STUFF FROM THE RIGHT /* - * get information from query descriptor - */ - rangeTable = parseTree->rtable; - - /* - * initialize the node's execution state - */ - estate->es_range_table = rangeTable; - - /* - * if there is a result relation, initialize result relation stuff - */ - if (parseTree->resultRelation) - { - List *resultRelations = parseTree->resultRelations; - int numResultRelations; - ResultRelInfo *resultRelInfos; - - if (resultRelations != NIL) - { - /* - * Multiple result relations (due to inheritance) - * parseTree->resultRelations identifies them all - */ - ResultRelInfo *resultRelInfo; - - numResultRelations = list_length(resultRelations); - resultRelInfos = (ResultRelInfo *) - palloc(numResultRelations * sizeof(ResultRelInfo)); - resultRelInfo = resultRelInfos; - foreach(l, resultRelations) - { - initResultRelInfo(resultRelInfo, - lfirst_int(l), - rangeTable, - operation, - estate->es_instrument); - resultRelInfo++; - } - } - else - { - /* - * Single result relation identified by parseTree->resultRelation - */ - numResultRelations = 1; - resultRelInfos = (ResultRelInfo *) palloc(sizeof(ResultRelInfo)); - initResultRelInfo(resultRelInfos, - parseTree->resultRelation, - rangeTable, - operation, - estate->es_instrument); - } - - estate->es_result_relations = resultRelInfos; - estate->es_num_result_relations = numResultRelations; - /* Initialize to first or only result rel */ - estate->es_result_relation_info = resultRelInfos; - } - else - { - /* - * if no result relation, then set state appropriately - */ - estate->es_result_relations = NULL; - estate->es_num_result_relations = 0; - estate->es_result_relation_info = NULL; - } - - /* - * Detect whether we're doing SELECT INTO. If so, set the es_into_oids - * flag appropriately so that the plan tree will be initialized with the - * correct tuple descriptors. (Other SELECT INTO stuff comes later.) - */ - estate->es_select_into = false; - if (operation == CMD_SELECT && parseTree->into != NULL) - { - estate->es_select_into = true; - estate->es_into_oids = interpretOidsOption(parseTree->intoOptions); - } - - /* - * Have to lock relations selected FOR UPDATE/FOR SHARE before we - * initialize the plan tree, else we'd be doing a lock upgrade. - * While we are at it, build the ExecRowMark list. - */ - estate->es_rowMarks = NIL; - foreach(l, parseTree->rowMarks) - { - RowMarkClause *rc = (RowMarkClause *) lfirst(l); - Oid relid = getrelid(rc->rti, rangeTable); - Relation relation; - ExecRowMark *erm; - - relation = heap_open(relid, RowShareLock); - erm = (ExecRowMark *) palloc(sizeof(ExecRowMark)); - erm->relation = relation; - erm->rti = rc->rti; - erm->forUpdate = rc->forUpdate; - erm->noWait = rc->noWait; - /* We'll set up ctidAttno below */ - erm->ctidAttNo = InvalidAttrNumber; - estate->es_rowMarks = lappend(estate->es_rowMarks, erm); - } - - /* - * initialize the executor "tuple" table. We need slots for all the plan - * nodes, plus possibly output slots for the junkfilter(s). At this point - * we aren't sure if we need junkfilters, so just add slots for them - * unconditionally. Also, if it's not a SELECT, set up a slot for use for - * trigger output tuples. - */ - { - int nSlots = ExecCountSlotsNode(plan); - - if (parseTree->resultRelations != NIL) - nSlots += list_length(parseTree->resultRelations); - else - nSlots += 1; - if (operation != CMD_SELECT) - nSlots++; /* for es_trig_tuple_slot */ - if (parseTree->returningLists) - nSlots++; /* for RETURNING projection */ - - estate->es_tupleTable = ExecCreateTupleTable(nSlots); - - if (operation != CMD_SELECT) - estate->es_trig_tuple_slot = - ExecAllocTableSlot(estate->es_tupleTable); - } - - /* mark EvalPlanQual not active */ - estate->es_topPlan = plan; - estate->es_evalPlanQual = NULL; - estate->es_evTupleNull = NULL; - estate->es_evTuple = NULL; - estate->es_useEvalPlan = false; - - /* - * initialize the private state information for all the nodes in the query - * tree. This opens files, allocates storage and leaves us ready to start - * processing tuples. - */ - planstate = ExecInitNode(plan, estate, eflags); - - /* - * Get the tuple descriptor describing the type of tuples to return. (this - * is especially important if we are creating a relation with "SELECT - * INTO") - */ - tupType = ExecGetResultType(planstate); - - /* - * Initialize the junk filter if needed. SELECT and INSERT queries need a - * filter if there are any junk attrs in the tlist. INSERT and SELECT - * INTO also need a filter if the plan may return raw disk tuples (else - * heap_insert will be scribbling on the source relation!). UPDATE and - * DELETE always need a filter, since there's always a junk 'ctid' - * attribute present --- no need to look first. - */ - { - bool junk_filter_needed = false; - ListCell *tlist; - - switch (operation) - { - case CMD_SELECT: - case CMD_INSERT: - foreach(tlist, plan->targetlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tlist); - - if (tle->resjunk) - { - junk_filter_needed = true; - break; - } - } - if (!junk_filter_needed && - (operation == CMD_INSERT || estate->es_select_into) && - ExecMayReturnRawTuples(planstate)) - junk_filter_needed = true; - break; - case CMD_UPDATE: - case CMD_DELETE: - junk_filter_needed = true; - break; - default: - break; - } - - if (junk_filter_needed) - { - /* - * If there are multiple result relations, each one needs its own - * junk filter. Note this is only possible for UPDATE/DELETE, so - * we can't be fooled by some needing a filter and some not. - */ - if (parseTree->resultRelations != NIL) - { - PlanState **appendplans; - int as_nplans; - ResultRelInfo *resultRelInfo; - int i; - - /* Top plan had better be an Append here. */ - Assert(IsA(plan, Append)); - Assert(((Append *) plan)->isTarget); - Assert(IsA(planstate, AppendState)); - appendplans = ((AppendState *) planstate)->appendplans; - as_nplans = ((AppendState *) planstate)->as_nplans; - Assert(as_nplans == estate->es_num_result_relations); - resultRelInfo = estate->es_result_relations; - for (i = 0; i < as_nplans; i++) - { - PlanState *subplan = appendplans[i]; - JunkFilter *j; - - j = ExecInitJunkFilter(subplan->plan->targetlist, - resultRelInfo->ri_RelationDesc->rd_att->tdhasoid, - ExecAllocTableSlot(estate->es_tupleTable)); - /* - * Since it must be UPDATE/DELETE, there had better be - * a "ctid" junk attribute in the tlist ... but ctid could - * be at a different resno for each result relation. - * We look up the ctid resnos now and save them in the - * junkfilters. - */ - j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); - if (!AttributeNumberIsValid(j->jf_junkAttNo)) - elog(ERROR, "could not find junk ctid column"); - resultRelInfo->ri_junkFilter = j; - resultRelInfo++; - } - - /* - * Set active junkfilter too; at this point ExecInitAppend has - * already selected an active result relation... - */ - estate->es_junkFilter = - estate->es_result_relation_info->ri_junkFilter; - } - else - { - /* Normal case with just one JunkFilter */ - JunkFilter *j; - - j = ExecInitJunkFilter(planstate->plan->targetlist, - tupType->tdhasoid, - ExecAllocTableSlot(estate->es_tupleTable)); - estate->es_junkFilter = j; - if (estate->es_result_relation_info) - estate->es_result_relation_info->ri_junkFilter = j; - - if (operation == CMD_SELECT) - { - /* For SELECT, want to return the cleaned tuple type */ - tupType = j->jf_cleanTupType; - /* For SELECT FOR UPDATE/SHARE, find the ctid attrs now */ - foreach(l, estate->es_rowMarks) - { - ExecRowMark *erm = (ExecRowMark *) lfirst(l); - char resname[32]; - - snprintf(resname, sizeof(resname), "ctid%u", erm->rti); - erm->ctidAttNo = ExecFindJunkAttribute(j, resname); - if (!AttributeNumberIsValid(erm->ctidAttNo)) - elog(ERROR, "could not find junk \"%s\" column", - resname); - } - } - else if (operation == CMD_UPDATE || operation == CMD_DELETE) - { - /* For UPDATE/DELETE, find the ctid junk attr now */ - j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); - if (!AttributeNumberIsValid(j->jf_junkAttNo)) - elog(ERROR, "could not find junk ctid column"); - } - } - } - else - estate->es_junkFilter = NULL; - } - - /* - * Initialize RETURNING projections if needed. - */ - if (parseTree->returningLists) - { - TupleTableSlot *slot; - ExprContext *econtext; - ResultRelInfo *resultRelInfo; - - /* - * We set QueryDesc.tupDesc to be the RETURNING rowtype in this case. - * We assume all the sublists will generate the same output tupdesc. - */ - tupType = ExecTypeFromTL((List *) linitial(parseTree->returningLists), - false); - - /* Set up a slot for the output of the RETURNING projection(s) */ - slot = ExecAllocTableSlot(estate->es_tupleTable); - ExecSetSlotDescriptor(slot, tupType); - /* Need an econtext too */ - econtext = CreateExprContext(estate); - - /* - * Build a projection for each result rel. Note that any SubPlans in - * the RETURNING lists get attached to the topmost plan node. - */ - Assert(list_length(parseTree->returningLists) == estate->es_num_result_relations); - resultRelInfo = estate->es_result_relations; - foreach(l, parseTree->returningLists) - { - List *rlist = (List *) lfirst(l); - List *rliststate; - - rliststate = (List *) ExecInitExpr((Expr *) rlist, planstate); - resultRelInfo->ri_projectReturning = - ExecBuildProjectionInfo(rliststate, econtext, slot); - resultRelInfo++; - } - - /* - * Because we already ran ExecInitNode() for the top plan node, any - * subplans we just attached to it won't have been initialized; so we - * have to do it here. (Ugly, but the alternatives seem worse.) - */ - foreach(l, planstate->subPlan) - { - SubPlanState *sstate = (SubPlanState *) lfirst(l); - - Assert(IsA(sstate, SubPlanState)); - if (sstate->planstate == NULL) /* already inited? */ - ExecInitSubPlan(sstate, estate, eflags); - } - } - - queryDesc->tupDesc = tupType; - queryDesc->planstate = planstate; - - /* - * If doing SELECT INTO, initialize the "into" relation. We must wait - * till now so we have the "clean" result tuple type to create the new - * table from. - * - * If EXPLAIN, skip creating the "into" relation. - */ - if (estate->es_select_into && !(eflags & EXEC_FLAG_EXPLAIN_ONLY)) - OpenIntoRel(queryDesc); -} - -/* - * Initialize ResultRelInfo data for one result relation - */ -static void -initResultRelInfo(ResultRelInfo *resultRelInfo, - Index resultRelationIndex, - List *rangeTable, - CmdType operation, - bool doInstrument) -{ - Oid resultRelationOid; - Relation resultRelationDesc; - - resultRelationOid = getrelid(resultRelationIndex, rangeTable); - resultRelationDesc = heap_open(resultRelationOid, RowExclusiveLock); - - switch (resultRelationDesc->rd_rel->relkind) - { - case RELKIND_SEQUENCE: - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change sequence \"%s\"", - RelationGetRelationName(resultRelationDesc)))); - break; - case RELKIND_TOASTVALUE: - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change TOAST relation \"%s\"", - RelationGetRelationName(resultRelationDesc)))); - break; - case RELKIND_VIEW: - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change view \"%s\"", - RelationGetRelationName(resultRelationDesc)))); - break; - } - - MemSet(resultRelInfo, 0, sizeof(ResultRelInfo)); - resultRelInfo->type = T_ResultRelInfo; - resultRelInfo->ri_RangeTableIndex = resultRelationIndex; - resultRelInfo->ri_RelationDesc = resultRelationDesc; - resultRelInfo->ri_NumIndices = 0; - resultRelInfo->ri_IndexRelationDescs = NULL; - resultRelInfo->ri_IndexRelationInfo = NULL; - /* make a copy so as not to depend on relcache info not changing... */ - resultRelInfo->ri_TrigDesc = CopyTriggerDesc(resultRelationDesc->trigdesc); - if (resultRelInfo->ri_TrigDesc) - { - int n = resultRelInfo->ri_TrigDesc->numtriggers; - - resultRelInfo->ri_TrigFunctions = (FmgrInfo *) - palloc0(n * sizeof(FmgrInfo)); - if (doInstrument) - resultRelInfo->ri_TrigInstrument = InstrAlloc(n); - else - resultRelInfo->ri_TrigInstrument = NULL; - } - else - { - resultRelInfo->ri_TrigFunctions = NULL; - resultRelInfo->ri_TrigInstrument = NULL; - } - resultRelInfo->ri_ConstraintExprs = NULL; - resultRelInfo->ri_junkFilter = NULL; - resultRelInfo->ri_projectReturning = NULL; - - /* - * If there are indices on the result relation, open them and save - * descriptors in the result relation info, so that we can add new index - * entries for the tuples we add/update. We need not do this for a - * DELETE, however, since deletion doesn't affect indexes. - */ - if (resultRelationDesc->rd_rel->relhasindex && - operation != CMD_DELETE) - ExecOpenIndices(resultRelInfo); -} - -/* - * ExecContextForcesOids - * - * This is pretty grotty: when doing INSERT, UPDATE, or SELECT INTO, - * we need to ensure that result tuples have space for an OID iff they are - * going to be stored into a relation that has OIDs. In other contexts - * we are free to choose whether to leave space for OIDs in result tuples - * (we generally don't want to, but we do if a physical-tlist optimization - * is possible). This routine checks the plan context and returns TRUE if the - * choice is forced, FALSE if the choice is not forced. In the TRUE case, - * *hasoids is set to the required value. - * - * One reason this is ugly is that all plan nodes in the plan tree will emit - * tuples with space for an OID, though we really only need the topmost node - * to do so. However, node types like Sort don't project new tuples but just - * return their inputs, and in those cases the requirement propagates down - * to the input node. Eventually we might make this code smart enough to - * recognize how far down the requirement really goes, but for now we just - * make all plan nodes do the same thing if the top level forces the choice. - * - * We assume that estate->es_result_relation_info is already set up to - * describe the target relation. Note that in an UPDATE that spans an - * inheritance tree, some of the target relations may have OIDs and some not. - * We have to make the decisions on a per-relation basis as we initialize - * each of the child plans of the topmost Append plan. - * - * SELECT INTO is even uglier, because we don't have the INTO relation's - * descriptor available when this code runs; we have to look aside at a - * flag set by InitPlan(). - */ -bool -ExecContextForcesOids(PlanState *planstate, bool *hasoids) -{ - if (planstate->state->es_select_into) - { - *hasoids = planstate->state->es_into_oids; - return true; - } - else - { - ResultRelInfo *ri = planstate->state->es_result_relation_info; - - if (ri != NULL) - { - Relation rel = ri->ri_RelationDesc; - - if (rel != NULL) - { - *hasoids = rel->rd_rel->relhasoids; - return true; - } - } - } - - return false; -} - -/* ---------------------------------------------------------------- - * ExecEndPlan - * - * Cleans up the query plan -- closes files and frees up storage - * - * NOTE: we are no longer very worried about freeing storage per se - * in this code; FreeExecutorState should be guaranteed to release all - * memory that needs to be released. What we are worried about doing - * is closing relations and dropping buffer pins. Thus, for example, - * tuple tables must be cleared or dropped to ensure pins are released. - * ---------------------------------------------------------------- - */ -void -ExecEndPlan(PlanState *planstate, EState *estate) -{ - ResultRelInfo *resultRelInfo; - int i; - ListCell *l; - - /* - * shut down any PlanQual processing we were doing - */ - if (estate->es_evalPlanQual != NULL) - EndEvalPlanQual(estate); - - /* - * shut down the node-type-specific query processing - */ - ExecEndNode(planstate); - - /* - * destroy the executor "tuple" table. - */ - ExecDropTupleTable(estate->es_tupleTable, true); - estate->es_tupleTable = NULL; - - /* - * close the result relation(s) if any, but hold locks until xact commit. - */ - resultRelInfo = estate->es_result_relations; - for (i = estate->es_num_result_relations; i > 0; i--) - { - /* Close indices and then the relation itself */ - ExecCloseIndices(resultRelInfo); - heap_close(resultRelInfo->ri_RelationDesc, NoLock); - resultRelInfo++; - } - - /* - * close any relations selected FOR UPDATE/FOR SHARE, again keeping locks - */ - foreach(l, estate->es_rowMarks) - { - ExecRowMark *erm = lfirst(l); - - heap_close(erm->relation, NoLock); - } -} - -/* ---------------------------------------------------------------- - * ExecutePlan - * - * processes the query plan to retrieve 'numberTuples' tuples in the - * direction specified. - * - * Retrieves all tuples if numberTuples is 0 - * - * result is either a slot containing the last tuple in the case - * of a SELECT or NULL otherwise. - * - * Note: the ctid attribute is a 'junk' attribute that is removed before the - * user can see it - * ---------------------------------------------------------------- - */ -static TupleTableSlot * -ExecutePlan(EState *estate, - PlanState *planstate, - CmdType operation, - long numberTuples, - ScanDirection direction, - DestReceiver *dest) -{ - JunkFilter *junkfilter; - TupleTableSlot *planSlot; - TupleTableSlot *slot; - ItemPointer tupleid = NULL; - ItemPointerData tuple_ctid; - long current_tuple_count; - TupleTableSlot *result; - - /* - * initialize local variables - */ - current_tuple_count = 0; - result = NULL; - - /* - * Set the direction. - */ - estate->es_direction = direction; - - /* - * Process BEFORE EACH STATEMENT triggers - */ - switch (operation) - { - case CMD_UPDATE: - ExecBSUpdateTriggers(estate, estate->es_result_relation_info); - break; - case CMD_DELETE: - ExecBSDeleteTriggers(estate, estate->es_result_relation_info); - break; - case CMD_INSERT: - ExecBSInsertTriggers(estate, estate->es_result_relation_info); - break; - default: - /* do nothing */ - break; - } - - /* - * Loop until we've processed the proper number of tuples from the plan. - */ - - for (;;) - { - /* Reset the per-output-tuple exprcontext */ - ResetPerTupleExprContext(estate); - - /* - * Execute the plan and obtain a tuple - */ -lnext: ; - if (estate->es_useEvalPlan) - { - planSlot = EvalPlanQualNext(estate); - if (TupIsNull(planSlot)) - planSlot = ExecProcNode(planstate); - } - else - planSlot = ExecProcNode(planstate); - - /* - * if the tuple is null, then we assume there is nothing more to - * process so we just return null... - */ - if (TupIsNull(planSlot)) - { - result = NULL; - break; - } - slot = planSlot; - - /* - * if we have a junk filter, then project a new tuple with the junk - * removed. - * - * Store this new "clean" tuple in the junkfilter's resultSlot. - * (Formerly, we stored it back over the "dirty" tuple, which is WRONG - * because that tuple slot has the wrong descriptor.) - * - * Also, extract all the junk information we need. - */ - if ((junkfilter = estate->es_junkFilter) != NULL) - { - Datum datum; - bool isNull; - - /* - * extract the 'ctid' junk attribute. - */ - if (operation == CMD_UPDATE || operation == CMD_DELETE) - { - datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo, - &isNull); - /* shouldn't ever get a null result... */ - if (isNull) - elog(ERROR, "ctid is NULL"); - - tupleid = (ItemPointer) DatumGetPointer(datum); - tuple_ctid = *tupleid; /* make sure we don't free the ctid!! */ - tupleid = &tuple_ctid; - } - - /* - * Process any FOR UPDATE or FOR SHARE locking requested. - */ - else if (estate->es_rowMarks != NIL) - { - ListCell *l; - - lmark: ; - foreach(l, estate->es_rowMarks) - { - ExecRowMark *erm = lfirst(l); - HeapTupleData tuple; - Buffer buffer; - ItemPointerData update_ctid; - TransactionId update_xmax; - TupleTableSlot *newSlot; - LockTupleMode lockmode; - HTSU_Result test; - - datum = ExecGetJunkAttribute(slot, - erm->ctidAttNo, - &isNull); - /* shouldn't ever get a null result... */ - if (isNull) - elog(ERROR, "ctid is NULL"); - - tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); - - if (erm->forUpdate) - lockmode = LockTupleExclusive; - else - lockmode = LockTupleShared; - - test = heap_lock_tuple(erm->relation, &tuple, &buffer, - &update_ctid, &update_xmax, - estate->es_snapshot->curcid, - lockmode, erm->noWait); - ReleaseBuffer(buffer); - switch (test) - { - case HeapTupleSelfUpdated: - /* treat it as deleted; do not process */ - goto lnext; - - case HeapTupleMayBeUpdated: - break; - - case HeapTupleUpdated: - if (IsXactIsoLevelSerializable) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("could not serialize access due to concurrent update"))); - if (!ItemPointerEquals(&update_ctid, - &tuple.t_self)) - { - /* updated, so look at updated version */ - newSlot = EvalPlanQual(estate, - erm->rti, - &update_ctid, - update_xmax, - estate->es_snapshot->curcid); - if (!TupIsNull(newSlot)) - { - slot = planSlot = newSlot; - estate->es_useEvalPlan = true; - goto lmark; - } - } - - /* - * if tuple was deleted or PlanQual failed for - * updated tuple - we must not return this tuple! - */ - goto lnext; - - default: - elog(ERROR, "unrecognized heap_lock_tuple status: %u", - test); - return NULL; - } - } - } - - /* - * Create a new "clean" tuple with all junk attributes removed. We - * don't need to do this for DELETE, however (there will in fact - * be no non-junk attributes in a DELETE!) - */ - if (operation != CMD_DELETE) - slot = ExecFilterJunk(junkfilter, slot); - } - - /* - * now that we have a tuple, do the appropriate thing with it.. either - * return it to the user, add it to a relation someplace, delete it - * from a relation, or modify some of its attributes. - */ - switch (operation) - { - case CMD_SELECT: - ExecSelect(slot, dest, estate); - result = slot; - break; - - case CMD_INSERT: - ExecInsert(slot, tupleid, planSlot, dest, estate); - result = NULL; - break; - - case CMD_DELETE: - ExecDelete(tupleid, planSlot, dest, estate); - result = NULL; - break; - - case CMD_UPDATE: - ExecUpdate(slot, tupleid, planSlot, dest, estate); - result = NULL; - break; - - default: - elog(ERROR, "unrecognized operation code: %d", - (int) operation); - result = NULL; - break; - } - - /* - * check our tuple count.. if we've processed the proper number then - * quit, else loop again and process more tuples. Zero numberTuples - * means no limit. - */ - current_tuple_count++; - if (numberTuples && numberTuples == current_tuple_count) - break; - } - - /* - * Process AFTER EACH STATEMENT triggers - */ - switch (operation) - { - case CMD_UPDATE: - ExecASUpdateTriggers(estate, estate->es_result_relation_info); - break; - case CMD_DELETE: - ExecASDeleteTriggers(estate, estate->es_result_relation_info); - break; - case CMD_INSERT: - ExecASInsertTriggers(estate, estate->es_result_relation_info); - break; - default: - /* do nothing */ - break; - } - - /* - * here, result is either a slot containing a tuple in the case of a - * SELECT or NULL otherwise. - */ - return result; -} - -/* ---------------------------------------------------------------- - * ExecSelect - * - * SELECTs are easy.. we just pass the tuple to the appropriate - * output function. - * ---------------------------------------------------------------- - */ -static void -ExecSelect(TupleTableSlot *slot, - DestReceiver *dest, - EState *estate) -{ - (*dest->receiveSlot) (slot, dest); - IncrRetrieved(); - (estate->es_processed)++; -} - -/* ---------------------------------------------------------------- - * ExecInsert - * - * INSERTs are trickier.. we have to insert the tuple into - * the base relation and insert appropriate tuples into the - * index relations. - * ---------------------------------------------------------------- - */ -static void -ExecInsert(TupleTableSlot *slot, - ItemPointer tupleid, - TupleTableSlot *planSlot, - DestReceiver *dest, - EState *estate) -{ - HeapTuple tuple; - ResultRelInfo *resultRelInfo; - Relation resultRelationDesc; - Oid newId; - - /* - * get the heap tuple out of the tuple table slot, making sure we have a - * writable copy - */ - tuple = ExecMaterializeSlot(slot); - - /* - * get information on the (current) result relation - */ - resultRelInfo = estate->es_result_relation_info; - resultRelationDesc = resultRelInfo->ri_RelationDesc; - - /* BEFORE ROW INSERT Triggers */ - if (resultRelInfo->ri_TrigDesc && - resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0) - { - HeapTuple newtuple; - - newtuple = ExecBRInsertTriggers(estate, resultRelInfo, tuple); - - 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); - ExecStoreTuple(newtuple, newslot, InvalidBuffer, false); - slot = newslot; - tuple = newtuple; - } - } - - /* - * Check the constraints of the tuple - */ - if (resultRelationDesc->rd_att->constr) - ExecConstraints(resultRelInfo, slot, estate); - - /* - * insert the tuple - * - * Note: heap_insert returns the tid (location) of the new tuple in the - * t_self field. - */ - newId = heap_insert(resultRelationDesc, tuple, - estate->es_snapshot->curcid, - true, true); - - IncrAppended(); - (estate->es_processed)++; - estate->es_lastoid = newId; - setLastTid(&(tuple->t_self)); - - /* - * insert index entries for tuple - */ - if (resultRelInfo->ri_NumIndices > 0) - ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false); - - /* AFTER ROW INSERT Triggers */ - ExecARInsertTriggers(estate, resultRelInfo, tuple); - - /* Process RETURNING if present */ - if (resultRelInfo->ri_projectReturning) - ExecProcessReturning(resultRelInfo->ri_projectReturning, - slot, planSlot, dest); -} - -/* ---------------------------------------------------------------- - * ExecDelete - * - * DELETE is like UPDATE, except that we delete the tuple and no - * index modifications are needed - * ---------------------------------------------------------------- - */ -static void -ExecDelete(ItemPointer tupleid, - TupleTableSlot *planSlot, - DestReceiver *dest, - EState *estate) -{ - ResultRelInfo *resultRelInfo; - Relation resultRelationDesc; - HTSU_Result result; - ItemPointerData update_ctid; - TransactionId update_xmax; - - /* - * get information on the (current) result relation - */ - resultRelInfo = estate->es_result_relation_info; - resultRelationDesc = resultRelInfo->ri_RelationDesc; - - /* BEFORE ROW DELETE Triggers */ - if (resultRelInfo->ri_TrigDesc && - resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_DELETE] > 0) - { - bool dodelete; - - dodelete = ExecBRDeleteTriggers(estate, resultRelInfo, tupleid, - estate->es_snapshot->curcid); - - if (!dodelete) /* "do nothing" */ - return; - } - -#ifdef REPLICATION - /* initialize the TxnToAbort return value */ - TxnToAbort = InvalidTransactionId; - - /* * Add the tuple info to the WriteSet. */ if ( txn_type == REPLICATED_LOCAL ) { - Oid resultRelationOid; - TupleCollection *tcoll; - - tcoll = &(((QueryInfo *) CurrentWriteSet->currQuery)->tcoll); - resultRelationOid = RelationGetRelid(resultRelationDesc); - if (resultRelationOid == tcoll->rel->relOid) - WriteSetCollectTuple(tupleid, planSlot, - CurrentWriteSet->currQuery, - estate->es_snapshot); } -#endif +// END OF STUFF FROM THE RIGHT /* * delete the tuple - * - * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that - * the row to be deleted 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. */ +// AN ADDITIONAL LINE FROM THE RIGHT +// A LAST COMMON LINE -ldelete:; - result = heap_delete(resultRelationDesc, tupleid, - &update_ctid, &update_xmax, - estate->es_snapshot->curcid, - estate->es_crosscheck_snapshot, - true /* wait for commit */ ); - //THIS LINE IS NEEDED TO TRIGGER THE BUG - 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; - goto ldelete; - } - } - /* tuple already deleted; nothing to do */ - return; - - default: - elog(ERROR, "unrecognized heap_delete status: %u", result); - return; - } - - IncrDeleted(); - (estate->es_processed)++; - - /* - * Note: Normally one would think that we have to delete index tuples - * associated with the heap tuple now... - * - * ... but in POSTGRES, we have no need to do this because VACUUM will - * take care of it later. We can't delete index tuples immediately - * anyway, since the tuple is still visible to other transactions. - */ - - /* AFTER ROW DELETE Triggers */ - ExecARDeleteTriggers(estate, resultRelInfo, tupleid); - - /* Process RETURNING if present */ - if (resultRelInfo->ri_projectReturning) - { - /* - * We have to put the target tuple into a slot, which means first we - * gotta fetch it. We can use the trigger tuple slot. - */ - TupleTableSlot *slot = estate->es_trig_tuple_slot; - HeapTupleData deltuple; - Buffer delbuffer; - - deltuple.t_self = *tupleid; - if (!heap_fetch(resultRelationDesc, SnapshotAny, - &deltuple, &delbuffer, false, NULL)) - elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING"); - - if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc)) - ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc)); - ExecStoreTuple(&deltuple, slot, InvalidBuffer, false); - - ExecProcessReturning(resultRelInfo->ri_projectReturning, - slot, planSlot, dest); - - ExecClearTuple(slot); - ReleaseBuffer(delbuffer); - } -}