/**********************************************************************
 * pl_exec.c		- Executor for the PL/pgSQL
 *			  procedural language
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.5 1999/01/17 21:53:32 tgl Exp $
 *
 *	  This software is copyrighted by Jan Wieck - Hamburg.
 *
 *	  The author hereby grants permission  to  use,  copy,	modify,
 *	  distribute,  and	license this software and its documentation
 *	  for any purpose, provided that existing copyright notices are
 *	  retained	in	all  copies  and  that	this notice is included
 *	  verbatim in any distributions. No written agreement, license,
 *	  or  royalty  fee	is required for any of the authorized uses.
 *	  Modifications to this software may be  copyrighted  by  their
 *	  author  and  need  not  follow  the licensing terms described
 *	  here, provided that the new terms are  clearly  indicated  on
 *	  the first page of each file where they apply.
 *
 *	  IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY
 *	  PARTY  FOR  DIRECT,	INDIRECT,	SPECIAL,   INCIDENTAL,	 OR
 *	  CONSEQUENTIAL   DAMAGES  ARISING	OUT  OF  THE  USE  OF  THIS
 *	  SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN
 *	  IF  THE  AUTHOR  HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH
 *	  DAMAGE.
 *
 *	  THE  AUTHOR  AND	DISTRIBUTORS  SPECIFICALLY	 DISCLAIM	ANY
 *	  WARRANTIES,  INCLUDING,  BUT	NOT  LIMITED  TO,  THE	IMPLIED
 *	  WARRANTIES  OF  MERCHANTABILITY,	FITNESS  FOR  A  PARTICULAR
 *	  PURPOSE,	AND NON-INFRINGEMENT.  THIS SOFTWARE IS PROVIDED ON
 *	  AN "AS IS" BASIS, AND THE AUTHOR	AND  DISTRIBUTORS  HAVE  NO
 *	  OBLIGATION   TO	PROVIDE   MAINTENANCE,	 SUPPORT,  UPDATES,
 *	  ENHANCEMENTS, OR MODIFICATIONS.
 *
 **********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <setjmp.h>

#include "plpgsql.h"
#include "pl.tab.h"

#include "executor/spi.h"
#include "commands/trigger.h"
#include "utils/elog.h"
#include "utils/builtins.h"
#include "fmgr.h"
#include "access/heapam.h"

#include "utils/syscache.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"


/************************************************************
 * Make Warn_restart from tcop/postgres.c visible for us.
 * The longjmp() mechanism of the elog(ERROR,...) makes it
 * impossible for us to call exceptions. But at least I
 * would like some suggestions about where in the PL function
 * the error occured.
 *
 * It's ugly - Jan
 ************************************************************/
#if defined(nextstep)
#define sigjmp_buf		jmp_buf
#define sigsetjmp(x,y)	setjmp(x)
#define siglongjmp		longjmp
#endif

extern sigjmp_buf Warn_restart; /* in tcop/postgres.c */

static PLpgSQL_function *error_info_func = NULL;
static PLpgSQL_stmt *error_info_stmt = NULL;
static char *error_info_text = NULL;


/************************************************************
 * Local function forward declarations
 ************************************************************/
static PLpgSQL_var *copy_var(PLpgSQL_var * var);
static PLpgSQL_rec *copy_rec(PLpgSQL_rec * rec);

static int exec_stmt_block(PLpgSQL_execstate * estate,
				PLpgSQL_stmt_block * block);
static int exec_stmts(PLpgSQL_execstate * estate,
		   PLpgSQL_stmts * stmts);
static int exec_stmt(PLpgSQL_execstate * estate,
		  PLpgSQL_stmt * stmt);
static int exec_stmt_assign(PLpgSQL_execstate * estate,
				 PLpgSQL_stmt_assign * stmt);
static int exec_stmt_if(PLpgSQL_execstate * estate,
			 PLpgSQL_stmt_if * stmt);
static int exec_stmt_loop(PLpgSQL_execstate * estate,
			   PLpgSQL_stmt_loop * stmt);
static int exec_stmt_while(PLpgSQL_execstate * estate,
				PLpgSQL_stmt_while * stmt);
static int exec_stmt_fori(PLpgSQL_execstate * estate,
			   PLpgSQL_stmt_fori * stmt);
static int exec_stmt_fors(PLpgSQL_execstate * estate,
			   PLpgSQL_stmt_fors * stmt);
static int exec_stmt_select(PLpgSQL_execstate * estate,
				 PLpgSQL_stmt_select * stmt);
static int exec_stmt_exit(PLpgSQL_execstate * estate,
			   PLpgSQL_stmt_exit * stmt);
static int exec_stmt_return(PLpgSQL_execstate * estate,
				 PLpgSQL_stmt_return * stmt);
static int exec_stmt_raise(PLpgSQL_execstate * estate,
				PLpgSQL_stmt_raise * stmt);
static int exec_stmt_execsql(PLpgSQL_execstate * estate,
				  PLpgSQL_stmt_execsql * stmt);

static void exec_assign_expr(PLpgSQL_execstate * estate,
				 PLpgSQL_datum * target,
				 PLpgSQL_expr * expr);
static void exec_assign_value(PLpgSQL_execstate * estate,
				  PLpgSQL_datum * target,
				  Datum value, Oid valtype, bool *isNull);
static Datum exec_eval_expr(PLpgSQL_execstate * estate,
			   PLpgSQL_expr * expr,
			   bool *isNull,
			   Oid *rettype);
static int exec_run_select(PLpgSQL_execstate * estate,
				PLpgSQL_expr * expr, int maxtuples);
static void exec_move_row(PLpgSQL_execstate * estate,
			  PLpgSQL_rec * rec,
			  PLpgSQL_row * row,
			  HeapTuple tup, TupleDesc tupdesc);
static Datum exec_cast_value(Datum value, Oid valtype,
				Oid reqtype,
				FmgrInfo *reqinput,
				int16 reqtypmod,
				bool *isnull);
static void exec_set_found(PLpgSQL_execstate * estate, bool state);


/* ----------
 * plpgsql_exec_function	Called by the call handler for
 *				function execution.
 * ----------
 */
Datum
plpgsql_exec_function(PLpgSQL_function * func,
					  FmgrValues *args, bool *isNull)
{
	PLpgSQL_execstate estate;
	int			i;
	sigjmp_buf	save_restart;
	PLpgSQL_function *save_efunc;
	PLpgSQL_stmt *save_estmt;
	char	   *save_etext;

	/* ----------
	 * Setup debug error info and catch elog()
	 * ----------
	 */
	save_efunc = error_info_func;
	save_estmt = error_info_stmt;
	save_etext = error_info_text;

	error_info_func = func;
	error_info_stmt = NULL;
	error_info_text = "while initialization of execution state";

	memcpy(&save_restart, &Warn_restart, sizeof(save_restart));
	if (sigsetjmp(Warn_restart, 1) != 0)
	{
		memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));

		/* ----------
		 * If we are the first of cascaded error catchings,
		 * print where this happened
		 * ----------
		 */
		if (error_info_func != NULL)
		{
			elog(DEBUG, "Last error occured while executing PL/pgSQL function %s",
				 error_info_func->fn_name);
			if (error_info_stmt != NULL)
			{
				char	   *stmttype;

				switch (error_info_stmt->cmd_type)
				{
					case PLPGSQL_STMT_BLOCK:
						stmttype = "blocks variable initialization";
						break;
					case PLPGSQL_STMT_ASSIGN:
						stmttype = "assignment";
						break;
					case PLPGSQL_STMT_IF:
						stmttype = "if";
						break;
					case PLPGSQL_STMT_LOOP:
						stmttype = "loop";
						break;
					case PLPGSQL_STMT_WHILE:
						stmttype = "while";
						break;
					case PLPGSQL_STMT_FORI:
						stmttype = "for with integer loopvar";
						break;
					case PLPGSQL_STMT_FORS:
						stmttype = "for over select rows";
						break;
					case PLPGSQL_STMT_SELECT:
						stmttype = "select into variables";
						break;
					case PLPGSQL_STMT_EXIT:
						stmttype = "exit";
						break;
					case PLPGSQL_STMT_RETURN:
						stmttype = "return";
						break;
					case PLPGSQL_STMT_RAISE:
						stmttype = "raise";
						break;
					case PLPGSQL_STMT_EXECSQL:
						stmttype = "SQL statement";
						break;
					default:
						stmttype = "unknown";
						break;
				}
				elog(DEBUG, "line %d at %s", error_info_stmt->lineno,
					 stmttype);
			}
			else
			{
				if (error_info_text != NULL)
					elog(DEBUG, "%s", error_info_text);
				else
					elog(DEBUG, "no more error information available");
			}

			error_info_func = NULL;
			error_info_stmt = NULL;
			error_info_text = NULL;
		}

		siglongjmp(Warn_restart, 1);
	}


	/* ----------
	 * Setup the execution state
	 * ----------
	 */
	estate.retval = 0;
	estate.retisnull = false;
	estate.rettype = InvalidOid;
	estate.retistuple = func->fn_retistuple;
	estate.retisset = func->fn_retset;
	estate.exitlabel = NULL;

	estate.found_varno = func->found_varno;
	estate.ndatums = func->ndatums;
	estate.datums = palloc(sizeof(PLpgSQL_datum *) * estate.ndatums);

	/* ----------
	 * Make local execution copies of all the datums
	 * ----------
	 */
	for (i = 0; i < func->ndatums; i++)
	{
		switch (func->datums[i]->dtype)
		{
			case PLPGSQL_DTYPE_VAR:
				estate.datums[i] = (PLpgSQL_datum *)
					copy_var((PLpgSQL_var *) (func->datums[i]));
				break;

			case PLPGSQL_DTYPE_REC:
				estate.datums[i] = (PLpgSQL_datum *)
					copy_rec((PLpgSQL_rec *) (func->datums[i]));
				break;

			case PLPGSQL_DTYPE_ROW:
			case PLPGSQL_DTYPE_RECFIELD:
				estate.datums[i] = func->datums[i];
				break;

			default:
				elog(ERROR, "unknown dtype %d in plpgsql_exec_function()",
					 func->datums[i]->dtype);
		}
	}

	/* ----------
	 * Put the actual call argument values into the variables
	 * ----------
	 */
	error_info_text = "while putting call arguments to local variables";
	for (i = 0; i < func->fn_nargs; i++)
	{
		int			n = func->fn_argvarnos[i];

		switch (estate.datums[n]->dtype)
		{
			case PLPGSQL_DTYPE_VAR:
				{
					PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[n];

					var->value = (Datum) (args->data[i]);
					var->isnull = *isNull;
					var->shouldfree = false;
				}
				break;

			case PLPGSQL_DTYPE_ROW:
				{
					HeapTuple	tup;
					TupleDesc	tupdesc;
					PLpgSQL_row *row = (PLpgSQL_row *) estate.datums[n];

					tup = ((TupleTableSlot *) (args->data[i]))->val;
					tupdesc = ((TupleTableSlot *) (args->data[i]))->ttc_tupleDescriptor;

					exec_move_row(&estate, NULL, row, tup, tupdesc);
				}
				break;

			default:
				elog(ERROR, "unknown dtype %d in plpgsql_exec_function()",
					 func->datums[i]->dtype);
		}
	}

	/* ----------
	 * Initialize the other variables to NULL values for now.
	 * The default values are set when the blocks are entered.
	 * ----------
	 */
	error_info_text = "while initializing local variables to NULL";
	for (i = estate.found_varno; i < estate.ndatums; i++)
	{
		switch (estate.datums[i]->dtype)
		{
			case PLPGSQL_DTYPE_VAR:
				{
					PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[i];

					var->value = 0;
					var->isnull = true;
					var->shouldfree = false;
				}
				break;

			case PLPGSQL_DTYPE_ROW:
			case PLPGSQL_DTYPE_REC:
			case PLPGSQL_DTYPE_RECFIELD:
				break;

			default:
				elog(ERROR, "unknown dtype %d in plpgsql_exec_function()",
					 func->datums[i]->dtype);
		}
	}

	/* ----------
	 * Set the magic variable FOUND to false
	 * ----------
	 */
	exec_set_found(&estate, false);

	/* ----------
	 * Now call the toplevel block of statements
	 * ----------
	 */
	error_info_text = NULL;
	error_info_stmt = (PLpgSQL_stmt *) (func->action);
	if (exec_stmt_block(&estate, func->action) != PLPGSQL_RC_RETURN)
	{
		error_info_stmt = NULL;
		error_info_text = "at END of toplevel PL block";
		elog(ERROR, "control reaches end of function without RETURN");
	}

	/* ----------
	 * We got a return value - process it
	 * ----------
	 */
	error_info_stmt = NULL;
	error_info_text = "while casting return value to functions return type";

	*isNull = estate.retisnull;

	if (!estate.retistuple)
	{
		estate.retval = exec_cast_value(estate.retval, estate.rettype,
							  func->fn_rettype, &(func->fn_retinput), -1,
										isNull);

		/* ----------
		 * If the functions return type isn't by value,
		 * copy the value into upper executor memory context.
		 * ----------
		 */
		if (!*isNull && !func->fn_retbyval)
		{
			int			len;
			Datum		tmp;

			if (func->fn_rettyplen < 0)
				len = VARSIZE(estate.retval);
			else
				len = func->fn_rettyplen;

			tmp = (Datum) SPI_palloc(len);
			memcpy((void *) tmp, (void *) estate.retval, len);
			estate.retval = tmp;
		}
	}

	/* ----------
	 * Restore the previous error info and elog() jump target
	 * ----------
	 */
	error_info_func = save_efunc;
	error_info_stmt = save_estmt;
	error_info_text = save_etext;
	memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));

	/* ----------
	 * Return the functions result
	 * ----------
	 */
	return estate.retval;
}


/* ----------
 * plpgsql_exec_trigger		Called by the call handler for
 *				trigger execution.
 * ----------
 */
HeapTuple
plpgsql_exec_trigger(PLpgSQL_function * func,
					 TriggerData *trigdata)
{
	PLpgSQL_execstate estate;
	int			i;
	sigjmp_buf	save_restart;
	PLpgSQL_function *save_efunc;
	PLpgSQL_stmt *save_estmt;
	char	   *save_etext;
	PLpgSQL_rec *rec_new;
	PLpgSQL_rec *rec_old;
	PLpgSQL_var *var;
	HeapTuple	rettup;

	/* ----------
	 * Setup debug error info and catch elog()
	 * ----------
	 */
	save_efunc = error_info_func;
	save_estmt = error_info_stmt;
	save_etext = error_info_text;

	error_info_func = func;
	error_info_stmt = NULL;
	error_info_text = "while initialization of execution state";

	memcpy(&save_restart, &Warn_restart, sizeof(save_restart));
	if (sigsetjmp(Warn_restart, 1) != 0)
	{
		memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));

		/* ----------
		 * If we are the first of cascaded error catchings,
		 * print where this happened
		 * ----------
		 */
		if (error_info_func != NULL)
		{
			elog(DEBUG, "Last error occured while executing PL/pgSQL function %s",
				 error_info_func->fn_name);
			if (error_info_stmt != NULL)
			{
				char	   *stmttype;

				switch (error_info_stmt->cmd_type)
				{
					case PLPGSQL_STMT_BLOCK:
						stmttype = "blocks variable initialization";
						break;
					case PLPGSQL_STMT_ASSIGN:
						stmttype = "assignment";
						break;
					case PLPGSQL_STMT_IF:
						stmttype = "if";
						break;
					case PLPGSQL_STMT_LOOP:
						stmttype = "loop";
						break;
					case PLPGSQL_STMT_WHILE:
						stmttype = "while";
						break;
					case PLPGSQL_STMT_FORI:
						stmttype = "for with integer loopvar";
						break;
					case PLPGSQL_STMT_FORS:
						stmttype = "for over select rows";
						break;
					case PLPGSQL_STMT_SELECT:
						stmttype = "select into variables";
						break;
					case PLPGSQL_STMT_EXIT:
						stmttype = "exit";
						break;
					case PLPGSQL_STMT_RETURN:
						stmttype = "return";
						break;
					case PLPGSQL_STMT_RAISE:
						stmttype = "raise";
						break;
					case PLPGSQL_STMT_EXECSQL:
						stmttype = "SQL statement";
						break;
					default:
						stmttype = "unknown";
						break;
				}
				elog(DEBUG, "line %d at %s", error_info_stmt->lineno,
					 stmttype);
			}
			else
			{
				if (error_info_text != NULL)
					elog(DEBUG, "%s", error_info_text);
				else
					elog(DEBUG, "no more error information available");
			}

			error_info_func = NULL;
			error_info_stmt = NULL;
			error_info_text = NULL;
		}

		siglongjmp(Warn_restart, 1);
	}


	/* ----------
	 * Setup the execution state
	 * ----------
	 */
	estate.retval = 0;
	estate.retisnull = false;
	estate.rettype = InvalidOid;
	estate.retistuple = func->fn_retistuple;
	estate.retisset = func->fn_retset;
	estate.exitlabel = NULL;

	estate.found_varno = func->found_varno;
	estate.ndatums = func->ndatums;
	estate.datums = palloc(sizeof(PLpgSQL_datum *) * estate.ndatums);

	/* ----------
	 * Make local execution copies of all the datums
	 * ----------
	 */
	for (i = 0; i < func->ndatums; i++)
	{
		switch (func->datums[i]->dtype)
		{
			case PLPGSQL_DTYPE_VAR:
				estate.datums[i] = (PLpgSQL_datum *)
					copy_var((PLpgSQL_var *) (func->datums[i]));
				break;

			case PLPGSQL_DTYPE_REC:
				estate.datums[i] = (PLpgSQL_datum *)
					copy_rec((PLpgSQL_rec *) (func->datums[i]));
				break;

			case PLPGSQL_DTYPE_ROW:
			case PLPGSQL_DTYPE_RECFIELD:
			case PLPGSQL_DTYPE_TRIGARG:
				estate.datums[i] = func->datums[i];
				break;

			default:
				elog(ERROR, "unknown dtype %d in plpgsql_exec_function()",
					 func->datums[i]->dtype);
		}
	}

	/* ----------
	 * Put the trig and new tuples into the records
	 * and set the tg_op variable
	 * ----------
	 */
	rec_new = (PLpgSQL_rec *) (estate.datums[func->new_varno]);
	rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
	var = (PLpgSQL_var *) (estate.datums[func->tg_op_varno]);
	var->isnull = false;

	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
	{
		rec_new->tup = trigdata->tg_trigtuple;
		rec_new->tupdesc = trigdata->tg_relation->rd_att;
		rec_old->tup = NULL;
		rec_old->tupdesc = NULL;
		var->value = (Datum) textin("INSERT");
	}
	else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
	{
		rec_new->tup = trigdata->tg_newtuple;
		rec_new->tupdesc = trigdata->tg_relation->rd_att;
		rec_old->tup = trigdata->tg_trigtuple;
		rec_old->tupdesc = trigdata->tg_relation->rd_att;
		var->value = (Datum) textin("UPDATE");
	}
	else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
	{
		rec_new->tup = NULL;
		rec_new->tupdesc = NULL;
		rec_old->tup = trigdata->tg_trigtuple;
		rec_old->tupdesc = trigdata->tg_relation->rd_att;
		var->value = (Datum) textin("DELETE");
	}
	else
	{
		rec_new->tup = NULL;
		rec_new->tupdesc = NULL;
		var->value = (Datum) textin("UNKNOWN");
	}

	/* ----------
	 * Fill all the other special tg_ variables
	 * ----------
	 */
	var = (PLpgSQL_var *) (estate.datums[func->tg_name_varno]);
	var->isnull = false;
	var->value = (Datum) namein(trigdata->tg_trigger->tgname);

	var = (PLpgSQL_var *) (estate.datums[func->tg_when_varno]);
	var->isnull = false;
	if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
		var->value = (Datum) textin("BEFORE");
	else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
		var->value = (Datum) textin("AFTER");
	else
		var->value = (Datum) textin("UNKNOWN");

	var = (PLpgSQL_var *) (estate.datums[func->tg_level_varno]);
	var->isnull = false;
	if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
		var->value = (Datum) textin("ROW");
	else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
		var->value = (Datum) textin("STATEMENT");
	else
		var->value = (Datum) textin("UNKNOWN");

	var = (PLpgSQL_var *) (estate.datums[func->tg_relid_varno]);
	var->isnull = false;
	var->value = (Datum) (trigdata->tg_relation->rd_id);

	var = (PLpgSQL_var *) (estate.datums[func->tg_relname_varno]);
	var->isnull = false;
	var->value = (Datum) namein(nameout(&(trigdata->tg_relation->rd_rel->relname)));

	var = (PLpgSQL_var *) (estate.datums[func->tg_nargs_varno]);
	var->isnull = false;
	var->value = (Datum) (trigdata->tg_trigger->tgnargs);

	/* ----------
	 * Put the actual call argument values into the special
	 * execution state variables
	 * ----------
	 */
	error_info_text = "while putting call arguments to local variables";
	estate.trig_nargs = trigdata->tg_trigger->tgnargs;
	if (estate.trig_nargs == 0)
		estate.trig_argv = NULL;
	else
	{
		estate.trig_argv = palloc(sizeof(Datum) * estate.trig_nargs);
		for (i = 0; i < trigdata->tg_trigger->tgnargs; i++)
			estate.trig_argv[i] = (Datum) textin(trigdata->tg_trigger->tgargs[i]);
	}

	/* ----------
	 * Initialize the other variables to NULL values for now.
	 * The default values are set when the blocks are entered.
	 * ----------
	 */
	error_info_text = "while initializing local variables to NULL";
	for (i = estate.found_varno; i < estate.ndatums; i++)
	{
		switch (estate.datums[i]->dtype)
		{
			case PLPGSQL_DTYPE_VAR:
				{
					PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[i];

					var->value = 0;
					var->isnull = true;
					var->shouldfree = false;
				}
				break;

			case PLPGSQL_DTYPE_ROW:
			case PLPGSQL_DTYPE_REC:
			case PLPGSQL_DTYPE_RECFIELD:
			case PLPGSQL_DTYPE_TRIGARG:
				break;

			default:
				elog(ERROR, "unknown dtype %d in plpgsql_exec_trigger()",
					 func->datums[i]->dtype);
		}
	}

	/* ----------
	 * Set the magic variable FOUND to false
	 * ----------
	 */
	exec_set_found(&estate, false);

	/* ----------
	 * Now call the toplevel block of statements
	 * ----------
	 */
	error_info_text = NULL;
	error_info_stmt = (PLpgSQL_stmt *) (func->action);
	if (exec_stmt_block(&estate, func->action) != PLPGSQL_RC_RETURN)
	{
		error_info_stmt = NULL;
		error_info_text = "at END of toplevel PL block";
		elog(ERROR, "control reaches end of trigger procedure without RETURN");
	}

	/* ----------
	 * Check that the returned tuple structure has the same attributes,
	 * the relation that fired the trigger has.
	 *
	 * XXX This way it is possible, that the trigger returns a tuple
	 *	   where attributes don't have the correct atttypmod's length.
	 *	   It's up to the trigger's programmer to ensure that this
	 *	   doesn't happen. Jan
	 * ----------
	 */
	if (estate.retisnull)
		rettup = NULL;
	else
	{
		TupleDesc	td1 = trigdata->tg_relation->rd_att;
		TupleDesc	td2 = estate.rettupdesc;
		int			i;

		if (td1->natts != td2->natts)
			elog(ERROR, "returned tuple structure doesn't match table of trigger event");
		for (i = 1; i <= td1->natts; i++)
		{
			if (SPI_gettypeid(td1, i) != SPI_gettypeid(td2, i))
				elog(ERROR, "returned tuple structure doesn't match table of trigger event");
		}

		rettup = SPI_copytuple((HeapTuple) (estate.retval));
	}

	/* ----------
	 * Restore the previous error info and elog() jump target
	 * ----------
	 */
	error_info_func = save_efunc;
	error_info_stmt = save_estmt;
	error_info_text = save_etext;
	memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));

	/* ----------
	 * Return the triggers result
	 * ----------
	 */
	return rettup;
}


/* ----------
 * Support functions for copying local execution variables
 * ----------
 */
static PLpgSQL_var *
copy_var(PLpgSQL_var * var)
{
	PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var));

	memcpy(new, var, sizeof(PLpgSQL_var));

	return new;
}


static PLpgSQL_rec *
copy_rec(PLpgSQL_rec * rec)
{
	PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec));

	memcpy(new, rec, sizeof(PLpgSQL_rec));

	return new;
}


/* ----------
 * exec_stmt_block			Execute a block of statements
 * ----------
 */
static int
exec_stmt_block(PLpgSQL_execstate * estate, PLpgSQL_stmt_block * block)
{
	int			rc;
	int			i;
	int			n;

	/* ----------
	 * First initialize all variables declared in this block
	 * ----------
	 */
	for (i = 0; i < block->n_initvars; i++)
	{
		n = block->initvarnos[i];

		switch (estate->datums[n]->dtype)
		{
			case PLPGSQL_DTYPE_VAR:
				{
					PLpgSQL_var *var = (PLpgSQL_var *) (estate->datums[n]);

					if (!var->isconst || var->isnull)
					{
						if (var->default_val == NULL)
						{
							var->value = (Datum) 0;
							var->isnull = true;
							if (var->notnull)
								elog(ERROR, "variable '%s' declared NOT NULL cannot default to NULL", var->refname);
						}
						else
						{
							exec_assign_expr(estate, (PLpgSQL_datum *) var,
											 var->default_val);
						}
					}
				}
				break;

			case PLPGSQL_DTYPE_REC:
				{
					PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[n]);

					rec->tup = NULL;
					rec->tupdesc = NULL;
				}
				break;

			case PLPGSQL_DTYPE_RECFIELD:
				break;

			default:
				elog(ERROR, "unknown dtype %d in exec_stmt_block()", estate->datums[n]->dtype);
		}

	}

	/* ----------
	 * Execute the statements in the block's body
	 * ----------
	 */
	rc = exec_stmts(estate, block->body);

	/* ----------
	 * Handle the return code.
	 * ----------
	 */
	switch (rc)
	{
		case PLPGSQL_RC_OK:
			return PLPGSQL_RC_OK;

		case PLPGSQL_RC_EXIT:
			if (estate->exitlabel == NULL)
				return PLPGSQL_RC_OK;
			if (block->label == NULL)
				return PLPGSQL_RC_EXIT;
			if (strcmp(block->label, estate->exitlabel))
				return PLPGSQL_RC_EXIT;
			estate->exitlabel = NULL;
			return PLPGSQL_RC_OK;

		case PLPGSQL_RC_RETURN:
			return PLPGSQL_RC_RETURN;

		default:
			elog(ERROR, "unknown rc %d from exec_stmt()", rc);
	}

	return PLPGSQL_RC_OK;
}


/* ----------
 * exec_stmts			Iterate over a list of statements
 *				as long as their return code is OK
 * ----------
 */
static int
exec_stmts(PLpgSQL_execstate * estate, PLpgSQL_stmts * stmts)
{
	int			rc;
	int			i;

	for (i = 0; i < stmts->stmts_used; i++)
	{
		rc = exec_stmt(estate, (PLpgSQL_stmt *) (stmts->stmts[i]));
		if (rc != PLPGSQL_RC_OK)
			return rc;
	}

	return PLPGSQL_RC_OK;
}


/* ----------
 * exec_stmt			Distribute one statement to the statements
 *				type specific execution function.
 * ----------
 */
static int
exec_stmt(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt)
{
	PLpgSQL_stmt *save_estmt;
	int			rc = -1;

	save_estmt = error_info_stmt;
	error_info_stmt = stmt;

	switch (stmt->cmd_type)
	{
		case PLPGSQL_STMT_BLOCK:
			rc = exec_stmt_block(estate, (PLpgSQL_stmt_block *) stmt);
			break;

		case PLPGSQL_STMT_ASSIGN:
			rc = exec_stmt_assign(estate, (PLpgSQL_stmt_assign *) stmt);
			break;

		case PLPGSQL_STMT_IF:
			rc = exec_stmt_if(estate, (PLpgSQL_stmt_if *) stmt);
			break;

		case PLPGSQL_STMT_LOOP:
			rc = exec_stmt_loop(estate, (PLpgSQL_stmt_loop *) stmt);
			break;

		case PLPGSQL_STMT_WHILE:
			rc = exec_stmt_while(estate, (PLpgSQL_stmt_while *) stmt);
			break;

		case PLPGSQL_STMT_FORI:
			rc = exec_stmt_fori(estate, (PLpgSQL_stmt_fori *) stmt);
			break;

		case PLPGSQL_STMT_FORS:
			rc = exec_stmt_fors(estate, (PLpgSQL_stmt_fors *) stmt);
			break;

		case PLPGSQL_STMT_SELECT:
			rc = exec_stmt_select(estate, (PLpgSQL_stmt_select *) stmt);
			break;

		case PLPGSQL_STMT_EXIT:
			rc = exec_stmt_exit(estate, (PLpgSQL_stmt_exit *) stmt);
			break;

		case PLPGSQL_STMT_RETURN:
			rc = exec_stmt_return(estate, (PLpgSQL_stmt_return *) stmt);
			break;

		case PLPGSQL_STMT_RAISE:
			rc = exec_stmt_raise(estate, (PLpgSQL_stmt_raise *) stmt);
			break;

		case PLPGSQL_STMT_EXECSQL:
			rc = exec_stmt_execsql(estate, (PLpgSQL_stmt_execsql *) stmt);
			break;

		default:
			error_info_stmt = save_estmt;
			elog(ERROR, "unknown cmdtype %d in exec_stmt",
				 stmt->cmd_type);
	}

	error_info_stmt = save_estmt;

	return rc;
}


/* ----------
 * exec_stmt_assign			Evaluate an expression and
 *					put the result into a variable.
 * ----------
 */
static int
exec_stmt_assign(PLpgSQL_execstate * estate, PLpgSQL_stmt_assign * stmt)
{
	if (stmt->varno < 0)
		exec_assign_expr(estate, NULL, stmt->expr);
	else
		exec_assign_expr(estate, estate->datums[stmt->varno], stmt->expr);

	return PLPGSQL_RC_OK;
}


/* ----------
 * exec_stmt_if				Evaluate a bool expression and
 *					execute the true or false body
 *					conditionally.
 * ----------
 */
static int
exec_stmt_if(PLpgSQL_execstate * estate, PLpgSQL_stmt_if * stmt)
{
	Datum		value;
	Oid			valtype;
	bool		isnull = false;

	value = exec_eval_expr(estate, stmt->cond, &isnull, &valtype);

	if (value)
	{
		if (stmt->true_body != NULL)
			return exec_stmts(estate, stmt->true_body);
	}
	else
	{
		if (stmt->false_body != NULL)
			return exec_stmts(estate, stmt->false_body);
	}

	return PLPGSQL_RC_OK;
}


/* ----------
 * exec_stmt_loop			Loop over statements until
 *					an exit occurs.
 * ----------
 */
static int
exec_stmt_loop(PLpgSQL_execstate * estate, PLpgSQL_stmt_loop * stmt)
{
	int			rc;

	for (;;)
	{
		rc = exec_stmts(estate, stmt->body);

		switch (rc)
		{
			case PLPGSQL_RC_OK:
				break;

			case PLPGSQL_RC_EXIT:
				if (estate->exitlabel == NULL)
					return PLPGSQL_RC_OK;
				if (stmt->label == NULL)
					return PLPGSQL_RC_EXIT;
				if (strcmp(stmt->label, estate->exitlabel))
					return PLPGSQL_RC_EXIT;
				estate->exitlabel = NULL;
				return PLPGSQL_RC_OK;

			case PLPGSQL_RC_RETURN:
				return PLPGSQL_RC_RETURN;

			default:
				elog(ERROR, "unknown rc %d from exec_stmts()", rc);
		}
	}

	return PLPGSQL_RC_OK;
}


/* ----------
 * exec_stmt_while			Loop over statements as long
 *					as an expression evaluates to
 *					true or an exit occurs.
 * ----------
 */
static int
exec_stmt_while(PLpgSQL_execstate * estate, PLpgSQL_stmt_while * stmt)
{
	Datum		value;
	Oid			valtype;
	bool		isnull = false;
	int			rc;

	for (;;)
	{
		value = exec_eval_expr(estate, stmt->cond, &isnull, &valtype);
		if (!value)
			break;

		rc = exec_stmts(estate, stmt->body);

		switch (rc)
		{
			case PLPGSQL_RC_OK:
				break;

			case PLPGSQL_RC_EXIT:
				if (estate->exitlabel == NULL)
					return PLPGSQL_RC_OK;
				if (stmt->label == NULL)
					return PLPGSQL_RC_EXIT;
				if (strcmp(stmt->label, estate->exitlabel))
					return PLPGSQL_RC_EXIT;
				estate->exitlabel = NULL;
				return PLPGSQL_RC_OK;

			case PLPGSQL_RC_RETURN:
				return PLPGSQL_RC_RETURN;

			default:
				elog(ERROR, "unknown rc %d from exec_stmts()", rc);
		}
	}

	return PLPGSQL_RC_OK;
}


/* ----------
 * exec_stmt_fori			Iterate an integer variable
 *					from a lower to an upper value.
 *					Loop can be left with exit.
 * ----------
 */
static int
exec_stmt_fori(PLpgSQL_execstate * estate, PLpgSQL_stmt_fori * stmt)
{
	PLpgSQL_var *var;
	Datum		value;
	Oid			valtype;
	bool		isnull = false;
	int			rc;

	/* ----------
	 * Get the value of the lower bound into the loop var
	 * ----------
	 */
	value = exec_eval_expr(estate, stmt->lower, &isnull, &valtype);
	var = (PLpgSQL_var *) (estate->datums[stmt->var->varno]);

	value = exec_cast_value(value, valtype, var->datatype->typoid,
							&(var->datatype->typinput),
							var->datatype->atttypmod, &isnull);
	if (isnull)
		elog(ERROR, "lower bound of FOR loop cannot be NULL");
	var->value = value;
	var->isnull = false;

	/* ----------
	 * Get the value of the upper bound
	 * ----------
	 */
	value = exec_eval_expr(estate, stmt->upper, &isnull, &valtype);
	value = exec_cast_value(value, valtype, var->datatype->typoid,
							&(var->datatype->typinput),
							var->datatype->atttypmod, &isnull);
	if (isnull)
		elog(ERROR, "upper bound of FOR loop cannot be NULL");

	/* ----------
	 * Now do the loop
	 * ----------
	 */
	exec_set_found(estate, false);
	for (;;)
	{
		/* ----------
		 * Check bounds
		 * ----------
		 */
		if (stmt->reverse)
		{
			if ((int4) (var->value) < (int4) value)
				break;
		}
		else
		{
			if ((int4) (var->value) > (int4) value)
				break;
		}
		exec_set_found(estate, true);

		/* ----------
		 * Execute the statements
		 * ----------
		 */
		rc = exec_stmts(estate, stmt->body);

		/* ----------
		 * Check returncode
		 * ----------
		 */
		switch (rc)
		{
			case PLPGSQL_RC_OK:
				break;

			case PLPGSQL_RC_EXIT:
				if (estate->exitlabel == NULL)
					return PLPGSQL_RC_OK;
				if (stmt->label == NULL)
					return PLPGSQL_RC_EXIT;
				if (strcmp(stmt->label, estate->exitlabel))
					return PLPGSQL_RC_EXIT;
				estate->exitlabel = NULL;
				return PLPGSQL_RC_OK;

			case PLPGSQL_RC_RETURN:
				return PLPGSQL_RC_RETURN;

			default:
				elog(ERROR, "unknown rc %d from exec_stmts()", rc);
		}

		/* ----------
		 * Increase/decrease loop var
		 * ----------
		 */
		if (stmt->reverse)
			var->value--;
		else
			var->value++;
	}

	return PLPGSQL_RC_OK;
}


/* ----------
 * exec_stmt_fors			Execute a query, assign each
 *					tuple to a record or row and
 *					execute a group of statements
 *					for it.
 * ----------
 */
static int
exec_stmt_fors(PLpgSQL_execstate * estate, PLpgSQL_stmt_fors * stmt)
{
	PLpgSQL_rec *rec = NULL;
	PLpgSQL_row *row = NULL;
	SPITupleTable *tuptab;
	int			rc;
	int			i;
	int			n;

	/* ----------
	 * Initialize the global found variable to false
	 * ----------
	 */
	exec_set_found(estate, false);

	/* ----------
	 * Determine if we assign to a record or a row
	 * ----------
	 */
	if (stmt->rec != NULL)
		rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
	else
	{
		if (stmt->row != NULL)
			row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
		else
			elog(ERROR, "unsupported target in exec_stmt_fors()");
	}

	/* ----------
	 * Run the query
	 * ----------
	 */
	exec_run_select(estate, stmt->query, 0);
	n = SPI_processed;

	/* ----------
	 * If the query didn't return any row, set the target
	 * to NULL and return.
	 * ----------
	 */
	if (n == 0)
	{
		exec_move_row(estate, rec, row, NULL, NULL);
		return PLPGSQL_RC_OK;
	}

	/* ----------
	 * There are tuples, so set found to true
	 * ----------
	 */
	exec_set_found(estate, true);

	/* ----------
	 * Now do the loop
	 * ----------
	 */
	tuptab = SPI_tuptable;
	SPI_tuptable = NULL;

	for (i = 0; i < n; i++)
	{
		/* ----------
		 * Assign the tuple to the target
		 * ----------
		 */
		exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);

		/* ----------
		 * Execute the statements
		 * ----------
		 */
		rc = exec_stmts(estate, stmt->body);

		/* ----------
		 * Check returncode
		 * ----------
		 */
		switch (rc)
		{
			case PLPGSQL_RC_OK:
				break;

			case PLPGSQL_RC_EXIT:
				if (estate->exitlabel == NULL)
					return PLPGSQL_RC_OK;
				if (stmt->label == NULL)
					return PLPGSQL_RC_EXIT;
				if (strcmp(stmt->label, estate->exitlabel))
					return PLPGSQL_RC_EXIT;
				estate->exitlabel = NULL;
				return PLPGSQL_RC_OK;

			case PLPGSQL_RC_RETURN:
				return PLPGSQL_RC_RETURN;

			default:
				elog(ERROR, "unknown rc %d from exec_stmts()", rc);
		}
	}

	return PLPGSQL_RC_OK;
}


/* ----------
 * exec_stmt_select			Run a query and assign the first
 *					row to a record or rowtype.
 *					 ----------
 */
static int
exec_stmt_select(PLpgSQL_execstate * estate, PLpgSQL_stmt_select * stmt)
{
	PLpgSQL_rec *rec = NULL;
	PLpgSQL_row *row = NULL;
	SPITupleTable *tuptab;
	int			n;

	/* ----------
	 * Initialize the global found variable to false
	 * ----------
	 */
	exec_set_found(estate, false);

	/* ----------
	 * Determine if we assign to a record or a row
	 * ----------
	 */
	if (stmt->rec != NULL)
		rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
	else
	{
		if (stmt->row != NULL)
			row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
		else
			elog(ERROR, "unsupported target in exec_stmt_select()");
	}

	/* ----------
	 * Run the query
	 * ----------
	 */
	exec_run_select(estate, stmt->query, 1);
	n = SPI_processed;

	/* ----------
	 * If the query didn't return any row, set the target
	 * to NULL and return.
	 * ----------
	 */
	if (n == 0)
	{
		exec_move_row(estate, rec, row, NULL, NULL);
		return PLPGSQL_RC_OK;
	}

	/* ----------
	 * Put the result into the target and set found to true
	 * ----------
	 */
	tuptab = SPI_tuptable;
	SPI_tuptable = NULL;

	exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);

	exec_set_found(estate, true);

	return PLPGSQL_RC_OK;
}


/* ----------
 * exec_stmt_exit			Start exiting loop(s) or blocks
 * ----------
 */
static int
exec_stmt_exit(PLpgSQL_execstate * estate, PLpgSQL_stmt_exit * stmt)
{
	Datum		value;
	Oid			valtype;
	bool		isnull = false;

	/* ----------
	 * If the exit has a condition, check that it's true
	 * ----------
	 */
	if (stmt->cond != NULL)
	{
		value = exec_eval_expr(estate, stmt->cond, &isnull, &valtype);
		if (!value)
			return PLPGSQL_RC_OK;
	}

	estate->exitlabel = stmt->label;
	return PLPGSQL_RC_EXIT;
}


/* ----------
 * exec_stmt_return			Evaluate an expression and start
 *					returning from the function.
 * ----------
 */
static int
exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt)
{
	if (estate->retistuple)
	{
		if (stmt->retrecno >= 0)
		{
			PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt->retrecno]);

			estate->retval = (Datum) (rec->tup);
			estate->rettupdesc = rec->tupdesc;
			estate->retisnull = !HeapTupleIsValid(rec->tup);

			return PLPGSQL_RC_RETURN;
		}

		if (stmt->expr == NULL)
		{
			estate->retval = (Datum) 0;
			estate->rettupdesc = NULL;
			estate->retisnull = true;
		}
		else
		{
			exec_run_select(estate, stmt->expr, 1);
			estate->retval = (Datum) SPI_copytuple(SPI_tuptable->vals[0]);
			estate->rettupdesc = SPI_tuptable->tupdesc;
			estate->retisnull = false;
		}
		return PLPGSQL_RC_RETURN;
	}

	estate->retval = exec_eval_expr(estate, stmt->expr,
									&(estate->retisnull),
									&(estate->rettype));

	return PLPGSQL_RC_RETURN;
}


/* ----------
 * exec_stmt_raise			Build a message and throw it with
 *					elog()
 * ----------
 */
static int
exec_stmt_raise(PLpgSQL_execstate * estate, PLpgSQL_stmt_raise * stmt)
{
	HeapTuple	typetup;
	Form_pg_type typeStruct;
	FmgrInfo	finfo_output;
	char	   *extval;
	int			pidx = 0;
	char		c[2] = {0, 0};
	char	   *cp;
	PLpgSQL_dstring ds;
	PLpgSQL_var *var;
	PLpgSQL_rec *rec;
	PLpgSQL_recfield *recfield;
	int			fno;

	plpgsql_dstring_init(&ds);

	for (cp = stmt->message; *cp; cp++)
	{
		/* ----------
		 * Occurences of a single % are replaced by the next
		 * arguments external representation. Double %'s are
		 * left as is so elog() will also don't touch them.
		 * ----------
		 */
		if ((c[0] = *cp) == '%')
		{
			cp++;
			if (*cp == '%')
			{
				plpgsql_dstring_append(&ds, c);
				plpgsql_dstring_append(&ds, c);
				continue;
			}
			cp--;
			if (pidx >= stmt->nparams)
			{
				plpgsql_dstring_append(&ds, c);
				plpgsql_dstring_append(&ds, c);
				continue;
			}
			switch (estate->datums[stmt->params[pidx]]->dtype)
			{
				case PLPGSQL_DTYPE_VAR:
					var = (PLpgSQL_var *)
						(estate->datums[stmt->params[pidx]]);
					if (var->isnull)
						extval = "<NULL>";
					else
					{
						typetup = SearchSysCacheTuple(TYPOID,
						ObjectIdGetDatum(var->datatype->typoid), 0, 0, 0);
						if (!HeapTupleIsValid(typetup))
							elog(ERROR, "cache lookup for type %d failed (1)", var->datatype->typoid);
						typeStruct = (Form_pg_type) GETSTRUCT(typetup);

						fmgr_info(typeStruct->typoutput, &finfo_output);
						extval = (char *) (*fmgr_faddr(&finfo_output)) (var->value, &(var->isnull), var->datatype->atttypmod);
					}
					plpgsql_dstring_append(&ds, extval);
					break;

				case PLPGSQL_DTYPE_RECFIELD:
					recfield = (PLpgSQL_recfield *)
						(estate->datums[stmt->params[pidx]]);
					rec = (PLpgSQL_rec *)
						(estate->datums[recfield->recno]);
					if (!HeapTupleIsValid(rec->tup))
						extval = "<NULL>";
					else
					{
						fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
						if (fno == SPI_ERROR_NOATTRIBUTE)
							elog(ERROR, "record %s has no field %s", rec->refname, recfield->fieldname);
						extval = SPI_getvalue(rec->tup, rec->tupdesc, fno);
					}
					plpgsql_dstring_append(&ds, extval);
					break;

				case PLPGSQL_DTYPE_TRIGARG:
					{
						PLpgSQL_trigarg *trigarg;
						int			value;
						Oid			valtype;
						bool		valisnull = false;

						trigarg = (PLpgSQL_trigarg *)
							(estate->datums[stmt->params[pidx]]);
						value = (int) exec_eval_expr(estate, trigarg->argnum,
												   &valisnull, &valtype);
						if (valisnull)
							extval = "<INDEX_IS_NULL>";
						else
						{
							if (value < 0 || value >= estate->trig_nargs)
								extval = "<OUT_OF_RANGE>";
							else
								extval = textout((text *) (estate->trig_argv[value]));
						}
						plpgsql_dstring_append(&ds, extval);
					}
					break;

				default:
					c[0] = '?';
					plpgsql_dstring_append(&ds, c);
					break;
			}
			pidx++;
			continue;
		}

		/* ----------
		 * Occurences of single ' are removed. double ' are reduced
		 * to single ones.
		 * ----------
		 */
		if (*cp == '\'')
		{
			cp++;
			if (*cp == '\'')
				plpgsql_dstring_append(&ds, c);
			else
				cp--;
			continue;
		}
		plpgsql_dstring_append(&ds, c);
	}

	/* ----------
	 * Now suppress debug info and throw the elog()
	 * ----------
	 */
	if (stmt->elog_level == ERROR)
	{
		error_info_func = NULL;
		error_info_stmt = NULL;
		error_info_text = NULL;
	}
	elog(stmt->elog_level, "%s", plpgsql_dstring_get(&ds));
	plpgsql_dstring_free(&ds);

	return PLPGSQL_RC_OK;
}


/* ----------
 * exec_stmt_execsql			Execute an SQL statement not
 *					returning any data.
 * ----------
 */
static int
exec_stmt_execsql(PLpgSQL_execstate * estate,
				  PLpgSQL_stmt_execsql * stmt)
{
	PLpgSQL_var *var;
	PLpgSQL_rec *rec;
	PLpgSQL_recfield *recfield;
	PLpgSQL_trigarg *trigarg;
	int			tgargno;
	Oid			tgargoid;
	int			fno;
	int			i;
	Datum	   *values;
	char	   *nulls;
	int			rc;
	PLpgSQL_expr *expr = stmt->sqlstmt;
	bool		isnull;

	/* ----------
	 * On the first call for this expression generate the plan
	 * ----------
	 */
	if (expr->plan == NULL)
	{
		void	   *plan;
		Oid		   *argtypes;

		argtypes = malloc(sizeof(Oid *) * (expr->nparams + 1));

		for (i = 0; i < expr->nparams; i++)
		{
			switch (estate->datums[expr->params[i]]->dtype)
			{
				case PLPGSQL_DTYPE_VAR:
					var = (PLpgSQL_var *) (estate->datums[expr->params[i]]);
					argtypes[i] = var->datatype->typoid;
					break;

				case PLPGSQL_DTYPE_RECFIELD:
					recfield = (PLpgSQL_recfield *) (estate->datums[expr->params[i]]);
					rec = (PLpgSQL_rec *) (estate->datums[recfield->recno]);

					if (!HeapTupleIsValid(rec->tup))
						elog(ERROR, "record %s is unassigned yet", rec->refname);
					fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
					if (fno == SPI_ERROR_NOATTRIBUTE)
						elog(ERROR, "record %s has no field %s", rec->refname, recfield->fieldname);
					argtypes[i] = SPI_gettypeid(rec->tupdesc, fno);
					break;

				case PLPGSQL_DTYPE_TRIGARG:
					argtypes[i] = (Oid) TEXTOID;
					break;

				default:
					elog(ERROR, "unknown parameter dtype %d in exec_stmt_execsql()", estate->datums[expr->params[i]]->dtype);
			}
		}

		plan = SPI_prepare(expr->query, expr->nparams, argtypes);
		if (plan == NULL)
			elog(ERROR, "SPI_prepare() failed on \"%s\"", expr->query);
		expr->plan = SPI_saveplan(plan);
		expr->plan_argtypes = argtypes;
	}

	/* ----------
	 * Now build up the values and nulls arguments for SPI_execp()
	 * ----------
	 */
	values = palloc(sizeof(Datum) * (expr->nparams + 1));
	nulls = palloc(expr->nparams + 1);

	for (i = 0; i < expr->nparams; i++)
	{
		switch (estate->datums[expr->params[i]]->dtype)
		{
			case PLPGSQL_DTYPE_VAR:
				var = (PLpgSQL_var *) (estate->datums[expr->params[i]]);
				values[i] = var->value;
				if (var->isnull)
					nulls[i] = 'n';
				else
					nulls[i] = ' ';
				break;

			case PLPGSQL_DTYPE_RECFIELD:
				recfield = (PLpgSQL_recfield *) (estate->datums[expr->params[i]]);
				rec = (PLpgSQL_rec *) (estate->datums[recfield->recno]);

				if (!HeapTupleIsValid(rec->tup))
					elog(ERROR, "record %s is unassigned yet", rec->refname);
				fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
				if (fno == SPI_ERROR_NOATTRIBUTE)
					elog(ERROR, "record %s has no field %s", rec->refname, recfield->fieldname);

				if (expr->plan_argtypes[i] != SPI_gettypeid(rec->tupdesc, fno))
					elog(ERROR, "type of %s.%s doesn't match that when preparing the plan", rec->refname, recfield->fieldname);

				values[i] = SPI_getbinval(rec->tup, rec->tupdesc, fno, &isnull);
				if (isnull)
					nulls[i] = 'n';
				else
					nulls[i] = ' ';
				break;

			case PLPGSQL_DTYPE_TRIGARG:
				trigarg = (PLpgSQL_trigarg *) (estate->datums[expr->params[i]]);
				tgargno = (int) exec_eval_expr(estate, trigarg->argnum,
											   &isnull, &tgargoid);
				if (isnull || tgargno < 0 || tgargno >= estate->trig_nargs)
				{
					values[i] = 0;
					nulls[i] = 'n';
				}
				else
				{
					values[i] = estate->trig_argv[tgargno];
					nulls[i] = ' ';
				}
				break;

			default:
				elog(ERROR, "unknown parameter dtype %d in exec_stmt_execsql()", estate->datums[expr->params[i]]->dtype);
		}
	}
	nulls[i] = '\0';

	/* ----------
	 * Execute the plan
	 * ----------
	 */
	rc = SPI_execp(expr->plan, values, nulls, 0);
	switch (rc)
	{
		case SPI_OK_UTILITY:
		case SPI_OK_SELINTO:
		case SPI_OK_INSERT:
		case SPI_OK_DELETE:
		case SPI_OK_UPDATE:
			break;

		case SPI_OK_SELECT:
			elog(ERROR, "unexpected SELECT query in exec_stmt_execsql()");

		default:
			elog(ERROR, "error executing query \"%s\"",
				 expr->query);
	}
	pfree(values);
	pfree(nulls);

	return PLPGSQL_RC_OK;
}


/* ----------
 * exec_assign_expr			Put an expressions result into
 *					a variable.
 * ----------
 */
static void
exec_assign_expr(PLpgSQL_execstate * estate, PLpgSQL_datum * target,
				 PLpgSQL_expr * expr)
{
	Datum		value;
	Oid			valtype;
	bool		isnull = false;

	value = exec_eval_expr(estate, expr, &isnull, &valtype);
	if (target != NULL)
		exec_assign_value(estate, target, value, valtype, &isnull);
}


/* ----------
 * exec_assign_value			Put a value into a target field
 * ----------
 */
static void
exec_assign_value(PLpgSQL_execstate * estate,
				  PLpgSQL_datum * target,
				  Datum value, Oid valtype, bool *isNull)
{
	PLpgSQL_var *var;
	PLpgSQL_rec *rec;
	PLpgSQL_recfield *recfield;
	int			fno;
	int			i;
	int			natts;
	Datum	   *values;
	char	   *nulls;
	bool		attisnull;
	Oid			atttype;
	int4		atttypmod;
	HeapTuple	typetup;
	Form_pg_type typeStruct;
	FmgrInfo	finfo_input;

	switch (target->dtype)
	{
		case PLPGSQL_DTYPE_VAR:
			/* ----------
			 * Target field is a variable - that's easy
			 * ----------
			 */
			var = (PLpgSQL_var *) target;
			var->value = exec_cast_value(value, valtype, var->datatype->typoid,
										 &(var->datatype->typinput),
									   var->datatype->atttypmod, isNull);

			if (isNull && var->notnull)
				elog(ERROR, "NULL assignment to variable '%s' declared NOT NULL", var->refname);

			var->isnull = *isNull;
			break;

		case PLPGSQL_DTYPE_RECFIELD:
			/* ----------
			 * Target field is a record
			 * ----------
			 */
			recfield = (PLpgSQL_recfield *) target;
			rec = (PLpgSQL_rec *) (estate->datums[recfield->recno]);

			/* ----------
			 * Check that there is already a tuple in the record.
			 * We need that because records don't have any predefined
			 * field structure.
			 * ----------
			 */
			if (!HeapTupleIsValid(rec->tup))
				elog(ERROR, "record %s is unassigned yet - don't know it's tuple structure", rec->refname);

			/* ----------
			 * Get the number of the records field to change and the
			 * number of attributes in the tuple.
			 * ----------
			 */
			fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
			if (fno == SPI_ERROR_NOATTRIBUTE)
				elog(ERROR, "record %s has no field %s", rec->refname, recfield->fieldname);
			fno--;
			natts = rec->tupdesc->natts;

			/* ----------
			 * We loop over the attributes of the rec's current tuple
			 * and collect the values in a Datum array along with the
			 * nulls information.
			 * ----------
			 */
			values = palloc(sizeof(Datum) * natts);
			nulls = palloc(natts + 1);

			for (i = 0; i < natts; i++)
			{
				/* ----------
				 * If this isn't the field we assign to, just use the
				 * value that's already in the tuple.
				 * ----------
				 */
				if (i != fno)
				{
					values[i] = SPI_getbinval(rec->tup, rec->tupdesc,
											  i + 1, &attisnull);
					if (attisnull)
						nulls[i] = 'n';
					else
						nulls[i] = ' ';
					continue;
				}

				/* ----------
				 * This is the field to change. Get it's type
				 * and cast the value we insert to that type.
				 * ----------
				 */
				atttype = SPI_gettypeid(rec->tupdesc, i + 1);
				atttypmod = rec->tupdesc->attrs[i]->atttypmod;
				typetup = SearchSysCacheTuple(TYPOID,
									 ObjectIdGetDatum(atttype), 0, 0, 0);
				if (!HeapTupleIsValid(typetup))
					elog(ERROR, "cache lookup for type %d failed", atttype);
				typeStruct = (Form_pg_type) GETSTRUCT(typetup);
				fmgr_info(typeStruct->typinput, &finfo_input);

				attisnull = *isNull;
				values[i] = exec_cast_value(value, valtype,
						   atttype, &finfo_input, atttypmod, &attisnull);
				if (attisnull)
					nulls[i] = 'n';
				else
					nulls[i] = ' ';
			}

			/* ----------
			 * Now call heap_formtuple() to create a new tuple
			 * that replaces the old one in the record.
			 * ----------
			 */
			nulls[i] = '\0';
			rec->tup = heap_formtuple(rec->tupdesc, values, nulls);
			pfree(values);
			pfree(nulls);

			break;

		default:
			elog(ERROR, "unknown dtype %d in exec_assign_value()",
				 target->dtype);
	}
}


/* ----------
 * exec_eval_expr			Evaluate an expression and return
 *					the result Datum.
 * ----------
 */
static Datum
exec_eval_expr(PLpgSQL_execstate * estate,
			   PLpgSQL_expr * expr,
			   bool *isNull,
			   Oid *rettype)
{
	int			rc;

	rc = exec_run_select(estate, expr, 2);
	if (rc != SPI_OK_SELECT)
		elog(ERROR, "query \"%s\" didn't return data", expr->query);

	/* ----------
	 * If there are no rows selected, the result is NULL.
	 * ----------
	 */
	if (SPI_processed == 0)
	{
		*isNull = true;
		return (Datum) 0;
	}

	/* ----------
	 * Check that the expression returned one single Datum
	 * ----------
	 */
	if (SPI_processed > 1)
		elog(ERROR, "query \"%s\" didn't return a single value", expr->query);
	if (SPI_tuptable->tupdesc->natts != 1)
		elog(ERROR, "query \"%s\" didn't return a single value", expr->query);

	/* ----------
	 * Return the result and it's type
	 * ----------
	 */
	*rettype = SPI_gettypeid(SPI_tuptable->tupdesc, 1);
	return SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, isNull);
}


/* ----------
 * exec_run_select			Execute a select query
 * ----------
 */
static int
exec_run_select(PLpgSQL_execstate * estate,
				PLpgSQL_expr * expr, int maxtuples)
{
	PLpgSQL_var *var;
	PLpgSQL_rec *rec;
	PLpgSQL_recfield *recfield;
	PLpgSQL_trigarg *trigarg;
	int			tgargno;
	Oid			tgargoid;
	int			i;
	Datum	   *values;
	char	   *nulls;
	int			rc;
	int			fno;
	bool		isnull;

	/* ----------
	 * On the first call for this expression generate the plan
	 * ----------
	 */
	if (expr->plan == NULL)
	{
		void	   *plan;
		Oid		   *argtypes;

		/* ----------
		 * Setup the argtypes array
		 * ----------
		 */
		argtypes = malloc(sizeof(Oid *) * (expr->nparams + 1));

		for (i = 0; i < expr->nparams; i++)
		{
			switch (estate->datums[expr->params[i]]->dtype)
			{
				case PLPGSQL_DTYPE_VAR:
					var = (PLpgSQL_var *) (estate->datums[expr->params[i]]);
					argtypes[i] = var->datatype->typoid;
					break;

				case PLPGSQL_DTYPE_RECFIELD:
					recfield = (PLpgSQL_recfield *) (estate->datums[expr->params[i]]);
					rec = (PLpgSQL_rec *) (estate->datums[recfield->recno]);

					if (!HeapTupleIsValid(rec->tup))
						elog(ERROR, "record %s is unassigned yet", rec->refname);
					fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
					if (fno == SPI_ERROR_NOATTRIBUTE)
						elog(ERROR, "record %s has no field %s", rec->refname, recfield->fieldname);
					argtypes[i] = SPI_gettypeid(rec->tupdesc, fno);
					break;

				case PLPGSQL_DTYPE_TRIGARG:
					argtypes[i] = (Oid) TEXTOID;
					break;

				default:
					elog(ERROR, "unknown parameter dtype %d in exec_run_select()", estate->datums[expr->params[i]]);
			}
		}

		/* ----------
		 * Generate and save the plan
		 * ----------
		 */
		plan = SPI_prepare(expr->query, expr->nparams, argtypes);
		if (plan == NULL)
			elog(ERROR, "SPI_prepare() failed on \"%s\"", expr->query);
		expr->plan = SPI_saveplan(plan);
		expr->plan_argtypes = argtypes;
	}

	/* ----------
	 * Now build up the values and nulls arguments for SPI_execp()
	 * ----------
	 */
	values = palloc(sizeof(Datum) * (expr->nparams + 1));
	nulls = palloc(expr->nparams + 1);

	for (i = 0; i < expr->nparams; i++)
	{
		switch (estate->datums[expr->params[i]]->dtype)
		{
			case PLPGSQL_DTYPE_VAR:
				var = (PLpgSQL_var *) (estate->datums[expr->params[i]]);
				values[i] = var->value;
				if (var->isnull)
					nulls[i] = 'n';
				else
					nulls[i] = ' ';
				break;

			case PLPGSQL_DTYPE_RECFIELD:
				recfield = (PLpgSQL_recfield *) (estate->datums[expr->params[i]]);
				rec = (PLpgSQL_rec *) (estate->datums[recfield->recno]);

				if (!HeapTupleIsValid(rec->tup))
					elog(ERROR, "record %s is unassigned yet", rec->refname);
				fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
				if (fno == SPI_ERROR_NOATTRIBUTE)
					elog(ERROR, "record %s has no field %s", rec->refname, recfield->fieldname);

				if (expr->plan_argtypes[i] != SPI_gettypeid(rec->tupdesc, fno))
					elog(ERROR, "type of %s.%s doesn't match that when preparing the plan", rec->refname, recfield->fieldname);

				values[i] = SPI_getbinval(rec->tup, rec->tupdesc, fno, &isnull);
				if (isnull)
					nulls[i] = 'n';
				else
					nulls[i] = ' ';
				break;

			case PLPGSQL_DTYPE_TRIGARG:
				trigarg = (PLpgSQL_trigarg *) (estate->datums[expr->params[i]]);
				tgargno = (int) exec_eval_expr(estate, trigarg->argnum,
											   &isnull, &tgargoid);
				if (isnull || tgargno < 0 || tgargno >= estate->trig_nargs)
				{
					values[i] = 0;
					nulls[i] = 'n';
				}
				else
				{
					values[i] = estate->trig_argv[tgargno];
					nulls[i] = ' ';
				}
				break;

			default:
				elog(ERROR, "unknown parameter dtype %d in exec_eval_expr()", estate->datums[expr->params[i]]);
		}
	}
	nulls[i] = '\0';

	/* ----------
	 * Execute the query
	 * ----------
	 */
	rc = SPI_execp(expr->plan, values, nulls, maxtuples);
	if (rc != SPI_OK_SELECT)
		elog(ERROR, "query \"%s\" isn't a SELECT", expr->query);
	pfree(values);
	pfree(nulls);

	return rc;
}


/* ----------
 * exec_move_row			Move one tuples values into a
 *					record or row
 * ----------
 */
static void
exec_move_row(PLpgSQL_execstate * estate,
			  PLpgSQL_rec * rec,
			  PLpgSQL_row * row,
			  HeapTuple tup, TupleDesc tupdesc)
{
	PLpgSQL_var *var;
	int			i;
	Datum		value;
	Oid			valtype;
	bool		isnull;

	/* ----------
	 * Record is simple - just put the tuple and it's descriptor
	 * into the record
	 * ----------
	 */
	if (rec != NULL)
	{
		if (HeapTupleIsValid(tup))
		{
			rec->tup = tup;
			rec->tupdesc = tupdesc;
		}
		else
		{
			rec->tup = NULL;
			rec->tupdesc = NULL;
		}

		return;
	}


	/* ----------
	 * Row is a bit more complicated in that we assign the single
	 * attributes of the query to the variables the row points to.
	 * ----------
	 */
	if (row != NULL)
	{
		if (HeapTupleIsValid(tup))
		{
			if (row->nfields != tupdesc->natts)
			{
				elog(ERROR, "query didn't return correct # of attributes for %s",
					 row->refname);
			}

			for (i = 0; i < row->nfields; i++)
			{
				var = (PLpgSQL_var *) (estate->datums[row->varnos[i]]);

				valtype = SPI_gettypeid(tupdesc, i + 1);
				value = SPI_getbinval(tup, tupdesc, i + 1, &isnull);
				exec_assign_value(estate, estate->datums[row->varnos[i]],
								  value, valtype, &isnull);

			}
		}
		else
		{
			for (i = 0; i < row->nfields; i++)
			{
				bool		nullval = true;

				exec_assign_value(estate, estate->datums[row->varnos[i]],
								  (Datum) 0, 0, &nullval);
			}
		}

		return;
	}

	elog(ERROR, "unsupported target in exec_move_row()");
}


/* ----------
 * exec_cast_value			Cast a value if required
 * ----------
 */
static Datum
exec_cast_value(Datum value, Oid valtype,
				Oid reqtype,
				FmgrInfo *reqinput,
				int16 reqtypmod,
				bool *isnull)
{
	if (!*isnull)
	{
		/* ----------
		 * If the type of the queries return value isn't
		 * that of the variable, convert it.
		 * ----------
		 */
		if (valtype != reqtype || reqtypmod > 0)
		{
			HeapTuple	typetup;
			Form_pg_type typeStruct;
			FmgrInfo	finfo_output;
			char	   *extval;

			typetup = SearchSysCacheTuple(TYPOID,
									 ObjectIdGetDatum(valtype), 0, 0, 0);
			if (!HeapTupleIsValid(typetup))
				elog(ERROR, "cache lookup for type %d failed", valtype);
			typeStruct = (Form_pg_type) GETSTRUCT(typetup);

			fmgr_info(typeStruct->typoutput, &finfo_output);
			extval = (char *) (*fmgr_faddr(&finfo_output)) (value, &isnull, -1);
			value = (Datum) (*fmgr_faddr(reqinput)) (extval, &isnull, reqtypmod);
		}
	}

	return value;
}


/* ----------
 * exec_set_found			Set the global found variable
 *					to true/false
 * ----------
 */
static void
exec_set_found(PLpgSQL_execstate * estate, bool state)
{
	PLpgSQL_var *var;

	var = (PLpgSQL_var *) (estate->datums[estate->found_varno]);
	var->value = (Datum) state;
	var->isnull = false;
}
