Example #1
0
/* --------------------------------
 *		ExecSetSlotDescriptor
 *
 *		This function is used to set the tuple descriptor associated
 *		with the slot's tuple.
 * --------------------------------
 */
void
ExecSetSlotDescriptor(TupleTableSlot *slot,		/* slot to change */
					  TupleDesc tupdesc,		/* new tuple descriptor */
					  bool shouldFree)	/* is desc owned by slot? */
{
	/* For safety, make sure slot is empty before changing it */
	ExecClearTuple(slot);

	/*
	 * Release any old descriptor.	Also release old Datum/isnull arrays if
	 * present (we don't bother to check if they could be re-used).
	 */
	if (slot->tts_shouldFreeDesc)
		FreeTupleDesc(slot->tts_tupleDescriptor);

	if (slot->tts_values)
		pfree(slot->tts_values);
	if (slot->tts_isnull)
		pfree(slot->tts_isnull);

	/*
	 * Set up the new descriptor
	 */
	slot->tts_tupleDescriptor = tupdesc;
	slot->tts_shouldFreeDesc = shouldFree;

	/*
	 * Allocate Datum/isnull arrays of the appropriate size.  These must have
	 * the same lifetime as the slot, so allocate in the slot's own context.
	 */
	slot->tts_values = (Datum *)
		MemoryContextAlloc(slot->tts_mcxt, tupdesc->natts * sizeof(Datum));
	slot->tts_isnull = (bool *)
		MemoryContextAlloc(slot->tts_mcxt, tupdesc->natts * sizeof(bool));
}
Example #2
0
/*
 * TupleDesc_FromNamesAndOids - create a TupleDesc from some basic elements
 *
 * Given a count of attributes, an array of names and type Oids, build and
 * return a TupleDesc.
 */
TupleDesc
TupleDesc_FromNamesAndOids(int ac, const char **names, Oid *types)
{
	unsigned int i;
	volatile TupleDesc td = NULL;

	PG_TRY();
	{
		td = CreateTemplateTupleDesc(ac, false);
		for (i = 0; i < ac; ++i)
		{
			const char *name = NULL;

			if (names)
				name = names[i];
			if (name == NULL)
				name = "";

			TupleDescInitEntry(td, i + 1, name, types[i], -1, 0);
		}
	}
	PG_CATCH();
	{
		if (td != NULL)
			FreeTupleDesc(td);
		PG_RE_THROW();
	}
	PG_END_TRY();

	return(td);
}
Example #3
0
void
plpgsql_check_recval_release(PLpgSQL_rec *rec)
{

#if PG_VERSION_NUM >= 110000

	Assert(rec->dtype == PLPGSQL_DTYPE_REC);

	if (rec->erh)
		DeleteExpandedObject(ExpandedRecordGetDatum(rec->erh));
	rec->erh = NULL;

#else

	if (rec->freetup)
		heap_freetuple(rec->tup);

	if (rec->freetupdesc)
		FreeTupleDesc(rec->tupdesc);

	rec->freetup = false;
	rec->freetupdesc = false;

#endif

}
Example #4
0
/*
 * Class:     org_postgresql_pljava_internal_TupleDesc
 * Method:    _free
 * Signature: (J)V
 */
JNIEXPORT void JNICALL
Java_org_postgresql_pljava_internal_TupleDesc__1free(JNIEnv* env, jobject _this, jlong pointer)
{
	BEGIN_NATIVE_NO_ERRCHECK
	Ptr2Long p2l;
	p2l.longVal = pointer;
	FreeTupleDesc((TupleDesc)p2l.ptrVal);
	END_NATIVE
}
Example #5
0
/*
 * Decrement the reference count of a tupdesc, remove the corresponding
 * reference from CurrentResourceOwner, and free the tupdesc if no more
 * references remain.
 *
 * Do not apply this to tupdescs that are not being refcounted.  (Use the
 * macro ReleaseTupleDesc for tupdescs of uncertain status.)
 */
void
DecrTupleDescRefCount(TupleDesc tupdesc)
{
	Assert(tupdesc->tdrefcount > 0);

	ResourceOwnerForgetTupleDesc(CurrentResourceOwner, tupdesc);
	if (--tupdesc->tdrefcount == 0)
		FreeTupleDesc(tupdesc);
}
Example #6
0
static void
tupd_dealloc(PyObj self)
{
	MemoryContext former = CurrentMemoryContext;

	Py_XDECREF(PyPgTupleDesc_GetNames(self));
	PyPgTupleDesc_SetNames(self, NULL);

	Py_XDECREF(PyPgTupleDesc_GetNameMap(self));
	PyPgTupleDesc_SetNameMap(self, NULL);

	Py_XDECREF(PyPgTupleDesc_GetTypesTuple(self));
	PyPgTupleDesc_SetTypesTuple(self, NULL);

	PG_TRY();
	{
		TupleDesc td;
		int *map;

		td = PyPgTupleDesc_GetTupleDesc(self);
		PyPgTupleDesc_SetTupleDesc(self, NULL);
		if (td != NULL)
			FreeTupleDesc(td);

		map = PyPgTupleDesc_GetIndexMap(self);
		PyPgTupleDesc_SetIndexMap(self, NULL);
		if (map != NULL)
			pfree(map);

		map = PyPgTupleDesc_GetFreeMap(self);
		PyPgTupleDesc_SetFreeMap(self, NULL);
		if (map != NULL)
			pfree(map);

		/*
		 * When PLPY_STRANGE_THINGS is defined.
		 */
		RaiseAStrangeError
	}
	PG_CATCH();
	{
		PyErr_EmitPgErrorAsWarning("could not deallocate Postgres.TupleDesc fields");
	}
	PG_END_TRY();
	/*
	 * Normally, this doesn't matter, but it is possible for the context
	 * to be switched by the above code..
	 */
	MemoryContextSwitchTo(former);

	Py_TYPE(self)->tp_free(self);
}
Example #7
0
/*
 * There should be only one.
 */
void
EmptyPyPgTupleDesc_Initialize(void)
{
	PG_TRY();
	{
		TupleDesc td;
		td = CreateTemplateTupleDesc(0, false);
		EmptyPyPgTupleDesc = PyPgTupleDesc_FromCopy(td);
		FreeTupleDesc(td);
	}
	PG_CATCH();
	{
		PyErr_SetPgError(true);
	}
	PG_END_TRY();
}
Example #8
0
static void
PLy_result_dealloc(PyObject *arg)
{
	PLyResultObject *ob = (PLyResultObject *) arg;

	Py_XDECREF(ob->nrows);
	Py_XDECREF(ob->rows);
	Py_XDECREF(ob->status);
	if (ob->tupdesc)
	{
		FreeTupleDesc(ob->tupdesc);
		ob->tupdesc = NULL;
	}

	arg->ob_type->tp_free(arg);
}
Example #9
0
void
plproxy_free_composite(ProxyComposite *rec)
{
	int i;
	int natts = rec->tupdesc->natts;

	for (i = 0; i < natts; i++)
	{
		plproxy_free_type(rec->type_list[i]);
		if (rec->name_list[i])
			pfree(rec->name_list[i]);
	}
	pfree(rec->type_list);
	pfree(rec->name_list);
	FreeTupleDesc(rec->tupdesc);
	pfree(rec);
}
Example #10
0
/* --------------------------------
 *		ExecDropSingleTupleTableSlot
 *
 *		Release a TupleTableSlot made with MakeSingleTupleTableSlot.
 * --------------------------------
 */
void
ExecDropSingleTupleTableSlot(TupleTableSlot *slot)
{
	/*
	 * sanity checks
	 */
	Assert(slot != NULL);

	ExecClearTuple(slot);
	if (slot->tts_shouldFreeDesc)
		FreeTupleDesc(slot->tts_tupleDescriptor);
	if (slot->tts_values)
		pfree(slot->tts_values);
	if (slot->tts_isnull)
		pfree(slot->tts_isnull);

	pfree(slot);
}
Example #11
0
/* --------------------------------
 *		ExecDropTupleTable
 *
 *		This frees the storage used by the tuple table itself
 *		and optionally frees the contents of the table also.
 *		It is expected that this routine be called by EndPlan().
 * --------------------------------
 */
void
ExecDropTupleTable(TupleTable table,	/* tuple table */
				   bool shouldFree)		/* true if we should free slot
										 * contents */
{
	/*
	 * sanity checks
	 */
	Assert(table != NULL);

	/*
	 * first free all the valid pointers in the tuple array and drop refcounts
	 * of any referenced buffers, if that's what the caller wants.  (There is
	 * probably no good reason for the caller ever not to want it!)
	 */
	if (shouldFree)
	{
		int			next = table->next;
		int			i;

		for (i = 0; i < next; i++)
		{
			TupleTableSlot *slot = &(table->array[i]);

			ExecClearTuple(slot);
			if (slot->tts_shouldFreeDesc)
				FreeTupleDesc(slot->tts_tupleDescriptor);
			if (slot->tts_values)
				pfree(slot->tts_values);
			if (slot->tts_isnull)
				pfree(slot->tts_isnull);
		}
	}

	/*
	 * finally free the tuple table itself.
	 */
	pfree(table);
}
Example #12
0
void
TupleFormerTerm(TupleFormer *former)
{
	if (former->typId)
		pfree(former->typId);

	if (former->typIOParam)
		pfree(former->typIOParam);

	if (former->typInput)
		pfree(former->typInput);

	if (former->values)
		pfree(former->values);

	if (former->isnull)
		pfree(former->isnull);

	if (former->attnum)
		pfree(former->attnum);

	if (former->desc)
		FreeTupleDesc(former->desc);
}
Example #13
0
/*
 * ExecEndRecScan
 *
 * Ends the SeqScan and then does some additional things.
 */
void
ExecEndRecScan(RecScanState *node)
{
	/* End the normal scan. */
	switch(nodeTag(node->subscan)) {
		case T_SeqScanState:
			ExecEndSeqScan((SeqScanState *) node->subscan);
			break;

		default:
			elog(ERROR, "invalid RecScan subscan type: %d", (int) nodeTag(node->subscan));
			break;
	}

	/* Now for extra stuff. */
	if (node->itemList)
		pfree(node->itemList);
	if (node->fullItemList)
		pfree(node->fullItemList);
	if (node->userFeatures)
		pfree(node->userFeatures);
	if (node->base_slot)
		FreeTupleDesc(node->base_slot);
}
Example #14
0
static void
lint_func_beg( PLpgSQL_execstate * estate, PLpgSQL_function * func )
{
	const char *err_text = estate->err_text;

	if (plpgsql_lint_enable)
	{
		int i;
		PLpgSQL_rec *saved_records;
		PLpgSQL_var *saved_vars;
		MemoryContext oldcontext;
		ResourceOwner oldowner;

		/*
		 * inside control a rec and vars variables are modified, so we should to save their
		 * content
		 */
		saved_records = palloc(sizeof(PLpgSQL_rec) * estate->ndatums);
		saved_vars = palloc(sizeof(PLpgSQL_var) * estate->ndatums);

		for (i = 0; i < estate->ndatums; i++)
		{
			if (estate->datums[i]->dtype == PLPGSQL_DTYPE_REC)
			{
				PLpgSQL_rec *rec = (PLpgSQL_rec *) estate->datums[i];

				saved_records[i].tup = rec->tup;
				saved_records[i].tupdesc = rec->tupdesc;
				saved_records[i].freetup = rec->freetup;
				saved_records[i].freetupdesc = rec->freetupdesc;

				/* don't release a original tupdesc and original tup */
				rec->freetup = false;
				rec->freetupdesc = false;
			}
			else if (estate->datums[i]->dtype == PLPGSQL_DTYPE_VAR)
			{
				PLpgSQL_var *var = (PLpgSQL_var *) estate->datums[i];

				saved_vars[i].value = var->value;
				saved_vars[i].isnull = var->isnull;
				saved_vars[i].freeval = var->freeval;

				var->freeval = false;
			}
		}

		estate->err_text = NULL;

		/*
		 * Raised exception should be trapped in outer functtion. Protection
		 * against outer trap is QUERY_CANCELED exception. 
		 */
		oldcontext = CurrentMemoryContext;
		oldowner = CurrentResourceOwner;

		PG_TRY();
		{
			lint_stmt(estate, func, (PLpgSQL_stmt *) func->action);
		}
		PG_CATCH();
		{
			ErrorData  *edata;

			/* Save error info */
			MemoryContextSwitchTo(oldcontext);
			edata = CopyErrorData();
			FlushErrorState();
			CurrentResourceOwner = oldowner;

			edata->sqlerrcode = ERRCODE_QUERY_CANCELED;
			ReThrowError(edata);
		}
		PG_END_TRY();

		estate->err_text = err_text;
		estate->err_stmt = NULL;

		/* return back a original rec variables */
		for (i = 0; i < estate->ndatums; i++)
		{
			if (estate->datums[i]->dtype == PLPGSQL_DTYPE_REC)
			{
				PLpgSQL_rec *rec = (PLpgSQL_rec *) estate->datums[i];

				if (rec->freetupdesc)
					FreeTupleDesc(rec->tupdesc);

				rec->tup = saved_records[i].tup;
				rec->tupdesc = saved_records[i].tupdesc;
				rec->freetup = saved_records[i].freetup;
				rec->freetupdesc = saved_records[i].freetupdesc;
			}
			else if (estate->datums[i]->dtype == PLPGSQL_DTYPE_VAR)
			{
				PLpgSQL_var *var = (PLpgSQL_var *) estate->datums[i];

				var->value = saved_vars[i].value;
				var->isnull = saved_vars[i].isnull;
				var->freeval = saved_vars[i].freeval;
			}
		}

		pfree(saved_records);
		pfree(saved_vars);
	}
}
Example #15
0
void
EndMotionLayerNode(MotionLayerState *mlStates, int16 motNodeID, bool flushCommLayer)
{
	MotionNodeEntry *pMNEntry;
	ChunkSorterEntry *pCSEntry;
	int i;

	pMNEntry = getMotionNodeEntry(mlStates, motNodeID, "EndMotionLayerNode");

#ifdef AMS_VERBOSE_LOGGING
	elog(DEBUG5, "Cleaning up Motion Layer details for motion node %d.",
		 motNodeID);
#endif

	/*
	 * Iterate through all entries in the motion layer's chunk-sort map, to
	 * see if we have gotten end-of-stream from all senders.
	 */
	if (pMNEntry->preserve_order && pMNEntry->ready_tuple_lists != NULL)
	{
		for (i=0; i < GpIdentity.numsegments; i++)
		{
			pCSEntry = &pMNEntry->ready_tuple_lists[i];

			/*
			 * QD should not expect end-of-stream comes from QEs who is not members of
			 * direct dispatch 
			 */
			if (!pCSEntry->init)
				continue;

			if (pMNEntry->preserve_order &&
				gp_log_interconnect >= GPVARS_VERBOSITY_DEBUG)
			{
				/* Print chunk-sorter entry statistics. */
				elog(DEBUG4, "Chunk-sorter entry [route=%d,node=%d] statistics:\n"
					 "\tAvailable Tuples High-Watermark: " UINT64_FORMAT,
					 i, pMNEntry->motion_node_id,
					 pMNEntry->stat_tuples_available_hwm);
			}

			if (!pMNEntry->stopped && !pCSEntry->end_of_stream)
			{
				if (flushCommLayer)
				{
					elog(FATAL, "Motion layer node %d cleanup - did not receive"
						 " end-of-stream from sender %d.", motNodeID, i);

					/*** TODO - get chunks until end-of-stream comes in. ***/
				}
				else
				{
					elog(LOG, "Motion layer node %d cleanup - did not receive"
						 " end-of-stream from sender %d.", motNodeID, i);
				}
			}
			else
			{
				/* End-of-stream is marked for this entry. */

				/*** TODO - do more than just complain! ***/

				if (pCSEntry->chunk_list.num_chunks > 0)
				{
					elog(LOG, "Motion layer node %d cleanup - there are still"
						 " %d chunks enqueued from sender %d.", motNodeID,
						 pCSEntry->chunk_list.num_chunks, i );
				}

				/***
					TODO - Make sure there are no outstanding tuples in the
					tuple-store.
				***/
			}

			/*
			 * Clean up the chunk-sorter entry, then remove it from the hash
			 * table.
			 */
			clearTCList(&pMNEntry->ser_tup_info.chunkCache, &pCSEntry->chunk_list);
			if (pMNEntry->preserve_order)	/* Clean up the tuple-store. */
				htfifo_destroy(pCSEntry->ready_tuples);
		}
	}
	pMNEntry->cleanedUp = true;

	/* Clean up the motion-node entry, then remove it from the hash table. */
	if (gp_log_interconnect >= GPVARS_VERBOSITY_VERBOSE)
    {
        if (pMNEntry->stat_total_bytes_sent > 0 ||
            pMNEntry->sel_wr_wait > 0)
        {
            elog(LOG, "Interconnect seg%d slice%d sent " UINT64_FORMAT " tuples, "
                 UINT64_FORMAT " total bytes, " UINT64_FORMAT " tuple bytes, "
				 UINT64_FORMAT " chunks; waited " UINT64_FORMAT " usec.",
		         Gp_segment,
                 currentSliceId,
		         pMNEntry->stat_total_sends,
		         pMNEntry->stat_total_bytes_sent,
		         pMNEntry->stat_tuple_bytes_sent,
		         pMNEntry->stat_total_chunks_sent,
		         pMNEntry->sel_wr_wait
		        );
        }
        if (pMNEntry->stat_total_bytes_recvd > 0 ||
            pMNEntry->sel_rd_wait > 0)
        {
            elog(LOG, "Interconnect seg%d slice%d received from slice%d: " UINT64_FORMAT " tuples, "
                 UINT64_FORMAT " total bytes, " UINT64_FORMAT " tuple bytes, " 
				 UINT64_FORMAT " chunks; waited " UINT64_FORMAT " usec.",
                 Gp_segment,
                 currentSliceId,
		         motNodeID,
		         pMNEntry->stat_total_recvs,
		         pMNEntry->stat_total_bytes_recvd,
		         pMNEntry->stat_tuple_bytes_recvd,
		         pMNEntry->stat_total_chunks_recvd,
		         pMNEntry->sel_rd_wait
		        );
        }
    }

	CleanupSerTupInfo(&pMNEntry->ser_tup_info);
	FreeTupleDesc(pMNEntry->tuple_desc);
	if (!pMNEntry->preserve_order)
		htfifo_destroy(pMNEntry->ready_tuples);

	pMNEntry->valid = false;
}
Example #16
0
TupleCheckStatus
FilterInit(Filter *filter, TupleDesc desc, Oid collation)
{
	int				i;
	ParsedFunction	func;
	HeapTuple		ftup;
	HeapTuple		ltup;
	Form_pg_proc	pp;
	Form_pg_language	lp;
	TupleCheckStatus	status = NEED_COERCION_CHECK;

	if (filter->funcstr == NULL)
		return NO_COERCION;

	/* parse filter function */
	func = ParseFunction(filter->funcstr, true);

	filter->funcid = func.oid;
	filter->nargs = func.nargs;
	for (i = 0; i < filter->nargs; i++)
	{
		/* Check for polymorphic types and internal pseudo-type argument */
		if (IsPolymorphicType(func.argtypes[i]) ||
			func.argtypes[i] == INTERNALOID)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("filter function does not support a polymorphic function and having a internal pseudo-type argument function: %s",
							get_func_name(filter->funcid))));

		filter->argtypes[i] = func.argtypes[i];
	}

	ftup = SearchSysCache(PROCOID, ObjectIdGetDatum(filter->funcid), 0, 0, 0);
	pp = (Form_pg_proc) GETSTRUCT(ftup);

	if (pp->proretset)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("filter function must not return set")));

	/* Check data type of the function result value */
	if (pp->prorettype == desc->tdtypeid && pp->prorettype != RECORDOID)
		status = NO_COERCION;
	else if (pp->prorettype == RECORDOID)
	{
		TupleDesc	resultDesc = NULL;

		/* Check for OUT parameters defining a RECORD result */
		resultDesc = build_function_result_tupdesc_t(ftup);

		if (resultDesc)
		{
			if (tupledesc_match(desc, resultDesc))
				status = NO_COERCION;

			FreeTupleDesc(resultDesc);
		}
	}
	else if (get_typtype(pp->prorettype) != TYPTYPE_COMPOSITE)
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("function return data type and target table data type do not match")));

	/* Get default values */
#if PG_VERSION_NUM >= 80400
	filter->fn_ndargs = pp->pronargdefaults;
	if (filter->fn_ndargs > 0)
	{
		Datum		proargdefaults;
		bool		isnull;
		char	   *str;
		List	   *defaults;
		ListCell   *l;

		filter->defaultValues = palloc(sizeof(Datum) * filter->fn_ndargs);
		filter->defaultIsnull = palloc(sizeof(bool) * filter->fn_ndargs);

		proargdefaults = SysCacheGetAttr(PROCOID, ftup,
										 Anum_pg_proc_proargdefaults,
										 &isnull);
		Assert(!isnull);
		str = TextDatumGetCString(proargdefaults);
		defaults = (List *) stringToNode(str);
		Assert(IsA(defaults, List));
		pfree(str);

		filter->econtext = CreateStandaloneExprContext();
		i = 0;
		foreach(l, defaults)
		{
			Expr		   *expr = (Expr *) lfirst(l);
			ExprState	   *argstate;
			ExprDoneCond	thisArgIsDone;

			argstate = ExecInitExpr(expr, NULL);

			filter->defaultValues[i] = ExecEvalExpr(argstate,
													filter->econtext,
													&filter->defaultIsnull[i],
													&thisArgIsDone);

			if (thisArgIsDone != ExprSingleResult)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("functions and operators can take at most one set argument")));

			i++;
		}
Example #17
0
/*
 *		ExecMakeTableFunctionResult
 *
 * Evaluate a table function, producing a materialized result in a Tuplestore
 * object.
 *
 * This is used by nodeFunctionscan.c.
 */
Tuplestorestate *
ExecMakeTableFunctionResult(SetExprState *setexpr,
							ExprContext *econtext,
							MemoryContext argContext,
							TupleDesc expectedDesc,
							bool randomAccess)
{
	Tuplestorestate *tupstore = NULL;
	TupleDesc	tupdesc = NULL;
	Oid			funcrettype;
	bool		returnsTuple;
	bool		returnsSet = false;
	FunctionCallInfoData fcinfo;
	PgStat_FunctionCallUsage fcusage;
	ReturnSetInfo rsinfo;
	HeapTupleData tmptup;
	MemoryContext callerContext;
	MemoryContext oldcontext;
	bool		first_time = true;

	callerContext = CurrentMemoryContext;

	funcrettype = exprType((Node *) setexpr->expr);

	returnsTuple = type_is_rowtype(funcrettype);

	/*
	 * Prepare a resultinfo node for communication.  We always do this even if
	 * not expecting a set result, so that we can pass expectedDesc.  In the
	 * generic-expression case, the expression doesn't actually get to see the
	 * resultinfo, but set it up anyway because we use some of the fields as
	 * our own state variables.
	 */
	rsinfo.type = T_ReturnSetInfo;
	rsinfo.econtext = econtext;
	rsinfo.expectedDesc = expectedDesc;
	rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize | SFRM_Materialize_Preferred);
	if (randomAccess)
		rsinfo.allowedModes |= (int) SFRM_Materialize_Random;
	rsinfo.returnMode = SFRM_ValuePerCall;
	/* isDone is filled below */
	rsinfo.setResult = NULL;
	rsinfo.setDesc = NULL;

	/*
	 * Normally the passed expression tree will be a SetExprState, since the
	 * grammar only allows a function call at the top level of a table
	 * function reference.  However, if the function doesn't return set then
	 * the planner might have replaced the function call via constant-folding
	 * or inlining.  So if we see any other kind of expression node, execute
	 * it via the general ExecEvalExpr() code; the only difference is that we
	 * don't get a chance to pass a special ReturnSetInfo to any functions
	 * buried in the expression.
	 */
	if (!setexpr->elidedFuncState)
	{
		/*
		 * This path is similar to ExecMakeFunctionResultSet.
		 */
		returnsSet = setexpr->funcReturnsSet;
		InitFunctionCallInfoData(fcinfo, &(setexpr->func),
								 list_length(setexpr->args),
								 setexpr->fcinfo_data.fncollation,
								 NULL, (Node *) &rsinfo);

		/*
		 * Evaluate the function's argument list.
		 *
		 * We can't do this in the per-tuple context: the argument values
		 * would disappear when we reset that context in the inner loop.  And
		 * the caller's CurrentMemoryContext is typically a query-lifespan
		 * context, so we don't want to leak memory there.  We require the
		 * caller to pass a separate memory context that can be used for this,
		 * and can be reset each time through to avoid bloat.
		 */
		MemoryContextReset(argContext);
		oldcontext = MemoryContextSwitchTo(argContext);
		ExecEvalFuncArgs(&fcinfo, setexpr->args, econtext);
		MemoryContextSwitchTo(oldcontext);

		/*
		 * If function is strict, and there are any NULL arguments, skip
		 * calling the function and act like it returned NULL (or an empty
		 * set, in the returns-set case).
		 */
		if (setexpr->func.fn_strict)
		{
			int			i;

			for (i = 0; i < fcinfo.nargs; i++)
			{
				if (fcinfo.argnull[i])
					goto no_function_result;
			}
		}
	}
	else
	{
		/* Treat setexpr as a generic expression */
		InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
	}

	/*
	 * Switch to short-lived context for calling the function or expression.
	 */
	MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);

	/*
	 * Loop to handle the ValuePerCall protocol (which is also the same
	 * behavior needed in the generic ExecEvalExpr path).
	 */
	for (;;)
	{
		Datum		result;

		CHECK_FOR_INTERRUPTS();

		/*
		 * reset per-tuple memory context before each call of the function or
		 * expression. This cleans up any local memory the function may leak
		 * when called.
		 */
		ResetExprContext(econtext);

		/* Call the function or expression one time */
		if (!setexpr->elidedFuncState)
		{
			pgstat_init_function_usage(&fcinfo, &fcusage);

			fcinfo.isnull = false;
			rsinfo.isDone = ExprSingleResult;
			result = FunctionCallInvoke(&fcinfo);

			pgstat_end_function_usage(&fcusage,
									  rsinfo.isDone != ExprMultipleResult);
		}
		else
		{
			result =
				ExecEvalExpr(setexpr->elidedFuncState, econtext, &fcinfo.isnull);
			rsinfo.isDone = ExprSingleResult;
		}

		/* Which protocol does function want to use? */
		if (rsinfo.returnMode == SFRM_ValuePerCall)
		{
			/*
			 * Check for end of result set.
			 */
			if (rsinfo.isDone == ExprEndResult)
				break;

			/*
			 * If first time through, build tuplestore for result.  For a
			 * scalar function result type, also make a suitable tupdesc.
			 */
			if (first_time)
			{
				oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
				tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
				rsinfo.setResult = tupstore;
				if (!returnsTuple)
				{
					tupdesc = CreateTemplateTupleDesc(1, false);
					TupleDescInitEntry(tupdesc,
									   (AttrNumber) 1,
									   "column",
									   funcrettype,
									   -1,
									   0);
					rsinfo.setDesc = tupdesc;
				}
				MemoryContextSwitchTo(oldcontext);
			}

			/*
			 * Store current resultset item.
			 */
			if (returnsTuple)
			{
				if (!fcinfo.isnull)
				{
					HeapTupleHeader td = DatumGetHeapTupleHeader(result);

					if (tupdesc == NULL)
					{
						/*
						 * This is the first non-NULL result from the
						 * function.  Use the type info embedded in the
						 * rowtype Datum to look up the needed tupdesc.  Make
						 * a copy for the query.
						 */
						oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
						tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td),
															  HeapTupleHeaderGetTypMod(td));
						rsinfo.setDesc = tupdesc;
						MemoryContextSwitchTo(oldcontext);
					}
					else
					{
						/*
						 * Verify all later returned rows have same subtype;
						 * necessary in case the type is RECORD.
						 */
						if (HeapTupleHeaderGetTypeId(td) != tupdesc->tdtypeid ||
							HeapTupleHeaderGetTypMod(td) != tupdesc->tdtypmod)
							ereport(ERROR,
									(errcode(ERRCODE_DATATYPE_MISMATCH),
									 errmsg("rows returned by function are not all of the same row type")));
					}

					/*
					 * tuplestore_puttuple needs a HeapTuple not a bare
					 * HeapTupleHeader, but it doesn't need all the fields.
					 */
					tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
					tmptup.t_data = td;

					tuplestore_puttuple(tupstore, &tmptup);
				}
				else
				{
					/*
					 * NULL result from a tuple-returning function; expand it
					 * to a row of all nulls.  We rely on the expectedDesc to
					 * form such rows.  (Note: this would be problematic if
					 * tuplestore_putvalues saved the tdtypeid/tdtypmod from
					 * the provided descriptor, since that might not match
					 * what we get from the function itself.  But it doesn't.)
					 */
					int			natts = expectedDesc->natts;
					bool	   *nullflags;

					nullflags = (bool *) palloc(natts * sizeof(bool));
					memset(nullflags, true, natts * sizeof(bool));
					tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags);
				}
			}
			else
			{
				/* Scalar-type case: just store the function result */
				tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo.isnull);
			}

			/*
			 * Are we done?
			 */
			if (rsinfo.isDone != ExprMultipleResult)
				break;
		}
		else if (rsinfo.returnMode == SFRM_Materialize)
		{
			/* check we're on the same page as the function author */
			if (!first_time || rsinfo.isDone != ExprSingleResult)
				ereport(ERROR,
						(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
						 errmsg("table-function protocol for materialize mode was not followed")));
			/* Done evaluating the set result */
			break;
		}
		else
			ereport(ERROR,
					(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
					 errmsg("unrecognized table-function returnMode: %d",
							(int) rsinfo.returnMode)));

		first_time = false;
	}

no_function_result:

	/*
	 * If we got nothing from the function (ie, an empty-set or NULL result),
	 * we have to create the tuplestore to return, and if it's a
	 * non-set-returning function then insert a single all-nulls row.  As
	 * above, we depend on the expectedDesc to manufacture the dummy row.
	 */
	if (rsinfo.setResult == NULL)
	{
		MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
		tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
		rsinfo.setResult = tupstore;
		if (!returnsSet)
		{
			int			natts = expectedDesc->natts;
			bool	   *nullflags;

			MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
			nullflags = (bool *) palloc(natts * sizeof(bool));
			memset(nullflags, true, natts * sizeof(bool));
			tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags);
		}
	}

	/*
	 * If function provided a tupdesc, cross-check it.  We only really need to
	 * do this for functions returning RECORD, but might as well do it always.
	 */
	if (rsinfo.setDesc)
	{
		tupledesc_match(expectedDesc, rsinfo.setDesc);

		/*
		 * If it is a dynamically-allocated TupleDesc, free it: it is
		 * typically allocated in a per-query context, so we must avoid
		 * leaking it across multiple usages.
		 */
		if (rsinfo.setDesc->tdrefcount == -1)
			FreeTupleDesc(rsinfo.setDesc);
	}

	MemoryContextSwitchTo(callerContext);

	/* All done, pass back the tuplestore */
	return rsinfo.setResult;
}
Example #18
0
/* ----------
 * toast_flatten_tuple_attribute -
 *
 *	If a Datum is of composite type, "flatten" it to contain no toasted fields.
 *	This must be invoked on any potentially-composite field that is to be
 *	inserted into a tuple.	Doing this preserves the invariant that toasting
 *	goes only one level deep in a tuple.
 * ----------
 */
Datum
toast_flatten_tuple_attribute(Datum value,
							  Oid typeId, int32 typeMod)
{
	TupleDesc	tupleDesc;
	HeapTupleHeader olddata;
	HeapTupleHeader new_data;
	int32		new_len;
	HeapTupleData tmptup;
	Form_pg_attribute *att;
	int			numAttrs;
	int			i;
	bool		need_change = false;
	bool		has_nulls = false;
	Datum		toast_values[MaxTupleAttributeNumber];
	bool		toast_isnull[MaxTupleAttributeNumber];
	bool		toast_free[MaxTupleAttributeNumber];

	/*
	 * See if it's a composite type, and get the tupdesc if so.
	 */
	tupleDesc = lookup_rowtype_tupdesc_noerror(typeId, typeMod, true);
	if (tupleDesc == NULL)
		return value;			/* not a composite type */

	tupleDesc = CreateTupleDescCopy(tupleDesc);
	att = tupleDesc->attrs;
	numAttrs = tupleDesc->natts;

	/*
	 * Break down the tuple into fields.
	 */
	olddata = DatumGetHeapTupleHeader(value);
	Assert(typeId == HeapTupleHeaderGetTypeId(olddata));
	Assert(typeMod == HeapTupleHeaderGetTypMod(olddata));
	/* Build a temporary HeapTuple control structure */
	tmptup.t_len = HeapTupleHeaderGetDatumLength(olddata);
	ItemPointerSetInvalid(&(tmptup.t_self));
	tmptup.t_tableOid = InvalidOid;
	tmptup.t_data = olddata;

	Assert(numAttrs <= MaxTupleAttributeNumber);
	heap_deform_tuple(&tmptup, tupleDesc, toast_values, toast_isnull);

	memset(toast_free, 0, numAttrs * sizeof(bool));

	for (i = 0; i < numAttrs; i++)
	{
		/*
		 * Look at non-null varlena attributes
		 */
		if (toast_isnull[i])
			has_nulls = true;
		else if (att[i]->attlen == -1)
		{
			varattrib  *new_value;

			new_value = (varattrib *) DatumGetPointer(toast_values[i]);
			if (VARATT_IS_EXTENDED(new_value))
			{
				new_value = heap_tuple_untoast_attr(new_value);
				toast_values[i] = PointerGetDatum(new_value);
				toast_free[i] = true;
				need_change = true;
			}
		}
	}

	/*
	 * If nothing to untoast, just return the original tuple.
	 */
	if (!need_change)
	{
		FreeTupleDesc(tupleDesc);
		return value;
	}

	/*
	 * Calculate the new size of the tuple.  Header size should not change,
	 * but data size might.
	 */
	new_len = offsetof(HeapTupleHeaderData, t_bits);
	if (has_nulls)
		new_len += BITMAPLEN(numAttrs);
	if (olddata->t_infomask & HEAP_HASOID)
		new_len += sizeof(Oid);
	new_len = MAXALIGN(new_len);
	Assert(new_len == olddata->t_hoff);
	new_len += heap_compute_data_size(tupleDesc, toast_values, toast_isnull);

	new_data = (HeapTupleHeader) palloc0(new_len);

	/*
	 * Put the tuple header and the changed values into place
	 */
	memcpy(new_data, olddata, olddata->t_hoff);

	HeapTupleHeaderSetDatumLength(new_data, new_len);

	heap_fill_tuple(tupleDesc,
					toast_values,
					toast_isnull,
					(char *) new_data + olddata->t_hoff,
					&(new_data->t_infomask),
					has_nulls ? new_data->t_bits : NULL);

	/*
	 * Free allocated temp values
	 */
	for (i = 0; i < numAttrs; i++)
		if (toast_free[i])
			pfree(DatumGetPointer(toast_values[i]));
	FreeTupleDesc(tupleDesc);

	return PointerGetDatum(new_data);
}
Example #19
0
static PyObj
func_new_from_oid(PyTypeObject *subtype, Oid fn_oid, PyObj fn_oid_int, PyObj fn_oid_str)
{
	volatile HeapTuple ht = NULL;
	volatile PyObj rob = NULL;

	Assert(OidIsValid(fn_oid));
	Assert(fn_oid_int != NULL);
	Assert(fn_oid_str != NULL);

	rob = subtype->tp_alloc(subtype, 0);
	if (rob == NULL)
		return(NULL);

	PyPgFunction_SetOid(rob, fn_oid);
	PyPgFunction_SetStateful(rob, false);

	Py_INCREF(fn_oid_int);
	Py_INCREF(fn_oid_str);

	PyPgFunction_SetPyLongOid(rob, fn_oid_int);
	PyPgFunction_SetPyUnicodeOid(rob, fn_oid_str);

	/*
	 * Collect the Function information from the system cache
	 */
	PG_TRY();
	{
		Form_pg_proc ps;
		Form_pg_namespace ns;
		FmgrInfo flinfo;
		text *prosrc;
		Datum prosrc_datum;
		bool isnull = true;
		const char *filename = NULL, *nspname, *q_nspname;
		TupleDesc argdesc = NULL, result_desc = NULL;
		Oid prorettype = InvalidOid;
		PyObj id_str_ob = NULL, nspname_str_ob = NULL;
		PyObj filename_str_ob = NULL, q_nspname_str_ob = NULL;
		PyObj output = NULL, src = NULL;
		PyObj input;

		ht = SearchSysCache(PROCOID, fn_oid, 0, 0, 0);
		if (!HeapTupleIsValid(ht))
		{
			ereport(ERROR,(
				errcode(ERRCODE_UNDEFINED_FUNCTION),
				errmsg("failed to find function at oid %d", fn_oid)
			));
		}

		PyPgFunction_SetXMin(rob, HeapTupleHeaderGetXmin(ht->t_data));
		PyPgFunction_SetItemPointer(rob, &(ht->t_self));

		ps = (Form_pg_proc) GETSTRUCT(ht);

		PyPgFunction_SetNamespace(rob, ps->pronamespace);
		PyPgFunction_SetLanguage(rob, ps->prolang);
		PyPgFunction_SetReturnsSet(rob, ps->proretset);
		PyPgFunction_SetVolatile(rob, ps->provolatile);

		prorettype = ps->prorettype;
		prosrc_datum = SysCacheGetAttr(
			PROCOID, ht, Anum_pg_proc_prosrc, &isnull);
		if (!isnull)
		{
			prosrc = DatumGetTextPCopy(prosrc_datum);
			src = PyUnicode_FromTEXT(prosrc);
			PyPgFunction_SetSource(rob, src);
			pfree(prosrc);
			prosrc = NULL;
		}
		else
		{
			src = Py_None;
			Py_INCREF(src);
			PyPgFunction_SetSource(rob, src);
		}
		if (src == NULL)
			PyErr_RelayException();

		/*
		 * Get the function's address.
		 */
		fmgr_info(fn_oid, &flinfo);
		PyPgFunction_SetPGFunction(rob, flinfo.fn_addr);

		/*
		 * Build function parameters TupleDesc
		 */
		if (ps->pronargs > 0)
		{
			argdesc = TupleDesc_From_pg_proc_arginfo(ht);
			input = PyPgTupleDesc_FromCopy(argdesc);
			if (input == NULL)
				PyErr_RelayException();
			PyPgFunction_SetInput(rob, input);
			FreeTupleDesc(argdesc);
		}
		else
		{
			Py_INCREF(EmptyPyPgTupleDesc);
			PyPgFunction_SetInput(rob, EmptyPyPgTupleDesc);
		}

		/*
		 * If it's a registered composite,
		 * PyPgType_FromOid will resolve that below.
		 */
		if (prorettype == RECORDOID)
		{
			/*
			 * Otherwise, build out a function result tupdesc.
			 */
			result_desc = build_function_result_tupdesc_t(ht);
			if (result_desc != NULL)
			{
				/*
				 * Anonymous composite returned by function.
				 */
				output = PyPgType_FromTupleDesc(result_desc);
				PyPgFunction_SetOutput(rob, output);
				FreeTupleDesc(result_desc);
				/*
				 * We will certainly be using it, so bless it right now iff
				 * it's *not* polymorphic.
				 */
				if (output && !PyPgType_IsPolymorphic(output))
					BlessTupleDesc(PyPgType_GetTupleDesc(output));
			}
			else
			{
				/*
				 * ew..
				 */
				goto lookup_output_type;
			}
		}
		else
		{
lookup_output_type:
			output = PyPgType_FromOid(prorettype);
			if (output == NULL)
				PyErr_RelayException();
			PyPgFunction_SetOutput(rob, output);
		}

		RELEASESYSCACHE(&ht);

		/*
		 * Don't worry *too* much about leaking memory.
		 */
		filename = format_procedure(fn_oid);
		Assert(filename != NULL);

		ht = SearchSysCache(NAMESPACEOID,
							PyPgFunction_GetNamespace(rob), 0, 0, 0);
		if (!HeapTupleIsValid(ht))
		{
			pfree((char *) filename);
			elog(ERROR, "function %u namespace %u does not exist",
						fn_oid, PyPgFunction_GetNamespace(rob));
		}

		ns = (Form_pg_namespace) GETSTRUCT(ht);
		nspname = pstrdup(NameStr(ns->nspname));
		RELEASESYSCACHE(&ht);

		/*
		 * Build the filename string.
		 */
		q_nspname = quote_identifier(nspname);

		nspname_str_ob = PyUnicode_FromCString(nspname);
		PyPgFunction_SetNamespaceName(rob, nspname_str_ob);
		if (nspname_str_ob == NULL)
		{
			/*
			 * Invalid encoded string?
			 */
			if (nspname != q_nspname)
				pfree((char *) q_nspname);
			pfree((char *) nspname);
			PyErr_RelayException();
		}

		q_nspname_str_ob = PyUnicode_FromCString(q_nspname);
		if (nspname != q_nspname)
			pfree((char *) q_nspname);
		pfree((char *) nspname);
		/*
		 * Ignore the potential exception for a moment.
		 */
		id_str_ob = PyUnicode_FromCString(filename);

		/*
		 * Skip the filename_str_ob if either of the above failed.
		 */
		if (id_str_ob != NULL && q_nspname_str_ob != NULL)
		{
			if (FunctionIsVisible(fn_oid))
				filename_str_ob = PyUnicode_FromFormat("%U.%U", q_nspname_str_ob, id_str_ob);
			else
			{
				filename_str_ob = id_str_ob;
				Py_INCREF(id_str_ob);
			}
		}
		PyPgFunction_SetFilename(rob, filename_str_ob);
		Py_XDECREF(q_nspname_str_ob);
		Py_XDECREF(id_str_ob);

		pfree((char *) filename);

		if (filename_str_ob == NULL)
			PyErr_RelayException();
	}
	PG_CATCH();
	{
		Py_XDECREF(rob);
		rob = NULL;

		PyErr_SetPgError(false);

		if (ht != NULL)
			ReleaseSysCache(ht);
	}
	PG_END_TRY();

	return(rob);
}
Example #20
0
static void
FunctionParserInit(FunctionParser *self, Checker *checker, const char *infile, TupleDesc desc, bool multi_process, Oid collation)
{
	int					i;
	ParsedFunction		function;
	int					nargs;
	Oid					funcid;
	HeapTuple			ftup;
	Form_pg_proc		pp;
	bool				tupledesc_matched = false;

	if (pg_strcasecmp(infile, "stdin") == 0)
		ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						errmsg("cannot load from STDIN in the case of \"TYPE = FUNCTION\"")));

	if (checker->encoding != -1)
		ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						errmsg("does not support parameter \"ENCODING\" in \"TYPE = FUNCTION\"")));

	function = ParseFunction(infile, false);

	funcid = function.oid;
	fmgr_info(funcid, &self->flinfo);

	if (!self->flinfo.fn_retset)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("function must return set")));

	ftup = SearchSysCache(PROCOID, ObjectIdGetDatum(funcid), 0, 0, 0);
	pp = (Form_pg_proc) GETSTRUCT(ftup);

	/* Check data type of the function result value */
	if (pp->prorettype == desc->tdtypeid && desc->tdtypeid != RECORDOID)
		tupledesc_matched = true;
	else if (pp->prorettype == RECORDOID)
	{
		TupleDesc	resultDesc = NULL;

		/* Check for OUT parameters defining a RECORD result */
		resultDesc = build_function_result_tupdesc_t(ftup);

		if (resultDesc)
		{
			tupledesc_match(desc, resultDesc);
			tupledesc_matched = true;
			FreeTupleDesc(resultDesc);
		}
	}
	else if (get_typtype(pp->prorettype) != TYPTYPE_COMPOSITE)
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("function return data type and target table data type do not match")));

	if (tupledesc_matched && checker->tchecker)
		checker->tchecker->status = NO_COERCION;

	/*
	 * assign arguments
	 */
	nargs = function.nargs;
	for (i = 0;
#if PG_VERSION_NUM >= 80400
		i < nargs - function.nvargs;
#else
		i < nargs;
#endif
		++i)
	{
		if (function.args[i] == NULL)
		{
			if (self->flinfo.fn_strict)
				ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					errmsg("function is strict, but argument %d is NULL", i)));
			self->fcinfo.argnull[i] = true;
		}
		else
		{
			Oid			typinput;
			Oid			typioparam;

			getTypeInputInfo(pp->proargtypes.values[i], &typinput, &typioparam);
			self->fcinfo.arg[i] = OidInputFunctionCall(typinput,
									(char *) function.args[i], typioparam, -1);
			self->fcinfo.argnull[i] = false;
			pfree(function.args[i]);
		}
	}

	/*
	 * assign variadic arguments
	 */
#if PG_VERSION_NUM >= 80400
	if (function.nvargs > 0)
	{
		int			nfixedarg;
		Oid			func;
		Oid			element_type;
		int16		elmlen;
		bool		elmbyval;
		char		elmalign;
		char		elmdelim;
		Oid			elmioparam;
		Datum	   *elems;
		bool	   *nulls;
		int			dims[1];
		int			lbs[1];
		ArrayType  *arry;

		nfixedarg = i;
		element_type = pp->provariadic;

		/*
		 * Get info about element type, including its input conversion proc
		 */
		get_type_io_data(element_type, IOFunc_input,
						 &elmlen, &elmbyval, &elmalign, &elmdelim,
						 &elmioparam, &func);

		elems = (Datum *) palloc(function.nvargs * sizeof(Datum));
		nulls = (bool *) palloc0(function.nvargs * sizeof(bool));
		for (i = 0; i < function.nvargs; i++)
		{
			if (function.args[nfixedarg + i] == NULL)
				nulls[i] = true;
			else
			{
				elems[i] = OidInputFunctionCall(func,
								(char *) function.args[nfixedarg + i], elmioparam, -1);
				pfree(function.args[nfixedarg + i]);
			}
		}

		dims[0] = function.nvargs;
		lbs[0] = 1;
		arry = construct_md_array(elems, nulls, 1, dims, lbs, element_type,
								  elmlen, elmbyval, elmalign);
		self->fcinfo.arg[nfixedarg] = PointerGetDatum(arry);
	}

	/*
	 * assign default arguments
	 */
	if (function.ndargs > 0)
	{
		Datum		proargdefaults;
		bool		isnull;
		char	   *str;
		List	   *defaults;
		int			ndelete;
		ListCell   *l;

		/* shouldn't happen, FuncnameGetCandidates messed up */
		if (function.ndargs > pp->pronargdefaults)
			elog(ERROR, "not enough default arguments");

		proargdefaults = SysCacheGetAttr(PROCOID, ftup,
										 Anum_pg_proc_proargdefaults,
										 &isnull);
		Assert(!isnull);
		str = TextDatumGetCString(proargdefaults);
		defaults = (List *) stringToNode(str);
		Assert(IsA(defaults, List));
		pfree(str);
		/* Delete any unused defaults from the returned list */
		ndelete = list_length(defaults) - function.ndargs;
		while (ndelete-- > 0)
			defaults = list_delete_first(defaults);

		self->arg_econtext = CreateStandaloneExprContext();
		foreach(l, defaults)
		{
			Expr	   *expr = (Expr *) lfirst(l);
			ExprState  *argstate;
			ExprDoneCond thisArgIsDone;

			/* probably shouldn't happen ... */
			if (nargs >= FUNC_MAX_ARGS)
				ereport(ERROR,
						(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
				 errmsg("cannot pass more than %d arguments to a function", FUNC_MAX_ARGS)));

			argstate = ExecInitExpr(expr, NULL);

			self->fcinfo.arg[nargs] = ExecEvalExpr(argstate,
												   self->arg_econtext,
												   &self->fcinfo.argnull[nargs],
												   &thisArgIsDone);

			if (thisArgIsDone != ExprSingleResult)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("functions and operators can take at most one set argument")));

			nargs++;
		}