fix postgresql 9.4 / php 5.5 issues

/**********************************************************************
 * plphp.c - PHP as a procedural language for PostgreSQL
 *
 * This software is copyright (c) Command Prompt Inc.
 *
 * 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.
 *
 * IDENTIFICATION
 *		$Id$
 *********************************************************************
 */

/* Package configuration generated by autoconf */
#include "config.h"

/* First round of undefs, to eliminate collision between plphp and postgresql
 * definitions
 */

#undef PACKAGE_BUGREPORT
#undef PACKAGE_NAME
#undef PACKAGE_STRING
#undef PACKAGE_TARNAME
#undef PACKAGE_VERSION

/* PostgreSQL stuff */
#include "postgres.h"
#include "access/heapam.h"
#include "access/transam.h"

#include "catalog/catversion.h"
#include "catalog/pg_language.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"

#include "commands/trigger.h"
#include "fmgr.h"
#include "funcapi.h"			/* needed for SRF support */
#include "lib/stringinfo.h"

#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/elog.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"

/*
 * These are defined again in php.h, so undef them to avoid some
 * cpp warnings.
 */
#undef PACKAGE_BUGREPORT
#undef PACKAGE_NAME
#undef PACKAGE_STRING
#undef PACKAGE_TARNAME
#undef PACKAGE_VERSION

/* PHP stuff */
#include "php.h"

#include "php_variables.h"
#include "php_globals.h"
#include "zend_hash.h"
#include "zend_modules.h"

#include "php_ini.h"			/* needed for INI_HARDCODED */
#include "php_main.h"

/* Our own stuff */
#include "plphp_io.h"
#include "plphp_spi.h"

/* system stuff */
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif

#define INI_HARDCODED(name,value) \
		zend_alter_ini_entry(name, sizeof(name), value, strlen(value), \
							 PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE);

/* Check for PostgreSQL version */
#if (CATALOG_VERSION_NO >= 200709301)
#define PG_VERSION_83_COMPAT
#endif
#if (CATALOG_VERSION_NO >= 200611241)
#define PG_VERSION_82_COMPAT
#endif
/* We only support 8.1 and above */
#if (CATALOG_VERSION_NO >= 200510211)
#define PG_VERSION_81_COMPAT
#else
#error "Unsupported PostgreSQL version"
#endif

#undef DEBUG_PLPHP_MEMORY

#ifdef DEBUG_PLPHP_MEMORY
#define REPORT_PHP_MEMUSAGE(where) \
	elog(NOTICE, "PHP mem usage: %s: %u", where, AG(allocated_memory));
#else
#define REPORT_PHP_MEMUSAGE(a)
#endif

/* PostgreSQL starting from v 8.2 requires this define
 * for all modules.
 */
#ifdef PG_VERSION_82_COMPAT
PG_MODULE_MAGIC;
#else
/* Supress warnings on 8.1 and below */
#define ReleaseTupleDesc(tupdesc)
#endif

/* PHP 5.2 and earlier do not contain these definitions */
#ifndef Z_SET_ISREF_P
#define Z_SET_ISREF_P(foo) (foo)->is_ref = 1
#define Z_UNSET_ISREF_P(foo) (foo)->is_ref = 0
#endif

/* 8.2 compatibility */
#ifndef TYPTYPE_PSEUDO
#define TYPTYPE_PSEUDO 'p'
#define TYPTYPE_COMPOSITE 'c'
#endif

/* Check the argument type to expect to accept an initial value */
#define IS_ARGMODE_OUT(mode) ((mode) == PROARGMODE_OUT || \
(mode) == PROARGMODE_TABLE)
/*
 * Return types.  Why on earth is this a bitmask?  Beats me.
 * We should have separate flags instead.
 */
typedef enum pl_type
{
	PL_TUPLE = 1 << 0,
	PL_ARRAY = 1 << 1,
	PL_PSEUDO = 1 << 2
} pl_type;

/*
 * The information we cache about loaded procedures.
 *
 * "proname" is the name of the function, given by the user.
 *
 * fn_xmin and fn_cmin are used to know when a function has been redefined and
 * needs to be recompiled.
 *
 * trusted indicates whether the function was created with a trusted handler.
 *
 * ret_type is a weird bitmask that indicates whether this function returns a
 * tuple, an array or a pseudotype.  ret_oid is the Oid of the return type.
 * retset indicates whether the function was declared to return a set.
 *
 * arg_argmode indicates whether the argument is IN, OUT or both. It follows
 * values in pg_proc.proargmodes.
 *
 * n_out_args - total number of OUT or INOUT arguments.
 * arg_out_tupdesc is a tuple descriptor of the tuple constructed for OUT args.
 *
 * XXX -- maybe this thing needs to be rethought.
 */
typedef struct plphp_proc_desc
{
	char	   *proname;
	TransactionId fn_xmin;
	CommandId	fn_cmin;
	bool		trusted;
	pl_type		ret_type;
	Oid			ret_oid;		/* Oid of returning type */
	bool		retset;
	FmgrInfo	result_in_func;
	Oid			result_typioparam;
	int			n_out_args;
	int			n_total_args;
	int			n_mixed_args;
	FmgrInfo	arg_out_func[FUNC_MAX_ARGS];
	Oid			arg_typioparam[FUNC_MAX_ARGS];
	char		arg_typtype[FUNC_MAX_ARGS];
	char		arg_argmode[FUNC_MAX_ARGS];
	TupleDesc	args_out_tupdesc;
} plphp_proc_desc;

/*
 * Global data
 */
static bool plphp_first_call = true;
static zval *plphp_proc_array = NULL;

/* for PHP write/flush */
static StringInfo currmsg = NULL;

/*
 * for PHP <-> Postgres error message passing
 *
 * XXX -- it would be much better if we could save errcontext,
 * errhint, etc as well.
 */
static char *error_msg = NULL;
/*
 * Forward declarations
 */
static void plphp_init_all(void);
void		plphp_init(void);

PG_FUNCTION_INFO_V1(plphp_call_handler);
Datum plphp_call_handler(PG_FUNCTION_ARGS);

PG_FUNCTION_INFO_V1(plphp_validator);
Datum plphp_validator(PG_FUNCTION_ARGS);

static Datum plphp_trigger_handler(FunctionCallInfo fcinfo,
								   plphp_proc_desc *desc
								   TSRMLS_DC);
static Datum plphp_func_handler(FunctionCallInfo fcinfo,
							    plphp_proc_desc *desc
								TSRMLS_DC);
static Datum plphp_srf_handler(FunctionCallInfo fcinfo,
						   	   plphp_proc_desc *desc
							   TSRMLS_DC);

static plphp_proc_desc *plphp_compile_function(Oid fnoid, bool is_trigger TSRMLS_DC);
static zval *plphp_call_php_func(plphp_proc_desc *desc,
								 FunctionCallInfo fcinfo
								 TSRMLS_DC);
static zval *plphp_call_php_trig(plphp_proc_desc *desc,
								 FunctionCallInfo fcinfo, zval *trigdata
								 TSRMLS_DC);

static void plphp_error_cb(int type, const char *filename, const uint lineno,
								  const char *fmt, va_list args);
static bool is_valid_php_identifier(char *name);

/*
 * FIXME -- this comment is quite misleading actually, which is not surprising
 * since it came verbatim from PL/pgSQL.  Rewrite memory handling here someday
 * and remove it.
 *
 * This routine is a crock, and so is everyplace that calls it.  The problem
 * is that the cached form of plphp functions/queries is allocated permanently
 * (mostly via malloc()) and never released until backend exit.  Subsidiary
 * data structures such as fmgr info records therefore must live forever
 * as well.  A better implementation would store all this stuff in a per-
 * function memory context that could be reclaimed at need.  In the meantime,
 * fmgr_info_cxt must be called specifying TopMemoryContext so that whatever
 * it might allocate, and whatever the eventual function might allocate using
 * fn_mcxt, will live forever too.
 */
static void
perm_fmgr_info(Oid functionId, FmgrInfo *finfo)
{
	fmgr_info_cxt(functionId, finfo, TopMemoryContext);
}

/*
 * sapi_plphp_write
 * 		Called when PHP wants to write something to stdout.
 *
 * We just save the output in a StringInfo until the next Flush call.
 */
static int
sapi_plphp_write(const char *str, uint str_length TSRMLS_DC)
{
	if (currmsg == NULL)
		currmsg = makeStringInfo();

	appendStringInfoString(currmsg, str);

	return str_length;
}

/*
 * sapi_plphp_flush
 * 		Called when PHP wants to flush stdout.
 *
 * The stupid PHP implementation calls write and follows with a Flush right
 * away -- a good implementation would write several times and flush when the
 * message is complete.  To make the output look reasonable in Postgres, we
 * skip the flushing if the accumulated message does not end in a newline.
 */
static void
sapi_plphp_flush(void *sth)
{
	if (currmsg != NULL)
	{
		Assert(currmsg->data != NULL);

		if (currmsg->data[currmsg->len - 1] == '\n')
		{
			/*
			 * remove the trailing newline because elog() inserts another
			 * one
			 */
			currmsg->data[currmsg->len - 1] = '\0';
		}
		elog(LOG, "%s", currmsg->data);

		pfree(currmsg->data);
		pfree(currmsg);
		currmsg = NULL;
	}
	else
		elog(LOG, "attempting to flush a NULL message");
}

static int
sapi_plphp_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
{
	return 1;
}

static void
php_plphp_log_messages(char *message)
{
	elog(LOG, "plphp: %s", message);
}

static sapi_module_struct plphp_sapi_module = {
	"plphp",					/* name */
	"PL/php PostgreSQL Handler",/* pretty name */

	NULL,						/* startup */
	php_module_shutdown_wrapper,/* shutdown */

	NULL,						/* activate */
	NULL,						/* deactivate */

	sapi_plphp_write,			/* unbuffered write */
	sapi_plphp_flush,			/* flush */
	NULL,						/* stat */
	NULL,						/* getenv */

	php_error,					/* sapi_error(int, const char *, ...) */

	NULL,						/* header handler */
	sapi_plphp_send_headers,	/* send headers */
	NULL,						/* send header */

	NULL,						/* read POST */
	NULL,						/* read cookies */

	NULL,						/* register server variables */
	php_plphp_log_messages,		/* log message */

	NULL,						/* Block interrupts */
	NULL,						/* Unblock interrupts */
	STANDARD_SAPI_MODULE_PROPERTIES
};

/*
 * plphp_init_all()		- Initialize all
 *
 * XXX This is called each time a function is invoked.
 */
static void
plphp_init_all(void)
{
	/* Execute postmaster-startup safe initialization */
	if (plphp_first_call)
		plphp_init();

	/*
	 * Any other initialization that must be done each time a new
	 * backend starts -- currently none.
	 */
}

/*
 * This function must not be static, so that it can be used in
 * preload_libraries.  If it is, it will be called by postmaster;
 * otherwise it will be called by each backend the first time a
 * function is called.
 */
void
plphp_init(void)
{
	TSRMLS_FETCH();
	/* Do initialization only once */
	if (!plphp_first_call)
		return;

	/*
	 * Need a Pg try/catch block to prevent an initialization-
	 * failure from bringing the whole server down.
	 */
	PG_TRY();
	{
		zend_try
		{
			/*
			 * XXX This is a hack -- we are replacing the error callback in an
			 * invasive manner that should not be expected to work on future PHP
			 * releases.
			 */
			zend_error_cb = plphp_error_cb;

			/* Omit HTML tags from output */
			plphp_sapi_module.phpinfo_as_text = 1;
			sapi_startup(&plphp_sapi_module);

			if (php_module_startup(&plphp_sapi_module, NULL, 0) == FAILURE)
				elog(ERROR, "php_module_startup call failed");

			/* php_module_startup changed it, so put it back */
			zend_error_cb = plphp_error_cb;

			/*
			 * FIXME -- Figure out what this comment is supposed to mean:
			 *
			 * There is no way to see if we must call zend_ini_deactivate()
			 * since we cannot check if EG(ini_directives) has been initialised
			 * because the executor's constructor does not initialize it.
			 * Apart from that there seems no need for zend_ini_deactivate() yet.
			 * So we error out.
			 */

			/* Init procedure cache */
			MAKE_STD_ZVAL(plphp_proc_array);
			array_init(plphp_proc_array);

			zend_register_functions(
#if PHP_MAJOR_VERSION == 5
									NULL,
#endif
									spi_functions, NULL,
									MODULE_PERSISTENT TSRMLS_CC);

			PG(during_request_startup) = true;

			/* Set some defaults */
			SG(options) |= SAPI_OPTION_NO_CHDIR;

			/* Hard coded defaults which cannot be overwritten in the ini file */
			INI_HARDCODED("register_argc_argv", "0");
			INI_HARDCODED("html_errors", "0");
			INI_HARDCODED("implicit_flush", "1");
			INI_HARDCODED("max_execution_time", "0");
			INI_HARDCODED("max_input_time", "-1");

			/*
			 * Set memory limit to ridiculously high value.  This helps the
			 * server not to crash, because the PHP allocator has the really
			 * stupid idea of calling exit() if the limit is exceeded.
			 */
			{
				char	limit[15];

				snprintf(limit, sizeof(limit), "%d", 1 << 30);
				INI_HARDCODED("memory_limit", limit);
			}

			/* tell the engine we're in non-html mode */
			zend_uv.html_errors = false;

			/* not initialized but needed for several options */
			CG(in_compilation) = false;

			EG(uninitialized_zval_ptr) = NULL;

			if (php_request_startup(TSRMLS_C) == FAILURE)
			{
				SG(headers_sent) = 1;
				SG(request_info).no_headers = 1;
				/* Use Postgres log */
				elog(ERROR, "php_request_startup call failed");
			}

			CG(interactive) = false;
			PG(during_request_startup) = true;

			/* Register the resource for SPI_result */
			SPIres_rtype = zend_register_list_destructors_ex(php_SPIresult_destroy,
															 NULL,
															 "SPI result",
															 0);

			/* Ok, we're done */
			plphp_first_call = false;
		}
		zend_catch
		{
			plphp_first_call = true;
			if (error_msg)
			{
				char	str[1024];

				strncpy(str, error_msg, sizeof(str));
				pfree(error_msg);
				error_msg = NULL;
				elog(ERROR, "fatal error during PL/php initialization: %s",
					 str);
			}
			else
				elog(ERROR, "fatal error during PL/php initialization");
		}
		zend_end_try();
	}
	PG_CATCH();
	{
		PG_RE_THROW();
	}
	PG_END_TRY();
}

/*
 * plphp_call_handler
 *
 * The visible function of the PL interpreter.  The PostgreSQL function manager
 * and trigger manager call this function for execution of php procedures.
 */
Datum
plphp_call_handler(PG_FUNCTION_ARGS)
{
	Datum		retval;
	TSRMLS_FETCH();

	/* Initialize interpreter */
	plphp_init_all();

	PG_TRY();
	{
		/* Connect to SPI manager */
		if (SPI_connect() != SPI_OK_CONNECT)
			ereport(ERROR,
					(errcode(ERRCODE_CONNECTION_FAILURE),
					 errmsg("could not connect to SPI manager")));

		zend_try
		{
			plphp_proc_desc *desc;

			/* Clean up SRF state */
			current_fcinfo = NULL;

			/* Redirect to the appropiate handler */
			if (CALLED_AS_TRIGGER(fcinfo))
			{
				desc = plphp_compile_function(fcinfo->flinfo->fn_oid, true TSRMLS_CC);

				/* Activate PHP safe mode if needed */
				PG(safe_mode) = desc->trusted;

				retval = plphp_trigger_handler(fcinfo, desc TSRMLS_CC);
			}
			else
			{
				desc = plphp_compile_function(fcinfo->flinfo->fn_oid, false TSRMLS_CC);

				/* Activate PHP safe mode if needed */
				PG(safe_mode) = desc->trusted;

				if (desc->retset)
					retval = plphp_srf_handler(fcinfo, desc TSRMLS_CC);
				else
					retval = plphp_func_handler(fcinfo, desc TSRMLS_CC);
			}
		}
		zend_catch
		{
			REPORT_PHP_MEMUSAGE("reporting error");
			if (error_msg)
			{
				char	str[1024];

				strncpy(str, error_msg, sizeof(str));
				pfree(error_msg);
				error_msg = NULL;
				elog(ERROR, "%s", str);
			}
			else
				elog(ERROR, "fatal error");

			/* not reached, but keep compiler quiet */
			return 0;
		}
		zend_end_try();
	}
	PG_CATCH();
	{
		PG_RE_THROW();
	}
	PG_END_TRY();

	return retval;
}

/*
 * plphp_validator
 *
 * 		Validator function for checking the function's syntax at creation
 * 		time
 */
Datum
plphp_validator(PG_FUNCTION_ARGS)
{
	Oid				funcoid = PG_GETARG_OID(0);
	Form_pg_proc	procForm;
	HeapTuple		procTup;
	char			tmpname[32];
	char			funcname[NAMEDATALEN];
	char		   *tmpsrc = NULL,
				   *prosrc;
	Datum			prosrcdatum;

	TSRMLS_FETCH();
	/* Initialize interpreter */
	plphp_init_all();

	PG_TRY();
	{
		bool			isnull;
		/* Grab the pg_proc tuple */
		procTup = SearchSysCache(PROCOID,
								 ObjectIdGetDatum(funcoid),
								 0, 0, 0);
		if (!HeapTupleIsValid(procTup))
			elog(ERROR, "cache lookup failed for function %u", funcoid);

		procForm = (Form_pg_proc) GETSTRUCT(procTup);

		/* Get the function source code */
		prosrcdatum = SysCacheGetAttr(PROCOID,
									  procTup,
									  Anum_pg_proc_prosrc,
									  &isnull);
		if (isnull)
			elog(ERROR, "cache lookup yielded NULL prosrc");
		prosrc = DatumGetCString(DirectFunctionCall1(textout,
													 prosrcdatum));

		/* Get the function name, for the error message */
		StrNCpy(funcname, NameStr(procForm->proname), NAMEDATALEN);

		/* Let go of the pg_proc tuple */
		ReleaseSysCache(procTup);

		/* Create a PHP function creation statement */
		snprintf(tmpname, sizeof(tmpname), "plphp_temp_%u", funcoid);
		tmpsrc = (char *) palloc(strlen(prosrc) +
								 strlen(tmpname) +
								 strlen("function  ($args, $argc){ } "));
		sprintf(tmpsrc, "function %s($args, $argc){%s}",
				tmpname, prosrc);

		pfree(prosrc);

		zend_try
		{
			/*
			 * Delete the function from the PHP function table, just in case it
			 * already existed.  This is quite unlikely, but still.
			 */
			zend_hash_del(CG(function_table), tmpname, strlen(tmpname) + 1);

			/*
			 * Let the user see the fireworks.  If the function doesn't validate,
			 * the ERROR will be raised and the function will not be created.
			 */
			if (zend_eval_string(tmpsrc, NULL,
								 "plphp function temp source" TSRMLS_CC) == FAILURE)
				elog(ERROR, "function \"%s\" does not validate", funcname);

			pfree(tmpsrc);
			tmpsrc = NULL;

			/* Delete the newly-created function from the PHP function table. */
			zend_hash_del(CG(function_table), tmpname, strlen(tmpname) + 1);
		}
		zend_catch
		{
			if (tmpsrc != NULL)
				pfree(tmpsrc);

			if (error_msg)
			{
				char	str[1024];

				StrNCpy(str, error_msg, sizeof(str));
				pfree(error_msg);
				error_msg = NULL;
				elog(ERROR, "function \"%s\" does not validate: %s", funcname, str);
			}
			else
				elog(ERROR, "fatal error");

			/* not reached, but keep compiler quiet */
			return 0;
		}
		zend_end_try();

		/* The result of a validator is ignored */
		PG_RETURN_VOID();
	}
	PG_CATCH();
	{
		PG_RE_THROW();
	}
	PG_END_TRY();
}

/*
 * plphp_get_function_tupdesc
 *
 * 		Returns a TupleDesc of the function's return type.
 */
static TupleDesc
plphp_get_function_tupdesc(Oid result_type, Node *rsinfo)
{
	if (result_type == RECORDOID)
	{
		ReturnSetInfo *rs = (ReturnSetInfo *) rsinfo;
		/* We must get the information from call context */
		if (!rsinfo || !IsA(rsinfo, ReturnSetInfo) || rs->expectedDesc == NULL)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("function returning record called in context "
							"that cannot accept type record")));
		return rs->expectedDesc;
	}
	else
		/* ordinary composite type */
		return lookup_rowtype_tupdesc(result_type, -1);
}

/*
 * Build the $_TD array for the trigger function.
 */
static zval *
plphp_trig_build_args(FunctionCallInfo fcinfo)
{
	TriggerData	   *tdata;
	TupleDesc		tupdesc;
	zval		   *retval;
	int				i;

	MAKE_STD_ZVAL(retval);
	array_init(retval);

	tdata = (TriggerData *) fcinfo->context;
	tupdesc = tdata->tg_relation->rd_att;

	/* The basic variables */
	add_assoc_string(retval, "name", tdata->tg_trigger->tgname, 1);
    add_assoc_long(retval, "relid", tdata->tg_relation->rd_id);
	add_assoc_string(retval, "relname", SPI_getrelname(tdata->tg_relation), 1);
	add_assoc_string(retval, "schemaname", SPI_getnspname(tdata->tg_relation), 1);

	/* EVENT */
	if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event))
		add_assoc_string(retval, "event", "INSERT", 1);
	else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event))
		add_assoc_string(retval, "event", "DELETE", 1);
	else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))
		add_assoc_string(retval, "event", "UPDATE", 1);
	else
		elog(ERROR, "unknown firing event for trigger function");

	/* NEW and OLD as appropiate */
	if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
	{
		if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event))
		{
			zval	   *hashref;

			hashref = plphp_build_tuple_argument(tdata->tg_trigtuple, tupdesc);
			add_assoc_zval(retval, "new", hashref);
		}
		else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event))
		{
			zval	   *hashref;

			hashref = plphp_build_tuple_argument(tdata->tg_trigtuple, tupdesc);
			add_assoc_zval(retval, "old", hashref);
		}
		else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))
		{
			zval	   *hashref;

			hashref = plphp_build_tuple_argument(tdata->tg_newtuple, tupdesc);
			add_assoc_zval(retval, "new", hashref);

			hashref = plphp_build_tuple_argument(tdata->tg_trigtuple, tupdesc);
			add_assoc_zval(retval, "old", hashref);
		}
		else
			elog(ERROR, "unknown firing event for trigger function");
	}

	/* ARGC and ARGS */
	add_assoc_long(retval, "argc", tdata->tg_trigger->tgnargs);

	if (tdata->tg_trigger->tgnargs > 0)
	{
		zval	   *hashref;

		MAKE_STD_ZVAL(hashref);
		array_init(hashref);

		for (i = 0; i < tdata->tg_trigger->tgnargs; i++)
			add_index_string(hashref, i, tdata->tg_trigger->tgargs[i], 1);

		zend_hash_update(retval->value.ht, "args", strlen("args") + 1,
						 (void *) &hashref, sizeof(zval *), NULL);
	}

	/* WHEN */
	if (TRIGGER_FIRED_BEFORE(tdata->tg_event))
		add_assoc_string(retval, "when", "BEFORE", 1);
	else if (TRIGGER_FIRED_AFTER(tdata->tg_event))
		add_assoc_string(retval, "when", "AFTER", 1);
	else
		elog(ERROR, "unknown firing time for trigger function");

	/* LEVEL */
	if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
		add_assoc_string(retval, "level", "ROW", 1);
	else if (TRIGGER_FIRED_FOR_STATEMENT(tdata->tg_event))
		add_assoc_string(retval, "level", "STATEMENT", 1);
	else
		elog(ERROR, "unknown firing level for trigger function");

	return retval;
}

/*
 * plphp_trigger_handler
 * 		Handler for trigger function calls
 */
static Datum
plphp_trigger_handler(FunctionCallInfo fcinfo, plphp_proc_desc *desc TSRMLS_DC)
{
	Datum		retval = 0;
	char	   *srv;
	zval	   *phpret,
			   *zTrigData;
	TriggerData *trigdata;

	REPORT_PHP_MEMUSAGE("going to build the trigger arg");

	zTrigData = plphp_trig_build_args(fcinfo);

	REPORT_PHP_MEMUSAGE("going to call the trigger function");

	phpret = plphp_call_php_trig(desc, fcinfo, zTrigData TSRMLS_CC);
	if (!phpret)
		elog(ERROR, "error during execution of function %s", desc->proname);

	REPORT_PHP_MEMUSAGE("trigger called, going to build the return value");

	/*
	 * Disconnect from SPI manager and then create the return values datum (if
	 * the input function does a palloc for it this must not be allocated in
	 * the SPI memory context because SPI_finish would free it).
	 */
	if (SPI_finish() != SPI_OK_FINISH)
		ereport(ERROR,
				(errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
				 errmsg("could not disconnect from SPI manager")));

	trigdata = (TriggerData *) fcinfo->context;

	if (zTrigData->type != IS_ARRAY)
		elog(ERROR, "$_TD is not an array");

	/*
	 * In a BEFORE trigger, compute the return value.  In an AFTER trigger
	 * it'll be ignored, so don't bother.
	 */
	if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
	{
		switch (phpret->type)
		{
			case IS_STRING:
				srv = phpret->value.str.val;
				if (strcasecmp(srv, "SKIP") == 0)
				{
					/* do nothing */
					break;
				}
				else if (strcasecmp(srv, "MODIFY") == 0)
				{
					if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event) ||
						TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
						retval = PointerGetDatum(plphp_modify_tuple(zTrigData,
																	trigdata));
					else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
						ereport(ERROR,
								(errcode(ERRCODE_SYNTAX_ERROR),
								 errmsg("on delete trigger can not modify the the return tuple")));
					else
						elog(ERROR, "unknown event in trigger function");
				}
				else
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("expected trigger function to return NULL, 'SKIP' or 'MODIFY'")));
				break;
			case IS_NULL:
				if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event) ||
					TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
					retval = (Datum) trigdata->tg_trigtuple;
				else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
					retval = (Datum) trigdata->tg_newtuple;
				break;
			default:
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("expected trigger function to return NULL, 'SKIP' or 'MODIFY'")));
				break;
		}
	}

	REPORT_PHP_MEMUSAGE("freeing some variables");

	zval_dtor(zTrigData);
	zval_dtor(phpret);

	FREE_ZVAL(phpret);
	FREE_ZVAL(zTrigData);

	REPORT_PHP_MEMUSAGE("trigger call done");

	return retval;
}

/*
 * plphp_func_handler
 * 		Handler for regular function calls
 */
static Datum
plphp_func_handler(FunctionCallInfo fcinfo, plphp_proc_desc *desc TSRMLS_DC)
{
	zval	   *phpret = NULL;
	Datum		retval;
	char	   *retvalbuffer = NULL;

	/* SRFs are handled separately */
	Assert(!desc->retset);

	/* Call the PHP function.  */
	phpret = plphp_call_php_func(desc, fcinfo TSRMLS_CC);
	if (!phpret)
		elog(ERROR, "error during execution of function %s", desc->proname);

	REPORT_PHP_MEMUSAGE("function invoked");

	/* Basic datatype checks */
	if ((desc->ret_type & PL_ARRAY) && phpret->type != IS_ARRAY)
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("function declared to return array must return an array")));
	if ((desc->ret_type & PL_TUPLE) && phpret->type != IS_ARRAY)
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("function declared to return tuple must return an array")));

	/*
	 * Disconnect from SPI manager and then create the return values datum (if
	 * the input function does a palloc for it this must not be allocated in
	 * the SPI memory context because SPI_finish would free it).
	 */
	if (SPI_finish() != SPI_OK_FINISH)
		ereport(ERROR,
				(errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
				 errmsg("could not disconnect from SPI manager")));
	retval = (Datum) 0;

	if (desc->ret_type & PL_PSEUDO)
	{
		HeapTuple	retTypeTup;
		Form_pg_type retTypeStruct;

		retTypeTup = SearchSysCache(TYPEOID,
									ObjectIdGetDatum(get_fn_expr_rettype(fcinfo->flinfo)),
									0, 0, 0);
		retTypeStruct = (Form_pg_type) GETSTRUCT(retTypeTup);
		perm_fmgr_info(retTypeStruct->typinput, &(desc->result_in_func));
		desc->result_typioparam = retTypeStruct->typelem;
		ReleaseSysCache(retTypeTup);
	}

	if (phpret)
	{
		switch (Z_TYPE_P(phpret))
		{
			case IS_NULL:
				fcinfo->isnull = true;
				break;
			case IS_BOOL:
			case IS_DOUBLE:
			case IS_LONG:
			case IS_STRING:
				retvalbuffer = plphp_zval_get_cstring(phpret, false, false);
				retval = CStringGetDatum(retvalbuffer);
				break;
			case IS_ARRAY:
				if (desc->ret_type & PL_ARRAY)
				{
					retvalbuffer = plphp_convert_to_pg_array(phpret);
					retval = CStringGetDatum(retvalbuffer);
				}
				else if (desc->ret_type & PL_TUPLE)
				{
					TupleDesc	td;
					HeapTuple	tup;

					if (desc->ret_type & PL_PSEUDO)
						td = plphp_get_function_tupdesc(desc->ret_oid,
														fcinfo->resultinfo);
					else
						td = lookup_rowtype_tupdesc(desc->ret_oid, (int32) -1);

					if (!td)
						elog(ERROR, "no TupleDesc info available");

					tup = plphp_htup_from_zval(phpret, td);
					retval = HeapTupleGetDatum(tup);
					ReleaseTupleDesc(td);
				}
				else
					/* FIXME -- should return the thing as a string? */
					elog(ERROR, "this plphp function cannot return arrays");
				break;
			default:
				elog(WARNING,
					 "plphp functions cannot return type %i",
					 phpret->type);
				fcinfo->isnull = true;
				break;
		}
	}
	else
	{
		fcinfo->isnull = true;
		retval = (Datum) 0;
	}

	if (!fcinfo->isnull && !(desc->ret_type & PL_TUPLE))
	{
		retval = FunctionCall3(&desc->result_in_func,
							   PointerGetDatum(retvalbuffer),
							   ObjectIdGetDatum(desc->result_typioparam),
							   Int32GetDatum(-1));
		pfree(retvalbuffer);
	}

	REPORT_PHP_MEMUSAGE("finished calling user function");

	return retval;
}

/*
 * plphp_srf_handler
 * 		Invoke a SRF
 */
static Datum
plphp_srf_handler(FunctionCallInfo fcinfo, plphp_proc_desc *desc TSRMLS_DC)
{
	ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
	TupleDesc	tupdesc;
	zval	   *phpret;
	MemoryContext	oldcxt;

	Assert(desc->retset);

	current_fcinfo = fcinfo;
	current_tuplestore = NULL;

	/* Check context before allowing the call to go through */
	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
		(rsi->allowedModes & SFRM_Materialize) == 0 ||
		rsi->expectedDesc == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("set-valued function called in context that "
						"cannot accept a set")));

	/*
	 * Fetch the function's tuple descriptor.  This will return NULL in the
	 * case of a scalar return type, in which case we will copy the TupleDesc
	 * from the ReturnSetInfo.
	 */
	get_call_result_type(fcinfo, NULL, &tupdesc);
	if (tupdesc == NULL)
		tupdesc = rsi->expectedDesc;

	/*
	 * If the expectedDesc is NULL, bail out, because most likely it's using
	 * IN/OUT parameters.
	 */
	if (tupdesc == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot use IN/OUT parameters in PL/php")));

	oldcxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);

	/* This context is reset once per row in return_next */
	current_memcxt = AllocSetContextCreate(CurTransactionContext,
										   "PL/php SRF context",
										   ALLOCSET_DEFAULT_MINSIZE,
										   ALLOCSET_DEFAULT_INITSIZE,
										   ALLOCSET_DEFAULT_MAXSIZE);

	/* Tuple descriptor and AttInMetadata for return_next */
	current_tupledesc = CreateTupleDescCopy(tupdesc);
	current_attinmeta = TupleDescGetAttInMetadata(current_tupledesc);

	/*
	 * Call the PHP function.  The user code must call return_next, which will
	 * create and populate the tuplestore appropiately.
	 */
	phpret = plphp_call_php_func(desc, fcinfo TSRMLS_CC);

	/* We don't use the return value */
	zval_dtor(phpret);

	/* Close the SPI connection */
	if (SPI_finish() != SPI_OK_FINISH)
		ereport(ERROR,
				(errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
				 errmsg("could not disconnect from SPI manager")));

	/* Now prepare the return values. */
	rsi->returnMode = SFRM_Materialize;

	if (current_tuplestore)
	{
		rsi->setResult = current_tuplestore;
		rsi->setDesc = current_tupledesc;
	}

	MemoryContextDelete(current_memcxt);
	current_memcxt = NULL;
	current_tupledesc = NULL;
	current_attinmeta = NULL;

	MemoryContextSwitchTo(oldcxt);

	/* All done */
	return (Datum) 0;
}

/*
 * plphp_compile_function
 *
 * 		Compile (or hopefully just look up) function
 */
static plphp_proc_desc *
plphp_compile_function(Oid fnoid, bool is_trigger TSRMLS_DC)
{
	HeapTuple	procTup;
	Form_pg_proc procStruct;
	char		internal_proname[64];
	plphp_proc_desc *prodesc = NULL;
	int			i;
	char	   *pointer = NULL;

	/*
	 * We'll need the pg_proc tuple in any case...
	 */
	procTup = SearchSysCache(PROCOID, ObjectIdGetDatum(fnoid), 0, 0, 0);
	if (!HeapTupleIsValid(procTup))
		elog(ERROR, "cache lookup failed for function %u", fnoid);
	procStruct = (Form_pg_proc) GETSTRUCT(procTup);

	/*
	 * Build our internal procedure name from the function's Oid
	 */
	if (is_trigger)
		snprintf(internal_proname, sizeof(internal_proname),
				 "plphp_proc_%u_trigger", fnoid);
	else
		snprintf(internal_proname, sizeof(internal_proname),
				 "plphp_proc_%u", fnoid);

	/*
	 * Look up the internal proc name in the hashtable
	 */
	pointer = plphp_zval_get_cstring(plphp_array_get_elem(plphp_proc_array,
														  internal_proname),
									 false, true);
	if (pointer)
	{
		bool uptodate;
		sscanf(pointer, "%p", &prodesc);

#ifdef PG_VERSION_83_COMPAT
		/* PostgreSQL 8.3 doesn't allow calling GetCmin if a tuple doesn't
		 * originate from the current transaction.
		 */
		uptodate =
			(prodesc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
			 prodesc->fn_cmin == HeapTupleHeaderGetRawCommandId(procTup->t_data));

#else
		uptodate =
			(prodesc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
			 prodesc->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data));

#endif

		/* We need to delete the old entry */
		if (!uptodate)
		{
			/*
			 * FIXME -- use a per-function memory context and fix this
			 * stuff for good
			 */
			free(prodesc->proname);
			free(prodesc);
			prodesc = NULL;
		}
	}

	if (prodesc == NULL)
	{
		HeapTuple	langTup;
		Form_pg_language langStruct;
		Datum		prosrcdatum;
		bool		isnull;
		char	   *proc_source;
		char	   *complete_proc_source;
		char	   *pointer = NULL;
		char	   *aliases = NULL;
		char	   *out_aliases = NULL;
		char	   *out_return_str = NULL;
		int16	typlen;
		char	typbyval,
				typalign,
				typtype,
				typdelim;
		Oid		typioparam,
				typinput,
				typoutput;
		/*
		 * Allocate a new procedure description block
		 */
		prodesc = (plphp_proc_desc *) malloc(sizeof(plphp_proc_desc));
		if (!prodesc)
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of memory")));

		MemSet(prodesc, 0, sizeof(plphp_proc_desc));
		prodesc->proname = strdup(internal_proname);
		if (!prodesc->proname)
		{
			free(prodesc);
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of memory")));
		}

		prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);

#ifdef PG_VERSION_83_COMPAT
		/* PostgreSQL 8.3 doesn't allow calling GetCmin if a tuple doesn't
		 * originate from the current transaction.
		 */
		prodesc->fn_cmin = HeapTupleHeaderGetRawCommandId(procTup->t_data);

#else
		prodesc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);

#endif

		/*
		 * Look up the pg_language tuple by Oid
		 */
		langTup = SearchSysCache(LANGOID,
								 ObjectIdGetDatum(procStruct->prolang),
								 0, 0, 0);
		if (!HeapTupleIsValid(langTup))
		{
			free(prodesc->proname);
			free(prodesc);
			elog(ERROR, "cache lookup failed for language %u",
					   procStruct->prolang);
		}
		langStruct = (Form_pg_language) GETSTRUCT(langTup);
		prodesc->trusted = langStruct->lanpltrusted;
		ReleaseSysCache(langTup);

		/*
		 * Get the required information for input conversion of the return
		 * value, and output conversion of the procedure's arguments.
		 */
		if (!is_trigger)
		{
			char  **argnames;
			char   *argmodes;
			Oid    *argtypes;
			int32	alias_str_end,
					out_str_end;

			typtype = get_typtype(procStruct->prorettype);
			get_type_io_data(procStruct->prorettype,
							 IOFunc_input,
							 &typlen,
							 &typbyval,
							 &typalign,
							 &typdelim,
							 &typioparam,
							 &typinput);

			/*
			 * Disallow pseudotype result, except:
			 * VOID, RECORD, ANYELEMENT or ANYARRAY
			 */
			if (typtype == TYPTYPE_PSEUDO)
			{
				if ((procStruct->prorettype == VOIDOID) ||
					(procStruct->prorettype == RECORDOID) ||
					(procStruct->prorettype == ANYELEMENTOID) ||
					(procStruct->prorettype == ANYARRAYOID))
				{
					/* okay */
					prodesc->ret_type |= PL_PSEUDO;
				}
				else if (procStruct->prorettype == TRIGGEROID)
				{
					free(prodesc->proname);
					free(prodesc);
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("trigger functions may only be called "
									"as triggers")));
				}
				else
				{
					free(prodesc->proname);
					free(prodesc);
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("plphp functions cannot return type %s",
									format_type_be(procStruct->prorettype))));
				}
			}

			prodesc->ret_oid = procStruct->prorettype;
			prodesc->retset = procStruct->proretset;

			if (typtype == TYPTYPE_COMPOSITE ||
				procStruct->prorettype == RECORDOID)
			{
				prodesc->ret_type |= PL_TUPLE;
			}

			if (procStruct->prorettype == ANYARRAYOID)
				prodesc->ret_type |= PL_ARRAY;
			else
			{
				/* function returns a normal (declared) array */
				if (typlen == -1 && get_element_type(procStruct->prorettype))
					prodesc->ret_type |= PL_ARRAY;
			}

			perm_fmgr_info(typinput, &(prodesc->result_in_func));
			prodesc->result_typioparam = typioparam;

			/* Deal with named arguments, OUT, IN/OUT and TABLE arguments */

			prodesc->n_total_args = get_func_arg_info(procTup, &argtypes,
											  		  &argnames, &argmodes);
			prodesc->n_out_args = 0;
			prodesc->n_mixed_args = 0;

			prodesc->args_out_tupdesc = NULL;
			out_return_str = NULL;
			alias_str_end = out_str_end = 0;

			/* Count the number of OUT arguments. Need to do this out of the
			 * main loop, to correctly determine the object to return for OUT args
		     */
			if (argmodes)
				for (i = 0; i < prodesc->n_total_args; i++)
				{
					switch(argmodes[i])
					{
						case PROARGMODE_OUT:
							prodesc->n_out_args++;
							break;
						case PROARGMODE_INOUT:
							prodesc->n_mixed_args++;
							break;
						case PROARGMODE_IN:
							break;
						case PROARGMODE_TABLE:
							break;
						case PROARGMODE_VARIADIC:
							elog(ERROR, "VARIADIC arguments are not supported");
						default:
							elog(ERROR, "Unsupported type %c for argument no %d",
								 argmodes[i], i);
					}
					prodesc->arg_argmode[i] = argmodes[i];
				}
			else
				MemSet(prodesc->arg_argmode, PROARGMODE_IN,
				 	   prodesc->n_total_args);

			/* Allocate memory for argument names unless all of them are OUT*/
			if (argnames && prodesc->n_total_args > 0)
				aliases = palloc((NAMEDATALEN + 32) * prodesc->n_total_args);

			/* Main argument processing loop. */
			for (i = 0; i < prodesc->n_total_args; i++)
			{
				prodesc->arg_typtype[i] = get_typtype(argtypes[i]);
				if (prodesc->arg_typtype[i] != TYPTYPE_COMPOSITE)
				{
					get_type_io_data(argtypes[i],
									 IOFunc_output,
									 &typlen,
									 &typbyval,
									 &typalign,
									 &typdelim,
									 &typioparam,
									 &typoutput);
					perm_fmgr_info(typoutput, &(prodesc->arg_out_func[i]));
					prodesc->arg_typioparam[i] = typioparam;
				}
				if (aliases && argnames[i][0] != '\0')
				{
					if (!is_valid_php_identifier(argnames[i]))
						elog(ERROR, "\"%s\" can not be used as a PHP variable name",
							 argnames[i]);
					/* Deal with argument name */
					alias_str_end += snprintf(aliases + alias_str_end,
										 	  NAMEDATALEN + 32,
								   		 	  " $%s = &$args[%d];",
											  argnames[i], i);
				}
				if ((prodesc->arg_argmode[i] == PROARGMODE_OUT ||
					 prodesc->arg_argmode[i] == PROARGMODE_INOUT) && !prodesc->retset)
				{
					/* Initialiazation for OUT arguments aliases */
					if (!out_return_str)
					{
						/* Generate return statment for a single OUT argument */
						out_return_str = palloc(NAMEDATALEN + 32);
						if (prodesc->n_out_args + prodesc->n_mixed_args == 1)
							snprintf(out_return_str, NAMEDATALEN + 32,
									 "return $args[%d];", i);
						else
						{
							/* PL/PHP deals with multiple OUT arguments by
							 * internally creating an array of references to them.
							 * E.g. out_fn(a out integer, b out integer )
							 * translates into:
							 * $_plphp_ret_out_fn_1234=array(a => $&a,b => $&b);
							 */
							char plphp_ret_array_name[NAMEDATALEN + 16];

							int array_namelen = snprintf(plphp_ret_array_name,
							 						 	 NAMEDATALEN + 16,
									 				 	 "_plphp_ret_%s",
													 	 internal_proname);

							snprintf(out_return_str, array_namelen + 16,
									"return $%s;", plphp_ret_array_name);

							/* 2 NAMEDATALEN for argument names, additional
							 * 16 bytes per each argument for assignment string,
							 * additional 16 bytes for the 'array' prefix string.
							 */
							out_aliases = palloc(array_namelen +
												 (prodesc->n_out_args +
												  prodesc->n_mixed_args) *
												 (2*NAMEDATALEN + 16) + 16);

							out_str_end = snprintf(out_aliases,
							 					   array_namelen +
												   (2 * NAMEDATALEN + 16) + 16,
												   "$%s = array(&$args[%d]",
												   plphp_ret_array_name, i);

						}
					}
					else if (out_aliases)
					{
					   /* Add new elements to the array of aliases for OUT args */
						Assert(prodesc->n_out_args + prodesc->n_mixed_args > 1);
						out_str_end += snprintf(out_aliases+out_str_end,
												2 * NAMEDATALEN + 16,
												",&$args[%d]", i);
					}
				}
			}
			if (aliases)
				strcat(aliases, " ");
			if (out_aliases)
				strcat(out_aliases, ")");
		}

		/*
		 * Create the text of the PHP function.  We do not use the same
		 * function name, because that would prevent function overloading.
		 * Sadly this also prevents PL/php functions from calling each other
		 * easily.
		 */
		prosrcdatum = SysCacheGetAttr(PROCOID, procTup,
									  Anum_pg_proc_prosrc, &isnull);
		if (isnull)
			elog(ERROR, "cache lookup yielded NULL prosrc");

		proc_source = DatumGetCString(DirectFunctionCall1(textout,
														  prosrcdatum));

		/* Create the procedure in the interpreter */
		complete_proc_source =
			(char *) palloc(strlen(proc_source) +
							strlen(internal_proname) +
							(aliases ? strlen(aliases) : 0) +
							(out_aliases ? strlen(out_aliases) : 0) +
							strlen("function  ($args, $argc){ } ") + 32 +
							(out_return_str ? strlen(out_return_str) : 0));

		/* XXX Is this usage of sprintf safe? */
		if (is_trigger)
			sprintf(complete_proc_source, "function %s($_TD){%s}",
					internal_proname, proc_source);
		else
			sprintf(complete_proc_source,
					"function %s($args, $argc){%s %s;%s; %s}",
					internal_proname,
					aliases ? aliases : "",
					out_aliases ? out_aliases : "",
					proc_source,
					out_return_str? out_return_str : "");

		elog(LOG, "complete_proc_source = %s",
				 	 complete_proc_source);

		zend_hash_del(CG(function_table), prodesc->proname,
					  strlen(prodesc->proname) + 1);

		pointer = (char *) palloc(64);
		sprintf(pointer, "%p", (void *) prodesc);
		add_assoc_string(plphp_proc_array, internal_proname,
						 (char *) pointer, 1);

		if (zend_eval_string(complete_proc_source, NULL,
							 "plphp function source" TSRMLS_CC) == FAILURE)
		{
			/* the next compilation will blow it up */
			prodesc->fn_xmin = InvalidTransactionId;
			elog(ERROR, "unable to compile function \"%s\"",
					   prodesc->proname);
		}

		if (aliases)
			pfree(aliases);
		if (out_aliases)
			pfree(out_aliases);
		if (out_return_str)
			pfree(out_return_str);
		pfree(complete_proc_source);
	}

	ReleaseSysCache(procTup);

	return prodesc;
}

/*
 * plphp_func_build_args
 * 		Build a PHP array representing the arguments to the function
 */
static zval *
plphp_func_build_args(plphp_proc_desc *desc, FunctionCallInfo fcinfo TSRMLS_DC)
{
	zval	   *retval;
	int			i,j;

	MAKE_STD_ZVAL(retval);
	array_init(retval);

	/*
	 * The first var iterates over every argument, the second one - over the
	 * IN or INOUT ones only
	 */
	for (i = 0, j = 0; i < desc->n_total_args;
		 (j = IS_ARGMODE_OUT(desc->arg_argmode[i]) ? j : j + 1), i++)
	{
		/* Assing NULLs to OUT or TABLE arguments initially */
		if (IS_ARGMODE_OUT(desc->arg_argmode[i]))
		{
			add_next_index_unset(retval);
			continue;
		}

		if (desc->arg_typtype[i] == TYPTYPE_PSEUDO)
		{
			HeapTuple	typeTup;
			Form_pg_type typeStruct;

			typeTup = SearchSysCache(TYPEOID,
									 ObjectIdGetDatum(get_fn_expr_argtype
													  (fcinfo->flinfo, j)),
									 0, 0, 0);
			typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
			perm_fmgr_info(typeStruct->typoutput,
						   &(desc->arg_out_func[i]));
			desc->arg_typioparam[i] = typeStruct->typelem;
			ReleaseSysCache(typeTup);
		}

		if (desc->arg_typtype[i] == TYPTYPE_COMPOSITE)
		{
			if (fcinfo->argnull[j])
				add_next_index_unset(retval);
			else
			{
				HeapTupleHeader	td;
				Oid				tupType;
				int32			tupTypmod;
				TupleDesc		tupdesc;
				HeapTupleData	tmptup;
				zval		   *hashref;

				td = DatumGetHeapTupleHeader(fcinfo->arg[j]);

				/* Build a temporary HeapTuple control structure */
				tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
				tmptup.t_data = DatumGetHeapTupleHeader(fcinfo->arg[j]);

				/* Extract rowtype info and find a tupdesc */
				tupType = HeapTupleHeaderGetTypeId(tmptup.t_data);
				tupTypmod = HeapTupleHeaderGetTypMod(tmptup.t_data);
				tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);

				/* Build the PHP hash */
				hashref = plphp_build_tuple_argument(&tmptup, tupdesc);
				zend_hash_next_index_insert(retval->value.ht,
											(void *) &hashref,
											sizeof(zval *), NULL);
				/* Finally release the acquired tupledesc */
				ReleaseTupleDesc(tupdesc);
			}
		}
		else
		{
			if (fcinfo->argnull[j])
				add_next_index_unset(retval);
			else
			{
				char	   *tmp;

				/*
				 * TODO room for improvement here: instead of going through the
				 * output function, figure out if we can just use the native
				 * representation to pass to PHP.
				 */

				tmp =
					DatumGetCString(FunctionCall3
									(&(desc->arg_out_func[i]),
									 fcinfo->arg[j],
									 ObjectIdGetDatum(desc->arg_typioparam[i]),
									 Int32GetDatum(-1)));
				/*
				 * FIXME -- this is bogus.  Not every value starting with { is
				 * an array.  Figure out a better method for detecting arrays.
				 */
				if (tmp[0] == '{')
				{
					zval	   *hashref;

					hashref = plphp_convert_from_pg_array(tmp TSRMLS_CC);
					zend_hash_next_index_insert(retval->value.ht,
												(void *) &hashref,
												sizeof(zval *), NULL);
				}
				else
					add_next_index_string(retval, tmp, 1);

				/*
				 * FIXME - figure out which parameters are passed by
				 * reference and need freeing
				 */
				/* pfree(tmp); */
			}
		}
	}

	return retval;
}

/*
 * plphp_call_php_func
 * 		Build the function argument array and call the PHP function.
 *
 * We use a private PHP symbol table, so that we can easily destroy everything
 * used during the execution of the function.  We use it to collect the
 * arguments' zvals as well.  We exclude the return value, because it will be
 * used by the caller -- it must be freed there!
 */
static zval *
plphp_call_php_func(plphp_proc_desc *desc, FunctionCallInfo fcinfo TSRMLS_DC)
{
	zval	   *retval;
	zval	   *args;
	zval	   *argc;
	zval	   *funcname;
	zval	  **params[2];
	char		call[64];
	HashTable  *orig_symbol_table;
	HashTable  *symbol_table;

	REPORT_PHP_MEMUSAGE("going to build function args");

	ALLOC_HASHTABLE(symbol_table);
	zend_hash_init(symbol_table, 0, NULL, ZVAL_PTR_DTOR, 0);

	/*
	 * Build the function arguments.  Save a pointer to each new zval in our
	 * private symbol table, so that we can clean up easily later.
	 */
	args = plphp_func_build_args(desc, fcinfo TSRMLS_CC);
	zend_hash_update(symbol_table, "args", strlen("args") + 1,
					 (void *) &args, sizeof(zval *), NULL);

	REPORT_PHP_MEMUSAGE("args built. Now the rest ...");

	MAKE_STD_ZVAL(argc);
	ZVAL_LONG(argc, desc->n_total_args);
	zend_hash_update(symbol_table, "argc", strlen("argc") + 1,
					 (void *) &argc, sizeof(zval *), NULL);

	params[0] = &args;
	params[1] = &argc;

	/* Build the internal function name, and save for later cleaning */
	sprintf(call, "plphp_proc_%u", fcinfo->flinfo->fn_oid);
	MAKE_STD_ZVAL(funcname);
	ZVAL_STRING(funcname, call, 1);
	zend_hash_update(symbol_table, "funcname", strlen("funcname") + 1,
					 (void *) &funcname, sizeof(zval *), NULL);

	REPORT_PHP_MEMUSAGE("going to call the function");

	orig_symbol_table = EG(active_symbol_table);
	EG(active_symbol_table) = symbol_table;

	saved_symbol_table = EG(active_symbol_table);

	/* XXX: why no_separation param is 1 is this call ? */
	if (call_user_function_ex(CG(function_table), NULL, funcname, &retval,
							  2, params, 1, symbol_table TSRMLS_CC) == FAILURE)
		elog(ERROR, "could not call function \"%s\"", call);

	REPORT_PHP_MEMUSAGE("going to free some vars");

	saved_symbol_table = NULL;

	/* Return to the original symbol table, and clean our private one */
	EG(active_symbol_table) = orig_symbol_table;
	zend_hash_clean(symbol_table);

	REPORT_PHP_MEMUSAGE("function call done");

	return retval;
}

/*
 * plphp_call_php_trig
 * 		Build trigger argument array and call the PHP function as a
 * 		trigger.
 *
 * Note we don't need to change the symbol table here like we do in
 * plphp_call_php_func, because we do manual cleaning of each zval used.
 */
static zval *
plphp_call_php_trig(plphp_proc_desc *desc, FunctionCallInfo fcinfo,
					zval *trigdata TSRMLS_DC)
{
	zval	   *retval;
	zval	   *funcname;
	char		call[64];
	zval	  **params[1];

	params[0] = &trigdata;

	/* Build the internal function name, and save for later cleaning */
	sprintf(call, "plphp_proc_%u_trigger", fcinfo->flinfo->fn_oid);
	MAKE_STD_ZVAL(funcname);
	ZVAL_STRING(funcname, call, 0);

	/*
	 * HACK: mark trigdata as a reference, so it won't be copied in
	 * call_user_function_ex.  This way the user function will be able to
	 * modify it, in order to change NEW.
	 */
	Z_SET_ISREF_P(trigdata);

	if (call_user_function_ex(CG(function_table), NULL, funcname, &retval,
							  1, params, 1, NULL TSRMLS_CC) == FAILURE)
		elog(ERROR, "could not call function \"%s\"", call);

	FREE_ZVAL(funcname);

	/* Return to the original state */
	Z_UNSET_ISREF_P(trigdata);

	return retval;
}

/*
 * plphp_error_cb
 *
 * A callback for PHP error handling.  This is called when the php_error or
 * zend_error function is invoked in our code.  Ideally this function should
 * clean up the PHP state after an ERROR, but zend_try blocks do not seem
 * to work as I'd expect.  So for now, we degrade the error to WARNING and
 * continue executing in the hope that the system doesn't crash later.
 *
 * Note that we do clean up some PHP state by hand but it doesn't seem to
 * work as expected either.
 */
void
plphp_error_cb(int type, const char *filename, const uint lineno,
	   		   const char *fmt, va_list args)
{
	char	str[1024];
	int		elevel;

	vsnprintf(str, 1024, fmt, args);

	/*
	 * PHP error classification is a bitmask, so this conversion is a bit
	 * bogus.  However, most calls to php_error() use a single bit.
	 * Whenever more than one is used, we will default to ERROR, so this is
	 * safe, if a bit excessive.
	 *
	 * XXX -- I wonder whether we should promote the WARNINGs to errors as
	 * well.  PHP has a really stupid way of continuing execution in presence
	 * of severe problems that I don't see why we should maintain.
	 */
	switch (type)
	{
		case E_ERROR:
		case E_CORE_ERROR:
		case E_COMPILE_ERROR:
		case E_USER_ERROR:
		case E_PARSE:
			elevel = ERROR;
			break;
		case E_WARNING:
		case E_CORE_WARNING:
		case E_COMPILE_WARNING:
		case E_USER_WARNING:
		case E_STRICT:
			elevel = WARNING;
			break;
		case E_NOTICE:
		case E_USER_NOTICE:
			elevel = NOTICE;
			break;
		default:
			elevel = ERROR;
			break;
	}

	REPORT_PHP_MEMUSAGE("reporting error");

	/*
	 * If this is a severe problem, we need to make PHP aware of it, so first
	 * save the error message and then bail out of the PHP block.  With luck,
	 * this will be trapped by a zend_try/zend_catch block outwards in PL/php
	 * code, which would translate it to a Postgres elog(ERROR), leaving
	 * everything in a consistent state.
	 *
	 * For this to work, there must be a try/catch block covering every place
	 * where PHP may raise an error!
	 */
	if (elevel >= ERROR)
	{
		if (lineno != 0)
		{
			char	msgline[1024];
			snprintf(msgline, sizeof(msgline), "%s at line %d", str, lineno);
			error_msg = pstrdup(msgline);
		}
		else
			error_msg = pstrdup(str);

		zend_bailout();
	}

	ereport(elevel,
			(errmsg("plphp: %s", str)));
}

/* Check if the name can be a valid PHP variable name */
static bool
is_valid_php_identifier(char *name)
{
	int 	len,
			i;

	Assert(name);

	len = strlen(name);

	/* Should start from the letter */
	if (!isalpha(name[0]))
		return false;
	for (i = 1; i < len; i++)
	{
		/* Only letters, digits and underscores are allowed */
		if (!isalpha(name[i]) && !isdigit(name[i]) && name[i] != '_')
			return false;
	}
	return true;
}

/*
 * vim:ts=4:sw=4:cino=(0
 */

/**********************************************************************
 * plphp_io.c
 *
 * Support functions for PL/php -- mainly functions to convert stuff
 * from the PHP representation to PostgreSQL representation and vice
 * versa, either text or binary representations.
 *
 * $Id$
 *
 **********************************************************************/

#include "postgres.h"
#include "plphp_io.h"

#include "catalog/pg_type.h"
#include "executor/spi.h"
#include "funcapi.h"
#include "lib/stringinfo.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/memutils.h"

/*
 * plphp_zval_from_tuple
 *		 Build a PHP hash from a tuple.
 */
zval *
plphp_zval_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
{
	int			i;
	char	   *attname = NULL;
	zval	   *array;

	MAKE_STD_ZVAL(array);
	array_init(array);

	for (i = 0; i < tupdesc->natts; i++)
	{
		char *attdata;

		/* Get the attribute name */
		attname = tupdesc->attrs[i]->attname.data;

		/* and get its value */
		if ((attdata = SPI_getvalue(tuple, tupdesc, i + 1)) != NULL)
		{
			/* "true" means strdup the string */
			add_assoc_string(array, attname, attdata, true);
			pfree(attdata);
		}
		else
			add_assoc_null(array, attname);
	}
	return array;
}

/*
 * plphp_htup_from_zval
 * 		Build a HeapTuple from a zval (which must be an array) and a TupleDesc.
 *
 * The return HeapTuple is allocated in the current memory context and must
 * be freed by the caller.
 *
 * If zval doesn't contain any of the element names from the TupleDesc,
 * build a tuple from the first N elements. This allows us to accept
 * arrays in form array(1,2,3) as the result of functions with OUT arguments.
 * XXX -- possible optimization: keep the memory context created and only
 * reset it between calls.
 */
HeapTuple
plphp_htup_from_zval(zval *val, TupleDesc tupdesc)
{
	MemoryContext	oldcxt;
	MemoryContext	tmpcxt;
	HeapTuple		ret;
	AttInMetadata  *attinmeta;
	HashPosition	pos;
	zval		  **element;
	char		  **values;
	int				i;
	bool			allempty = true;

	tmpcxt = AllocSetContextCreate(TopTransactionContext,
								   "htup_from_zval cxt",
								   ALLOCSET_DEFAULT_MINSIZE,
								   ALLOCSET_DEFAULT_INITSIZE,
								   ALLOCSET_DEFAULT_MAXSIZE);
	oldcxt = MemoryContextSwitchTo(tmpcxt);

	values = (char **) palloc(tupdesc->natts * sizeof(char *));

	for (i = 0; i < tupdesc->natts; i++)
	{
		char   *key = SPI_fname(tupdesc, i + 1);
		zval   *scalarval = plphp_array_get_elem(val, key);

		values[i] = plphp_zval_get_cstring(scalarval, true, true);
		/*
		 * Reset the flag is even one of the keys actually exists,
		 * even if it is NULL.
		 */
		if (scalarval != NULL)
			allempty = false;
	}
	/* None of the names from the tuple exists,
	 * try to get 1st N array elements and assign them to the tuple
	 */
	if (allempty)
		for (i = 0,
			 zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(val), &pos);
			 (zend_hash_get_current_data_ex(Z_ARRVAL_P(val),
										   (void **) &element,
											&pos) == SUCCESS) &&
			(i < tupdesc->natts);
			zend_hash_move_forward_ex(Z_ARRVAL_P(val), &pos), i++)
			values[i] = plphp_zval_get_cstring(element[0], true, true);

	attinmeta = TupleDescGetAttInMetadata(tupdesc);

	MemoryContextSwitchTo(oldcxt);
	ret = BuildTupleFromCStrings(attinmeta, values);

	MemoryContextDelete(tmpcxt);

	return ret;
}

/* plphp_srf_htup_from_zval
 * 		Build a tuple from a zval and a TupleDesc, for a SRF.
 *
 * Like above, but we don't use the names of the array attributes;
 * rather we build the tuple in order.  Also, we get a MemoryContext
 * from the caller and just clean it at return, rather than building it each
 * time.
 */
HeapTuple
plphp_srf_htup_from_zval(zval *val, AttInMetadata *attinmeta,
						 MemoryContext cxt)
{
	MemoryContext	oldcxt;
	HeapTuple		ret;
	HashPosition	pos;
	char		  **values;
	zval		  **element;
	int				i = 0;

	oldcxt = MemoryContextSwitchTo(cxt);

	/*
	 * Use palloc0 to initialize values to NULL, just in case the user does
	 * not pass all needed attributes
	 */
	values = (char **) palloc0(attinmeta->tupdesc->natts * sizeof(char *));

	/*
	 * If the input zval is an array, build a tuple using each element as an
	 * attribute.  Exception: if the return tuple has a single element and
	 * it's an array type, use the whole array as a single value.
	 *
	 * If the input zval is a scalar, use it as an element directly.
	 */
	if (Z_TYPE_P(val) == IS_ARRAY)
	{
		if (attinmeta->tupdesc->natts == 1)
		{
			/* Is it an array? */
			if (attinmeta->tupdesc->attrs[0]->attndims != 0 ||
				!OidIsValid(get_element_type(attinmeta->tupdesc->attrs[0]->atttypid)))
			{
				zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(val), &pos);
				zend_hash_get_current_data_ex(Z_ARRVAL_P(val),
											  (void **) &element,
											  &pos);
				values[0] = plphp_zval_get_cstring(element[0], true, true);
			}
			else
				values[0] = plphp_zval_get_cstring(val, true, true);
		}
		else
		{
			/*
			 * Ok, it's an array and the return tuple has more than one
			 * attribute, so scan each array element.
			 */
			for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(val), &pos);
				 zend_hash_get_current_data_ex(Z_ARRVAL_P(val),
											   (void **) &element,
											   &pos) == SUCCESS;
				 zend_hash_move_forward_ex(Z_ARRVAL_P(val), &pos))
			{
				/* avoid overrunning the palloc'ed chunk */
				if (i >= attinmeta->tupdesc->natts)
				{
					elog(WARNING, "more elements in array than attributes in return type");
					break;
				}

				values[i++] = plphp_zval_get_cstring(element[0], true, true);
			}
		}
	}
	else
	{
		/* The passed zval is not an array -- use as the only attribute */
		if (attinmeta->tupdesc->natts != 1)
			ereport(ERROR,
					(errmsg("returned array does not correspond to "
							"declared return value")));

		values[0] = plphp_zval_get_cstring(val, true, true);
	}

	MemoryContextSwitchTo(oldcxt);

	ret = BuildTupleFromCStrings(attinmeta, values);

	MemoryContextReset(cxt);

	return ret;
}

/*
 * plphp_convert_to_pg_array
 * 		Convert a zval into a Postgres text array representation.
 *
 * The return value is palloc'ed in the current memory context and
 * must be freed by the caller.
 */
char *
plphp_convert_to_pg_array(zval *array)
{
	int			arr_size;
	zval	  **element;
	int			i = 0;
	HashPosition 	pos;
	StringInfoData	str;

	initStringInfo(&str);

	arr_size = zend_hash_num_elements(Z_ARRVAL_P(array));

	appendStringInfoChar(&str, '{');
	if (Z_TYPE_P(array) == IS_ARRAY)
	{
		for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(array), &pos);
			 zend_hash_get_current_data_ex(Z_ARRVAL_P(array),
										   (void **) &element,
										   &pos) == SUCCESS;
			 zend_hash_move_forward_ex(Z_ARRVAL_P(array), &pos))
		{
			char *tmp;

			switch (Z_TYPE_P(element[0]))
			{
				case IS_LONG:
					appendStringInfo(&str, "%li", element[0]->value.lval);
					break;
				case IS_DOUBLE:
					appendStringInfo(&str, "%f", element[0]->value.dval);
					break;
				case IS_STRING:
					appendStringInfo(&str, "\"%s\"", element[0]->value.str.val);
					break;
				case IS_ARRAY:
					tmp = plphp_convert_to_pg_array(element[0]);
					appendStringInfo(&str, "%s", tmp);
					pfree(tmp);
					break;
				default:
					elog(ERROR, "unrecognized element type %d",
						 Z_TYPE_P(element[0]));
			}

			if (i != arr_size - 1)
				appendStringInfoChar(&str, ',');
			i++;
		}
	}

	appendStringInfoChar(&str, '}');

	return str.data;
}

/*
 * plphp_convert_from_pg_array
 * 		Convert a Postgres text array representation to a PHP array
 * 		(zval type thing).
 *
 * FIXME -- does not work if there are embedded {'s in the input value.
 *
 * FIXME -- does not correctly quote/dequote the values
 */
zval *
plphp_convert_from_pg_array(char *input TSRMLS_DC)
{
	zval	   *retval = NULL;
	int			i;
	StringInfoData str;

	initStringInfo(&str);

	MAKE_STD_ZVAL(retval);
	array_init(retval);

	for (i = 0; i < strlen(input); i++)
	{
		if (input[i] == '{')
			appendStringInfoString(&str, "array(");
		else if (input[i] == '}')
			appendStringInfoChar(&str, ')');
		else
			appendStringInfoChar(&str, input[i]);
	}
	appendStringInfoChar(&str, ';');

	if (zend_eval_string(str.data, retval,
						 "plphp array input parameter" TSRMLS_CC) == FAILURE)
		elog(ERROR, "plphp: convert to internal representation failure");

	pfree(str.data);

	return retval;
}

/*
 * plphp_array_get_elem
 * 		Return a pointer to the array element with the given key
 */
zval *
plphp_array_get_elem(zval *array, char *key)
{
	zval	  **element;

	if (!array)
		elog(ERROR, "passed zval is not a valid pointer");
	if (Z_TYPE_P(array) != IS_ARRAY)
		elog(ERROR, "passed zval is not an array");

	if (zend_symtable_find(array->value.ht,
					   	   key,
					       strlen(key) + 1,
					       (void **) &element) != SUCCESS)
		return NULL;

	return element[0];
}

/*
 * zval_get_cstring
 *		Get a C-string representation of a zval.
 *
 * All return values, except those that are NULL, are palloc'ed in the current
 * memory context and must be freed by the caller.
 *
 * If the do_array parameter is false, then array values will not be converted
 * and an error will be raised instead.
 *
 * If the null_ok parameter is true, we will return NULL for a NULL zval.
 * Otherwise we raise an error.
 */
char *
plphp_zval_get_cstring(zval *val, bool do_array, bool null_ok)
{
	char *ret;

	if (!val)
	{
		if (null_ok)
			return NULL;
		else
			elog(ERROR, "invalid zval pointer");
	}

	switch (Z_TYPE_P(val))
	{
		case IS_NULL:
			return NULL;
		case IS_LONG:
			ret = palloc(64);
			snprintf(ret, 64, "%ld", Z_LVAL_P(val));
			break;
		case IS_DOUBLE:
			ret = palloc(64);
			snprintf(ret, 64, "%f", Z_DVAL_P(val));
			break;
		case IS_BOOL:
			ret = palloc(8);
			snprintf(ret, 8, "%s", Z_BVAL_P(val) ? "true": "false");
			break;
		case IS_STRING:
			ret = palloc(Z_STRLEN_P(val) + 1);
			snprintf(ret, Z_STRLEN_P(val) + 1, "%s",
					 Z_STRVAL_P(val));
			break;
		case IS_ARRAY:
			if (!do_array)
				elog(ERROR, "can't stringize array value");
			ret = plphp_convert_to_pg_array(val);
			break;
		default:
			/* keep compiler quiet */
			ret = NULL;
			elog(ERROR, "can't stringize value of type %d", val->type);
	}

	return ret;
}

/*
 * plphp_build_tuple_argument
 *
 * Build a PHP array from all attributes of a given tuple
 */
zval *
plphp_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc)
{
	int			i;
	zval	   *output;
	Datum		attr;
	bool		isnull;
	char	   *attname;
	char	   *outputstr;
	HeapTuple	typeTup;
	Oid			typoutput;
	Oid			typioparam;

	MAKE_STD_ZVAL(output);
	array_init(output);

	for (i = 0; i < tupdesc->natts; i++)
	{
		/* Ignore dropped attributes */
		if (tupdesc->attrs[i]->attisdropped)
			continue;

		/* Get the attribute name */
		attname = tupdesc->attrs[i]->attname.data;

		/* Get the attribute value */
		attr = heap_getattr(tuple, i + 1, tupdesc, &isnull);

		/* If it is null, set it to undef in the hash. */
		if (isnull)
		{
			add_next_index_unset(output);
			continue;
		}

		/*
		 * Lookup the attribute type in the syscache for the output function
		 */
		typeTup = SearchSysCache(TYPEOID,
								 ObjectIdGetDatum(tupdesc->attrs[i]->atttypid),
								 0, 0, 0);
		if (!HeapTupleIsValid(typeTup))
		{
			elog(ERROR, "cache lookup failed for type %u",
				 tupdesc->attrs[i]->atttypid);
		}

		typoutput = ((Form_pg_type) GETSTRUCT(typeTup))->typoutput;
		typioparam = getTypeIOParam(typeTup);
		ReleaseSysCache(typeTup);

		/* Append the attribute name and the value to the list. */
		outputstr =
			DatumGetCString(OidFunctionCall3(typoutput, attr,
											 ObjectIdGetDatum(typioparam),
											 Int32GetDatum(tupdesc->attrs[i]->atttypmod)));
		add_assoc_string(output, attname, outputstr, 1);
		pfree(outputstr);
	}

	return output;
}

/*
 * plphp_modify_tuple
 * 		Return the modified NEW tuple, for use as return value in a BEFORE
 * 		trigger.  outdata must point to the $_TD variable from the PHP
 * 		function.
 *
 * The tuple will be allocated in the current memory context and must be freed
 * by the caller.
 *
 * XXX Possible optimization: make this a global context that is not deleted,
 * but only reset each time this function is called.  (Think about triggers
 * calling other triggers though).
 */
HeapTuple
plphp_modify_tuple(zval *outdata, TriggerData *tdata)
{
	TupleDesc	tupdesc;
	HeapTuple	rettuple;
	zval	   *newtup;
	zval	  **element;
	char	  **vals;
	int			i;
	AttInMetadata *attinmeta;
	MemoryContext tmpcxt,
				  oldcxt;

	tmpcxt = AllocSetContextCreate(CurTransactionContext,
								   "PL/php NEW context",
								   ALLOCSET_DEFAULT_MINSIZE,
								   ALLOCSET_DEFAULT_INITSIZE,
								   ALLOCSET_DEFAULT_MAXSIZE);

	oldcxt = MemoryContextSwitchTo(tmpcxt);

	/* Fetch "new" from $_TD */
	if (zend_hash_find(outdata->value.ht,
					   "new", strlen("new") + 1,
					   (void **) &element) != SUCCESS)
		elog(ERROR, "$_TD['new'] not found");

	if (Z_TYPE_P(element[0]) != IS_ARRAY)
		elog(ERROR, "$_TD['new'] must be an array");
	newtup = element[0];

	/* Fetch the tupledesc and metadata */
	tupdesc = tdata->tg_relation->rd_att;
	attinmeta = TupleDescGetAttInMetadata(tupdesc);

	i = zend_hash_num_elements(Z_ARRVAL_P(newtup));

	if (tupdesc->natts > i)
		ereport(ERROR,
				(errmsg("insufficient number of keys in $_TD['new']"),
				 errdetail("At least %d expected, %d found.",
						   tupdesc->natts, i)));

	vals = (char **) palloc(tupdesc->natts * sizeof(char *));

	/*
	 * For each attribute in the tupledesc, get its value from newtup and put
	 * it in an array of cstrings.
	 */
	for (i = 0; i < tupdesc->natts; i++)
	{
		zval  **element;
		char   *attname = NameStr(tupdesc->attrs[i]->attname);

		/* Fetch the attribute value from the zval */
		if (zend_symtable_find(newtup->value.ht, attname, strlen(attname) + 1,
						   	   (void **) &element) != SUCCESS)
			elog(ERROR, "$_TD['new'] does not contain attribute \"%s\"",
				 attname);

		vals[i] = plphp_zval_get_cstring(element[0], true, true);
	}

	/* Return to the original context so that the new tuple will survive */
	MemoryContextSwitchTo(oldcxt);

	/* Build the tuple */
	rettuple = BuildTupleFromCStrings(attinmeta, vals);

	/* Free the memory used */
	MemoryContextDelete(tmpcxt);

	return rettuple;
}

/*
 * vim:ts=4:sw=4:cino=(0
 */

/**********************************************************************
 * plphp_spi.c - SPI-related functions for PL/php.
 *
 * This software is copyright (c) Command Prompt Inc.
 *
 * 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.
 *
 * IDENTIFICATION
 *		$Id$
 *********************************************************************
 */

#include "postgres.h"
#include "plphp_spi.h"
#include "plphp_io.h"

/* PHP stuff */
#include "php.h"

/* PostgreSQL stuff */
#include "access/xact.h"
#include "miscadmin.h"

#undef DEBUG_PLPHP_MEMORY

#ifdef DEBUG_PLPHP_MEMORY
#define REPORT_PHP_MEMUSAGE(where) \
	elog(NOTICE, "PHP mem usage: «%s»: %u", where, AG(allocated_memory));
#else
#define REPORT_PHP_MEMUSAGE(a)
#endif

/* resource type Id for SPIresult */
int SPIres_rtype;

/* SPI function table */
zend_function_entry spi_functions[] =
{
	ZEND_FE(spi_exec, NULL)
	ZEND_FE(spi_fetch_row, NULL)
	ZEND_FE(spi_processed, NULL)
	ZEND_FE(spi_status, NULL)
	ZEND_FE(spi_rewind, NULL)
	ZEND_FE(pg_raise, NULL)
	ZEND_FE(return_next, NULL)
	{NULL, NULL, NULL}
};

/* SRF support: */
FunctionCallInfo current_fcinfo = NULL;
TupleDesc current_tupledesc = NULL;
AttInMetadata *current_attinmeta = NULL;
MemoryContext current_memcxt = NULL;
Tuplestorestate *current_tuplestore = NULL;

/* A symbol table to save for return_next for the RETURNS TABLE case */
HashTable *saved_symbol_table;

static zval *get_table_arguments(AttInMetadata *attinmeta);

/*
 * spi_exec
 * 		PL/php equivalent to SPI_exec().
 *
 * This function creates and return a PHP resource which describes the result
 * of a user-specified query.  If the query returns tuples, it's possible to
 * retrieve them by using spi_fetch_row.
 *
 * Receives one or two arguments.  The mandatory first argument is the query
 * text.  The optional second argument is the tuple limit.
 *
 * Note that just like PL/Perl, we start a subtransaction before invoking the
 * SPI call, and automatically roll it back if the call fails.
 */
ZEND_FUNCTION(spi_exec)
{
	char	   *query;
	int			query_len;
	long		status;
	long		limit;
	php_SPIresult *SPIres;
	int			spi_id;
	MemoryContext oldcontext = CurrentMemoryContext;
	ResourceOwner oldowner = CurrentResourceOwner;

	REPORT_PHP_MEMUSAGE("spi_exec called");

	if ((ZEND_NUM_ARGS() > 2) || (ZEND_NUM_ARGS() < 1))
		WRONG_PARAM_COUNT;

	/* Parse arguments */
	if (ZEND_NUM_ARGS() == 2)
	{
		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl",
								  &query, &query_len, &limit) == FAILURE)
		{
			zend_error(E_WARNING, "Can not parse parameters in %s",
					   get_active_function_name(TSRMLS_C));
			RETURN_FALSE;
		}
	}
	else if (ZEND_NUM_ARGS() == 1)
	{
		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",
								  &query, &query_len) == FAILURE)
		{
			zend_error(E_WARNING, "Can not parse parameters in %s",
					   get_active_function_name(TSRMLS_C));
			RETURN_FALSE;
		}
		limit = 0;
	}
	else
	{
		zend_error(E_WARNING, "Incorrect number of parameters to %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}

	BeginInternalSubTransaction(NULL);
	MemoryContextSwitchTo(oldcontext);

	/* Call SPI */
	PG_TRY();
	{
		status = SPI_exec(query, limit);

		ReleaseCurrentSubTransaction();
		MemoryContextSwitchTo(oldcontext);
		CurrentResourceOwner = oldowner;

		/*
		 * AtEOSubXact_SPI() should not have popped any SPI context, but just
		 * in case it did, make sure we remain connected.
		 */
		SPI_restore_connection();
	}
	PG_CATCH();
	{
		ErrorData	*edata;

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

		/* Abort the inner trasaction */
		RollbackAndReleaseCurrentSubTransaction();
		MemoryContextSwitchTo(oldcontext);
		CurrentResourceOwner = oldowner;

		/*
		 * If AtEOSubXact_SPI() popped any SPI context of the subxact, it will
		 * have left us in a disconnected state.  We need this hack to return
		 * to connected state.
		 */
		SPI_restore_connection();

		/* bail PHP out */
		zend_error(E_ERROR, "%s", strdup(edata->message));

		/* Can't get here, but keep compiler quiet */
		return;
	}
	PG_END_TRY();

	/* This malloc'ed chunk is freed in php_SPIresult_destroy */
	SPIres = (php_SPIresult *) malloc(sizeof(php_SPIresult));
	if (!SPIres)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory")));

	/* Prepare the return resource */
	SPIres->SPI_processed = SPI_processed;
	if (status == SPI_OK_SELECT)
		SPIres->SPI_tuptable = SPI_tuptable;
	else
		SPIres->SPI_tuptable = NULL;
	SPIres->current_row = 0;
	SPIres->status = status;

	REPORT_PHP_MEMUSAGE("spi_exec: creating resource");

	/* Register the resource to PHP so it will be able to free it */
	spi_id = ZEND_REGISTER_RESOURCE(return_value, (void *) SPIres,
					 				SPIres_rtype);

	REPORT_PHP_MEMUSAGE("spi_exec: returning");

	RETURN_RESOURCE(spi_id);
}

/*
 * spi_fetch_row
 * 		Grab a row from a SPI result (from spi_exec).
 *
 * This function receives a resource Id and returns a PHP hash representing the
 * next tuple in the result, or false if no tuples remain.
 *
 * XXX Apparently this is leaking memory.  How do we tell PHP to free the tuple
 * once the user is done with it?
 */
ZEND_FUNCTION(spi_fetch_row)
{
	zval	   *row = NULL;
	zval	  **z_spi = NULL;
	php_SPIresult	*SPIres;

	REPORT_PHP_MEMUSAGE("spi_fetch_row: called");

	if (ZEND_NUM_ARGS() != 1)
		WRONG_PARAM_COUNT;

	if (zend_get_parameters_ex(1, &z_spi) == FAILURE)
	{
		zend_error(E_WARNING, "Can not parse parameters in %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}
	if (z_spi == NULL)
	{
		zend_error(E_WARNING, "Could not get SPI resource in %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}

	ZEND_FETCH_RESOURCE(SPIres, php_SPIresult *, z_spi, -1, "SPI result",
						SPIres_rtype);

	if (SPIres->status != SPI_OK_SELECT)
	{
		zend_error(E_WARNING, "SPI status is not good");
		RETURN_FALSE;
	}

	if (SPIres->current_row < SPIres->SPI_processed)
	{
		row = plphp_zval_from_tuple(SPIres->SPI_tuptable->vals[SPIres->current_row],
			  						SPIres->SPI_tuptable->tupdesc);
		SPIres->current_row++;

		*return_value = *row;

		zval_copy_ctor(return_value);
		zval_dtor(row);
		FREE_ZVAL(row);

	}
	else
		RETURN_FALSE;

	REPORT_PHP_MEMUSAGE("spi_fetch_row: finish");
}

/*
 * spi_processed
 * 		Return the number of tuples returned in a spi_exec call.
 */
ZEND_FUNCTION(spi_processed)
{
	zval	   **z_spi = NULL;
	php_SPIresult	*SPIres;

	REPORT_PHP_MEMUSAGE("spi_processed: start");

	if (ZEND_NUM_ARGS() != 1)
		WRONG_PARAM_COUNT;

	if (zend_get_parameters_ex(1, &z_spi) == FAILURE)
	{
		zend_error(E_WARNING, "Cannot parse parameters in %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}
	if (z_spi == NULL)
	{
		zend_error(E_WARNING, "Could not get SPI resource in %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}

	ZEND_FETCH_RESOURCE(SPIres, php_SPIresult *, z_spi, -1, "SPI result",
						SPIres_rtype);

	REPORT_PHP_MEMUSAGE("spi_processed: finish");

	RETURN_LONG(SPIres->SPI_processed);
}

/*
 * spi_status
 * 		Return the status returned by a previous spi_exec call, as a string.
 */
ZEND_FUNCTION(spi_status)
{
	zval	   **z_spi = NULL;
	php_SPIresult	*SPIres;

	REPORT_PHP_MEMUSAGE("spi_status: start");

	if (ZEND_NUM_ARGS() != 1)
		WRONG_PARAM_COUNT;

	if (zend_get_parameters_ex(1, &z_spi) == FAILURE)
	{
		zend_error(E_WARNING, "Cannot parse parameters in %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}
	if (z_spi == NULL)
	{
		zend_error(E_WARNING, "Could not get SPI resource in %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}

	ZEND_FETCH_RESOURCE(SPIres, php_SPIresult *, z_spi, -1, "SPI result",
						SPIres_rtype);

	REPORT_PHP_MEMUSAGE("spi_status: finish");

	/*
	 * XXX The cast is wrong, but we use it to prevent a compiler warning.
	 * Note that the second parameter to RETURN_STRING is "duplicate", so
	 * we are returning a copy of the string anyway.
	 */
	RETURN_STRING((char *) SPI_result_code_string(SPIres->status), true);
}

/*
 * spi_rewind
 * 		Resets the internal counter for spi_fetch_row, so the next
 * 		spi_fetch_row call will start fetching from the beginning.
 */
ZEND_FUNCTION(spi_rewind)
{
	zval	   **z_spi = NULL;
	php_SPIresult	*SPIres;

	if (ZEND_NUM_ARGS() != 1)
		WRONG_PARAM_COUNT;

	if (zend_get_parameters_ex(1, &z_spi) == FAILURE)
	{
		zend_error(E_WARNING, "Cannot parse parameters in %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}
	if (z_spi == NULL)
	{
		zend_error(E_WARNING, "Could not get SPI resource in %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}

	ZEND_FETCH_RESOURCE(SPIres, php_SPIresult *, z_spi, -1, "SPI result",
						SPIres_rtype);

	SPIres->current_row = 0;

	RETURN_NULL();
}
/*
 * pg_raise
 *      User-callable function for sending messages to the Postgres log.
 */
ZEND_FUNCTION(pg_raise)
{
	char       *level = NULL,
			   *message = NULL;
	int         level_len,
				message_len,
				elevel = 0;

	if (ZEND_NUM_ARGS() != 2)
	{
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("wrong number of arguments to %s", "pg_raise")));
	}

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",
							  &level, &level_len,
							  &message, &message_len) == FAILURE)
	{
		zend_error(E_WARNING, "cannot parse parameters in %s",
				   get_active_function_name(TSRMLS_C));
	}

	if (strcasecmp(level, "ERROR") == 0)
		elevel = E_ERROR;
	else if (strcasecmp(level, "WARNING") == 0)
		elevel = E_WARNING;
	else if (strcasecmp(level, "NOTICE") == 0)
		elevel = E_NOTICE;
	else
		zend_error(E_ERROR, "incorrect log level");

	zend_error(elevel, "%s", message);
}

/*
 * return_next
 * 		Add a tuple to the current tuplestore
 */
ZEND_FUNCTION(return_next)
{
	MemoryContext	oldcxt;
	zval	   *param;
	HeapTuple	tup;
	ReturnSetInfo *rsi;

	/*
	 * Disallow use of return_next inside non-SRF functions
	 */
	if (current_fcinfo == NULL || current_fcinfo->flinfo == NULL ||
		!current_fcinfo->flinfo->fn_retset)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot use return_next in functions not declared to "
						"return a set")));

	rsi = (ReturnSetInfo *) current_fcinfo->resultinfo;

	Assert(current_tupledesc != NULL);
	Assert(rsi != NULL);

	if (ZEND_NUM_ARGS() > 1)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("wrong number of arguments to %s", "return_next")));

	if (ZEND_NUM_ARGS() == 0)
	{
		/*
		 * Called from the function declared with RETURNS TABLE
	     */
		param = get_table_arguments(current_attinmeta);
	}
	else if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z",
							  &param) == FAILURE)
	{
		zend_error(E_WARNING, "cannot parse parameters in %s",
				   get_active_function_name(TSRMLS_C));
	}

	/* Use the per-query context so that the tuplestore survives */
	oldcxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);

	/* Form the tuple */
	tup = plphp_srf_htup_from_zval(param, current_attinmeta, current_memcxt);

	/* First call?  Create the tuplestore. */
	if (!current_tuplestore)
		current_tuplestore = tuplestore_begin_heap(true, false, work_mem);

	/* Save the tuple and clean up */
	tuplestore_puttuple(current_tuplestore, tup);
	heap_freetuple(tup);

	MemoryContextSwitchTo(oldcxt);
}

/*
 * php_SPIresult_destroy
 * 		Free the resources allocated by a spi_exec call.
 *
 * This is automatically called when the resource goes out of scope
 * or is overwritten by another resource.
 */
void
php_SPIresult_destroy(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
	php_SPIresult *res = (php_SPIresult *) rsrc->ptr;

	if (res->SPI_tuptable != NULL)
		SPI_freetuptable(res->SPI_tuptable);

	free(res);
}

/* Return an array of TABLE argument values for return_next */
static
zval *get_table_arguments(AttInMetadata *attinmeta)
{
	zval   *retval = NULL;
	int		i;

	MAKE_STD_ZVAL(retval);
	array_init(retval);

	Assert(attinmeta->tupdesc);
	Assert(saved_symbol_table != NULL);
	/* Extract OUT argument names */
	for (i = 0; i < attinmeta->tupdesc->natts; i++)
	{
		zval 	**val;
		char 	*attname;

		Assert(!attinmeta->tupdesc->attrs[i]->attisdropped);

		attname = NameStr(attinmeta->tupdesc->attrs[i]->attname);

		if (zend_hash_find(saved_symbol_table,
						   attname, strlen(attname) + 1,
						   (void **)&val) == SUCCESS)

			add_next_index_zval(retval, *val);
		else
			add_next_index_unset(retval);
	}
	return retval;
}

/*
 * vim:ts=4:sw=4:cino=(0
 */

/**********************************************************************
 * plphp.c - PHP as a procedural language for PostgreSQL
 *
 * This software is copyright (c) Command Prompt Inc.
 *
 * 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.
 *
 * IDENTIFICATION
 *		$Id$
 *********************************************************************
 */

/* Package configuration generated by autoconf */
#include "config.h"

/* First round of undefs, to eliminate collision between plphp and postgresql
 * definitions
 */

#undef PACKAGE_BUGREPORT
#undef PACKAGE_NAME
#undef PACKAGE_STRING
#undef PACKAGE_TARNAME
#undef PACKAGE_VERSION

/* PostgreSQL stuff */
#include "postgres.h"
#include "access/heapam.h"
#include "access/transam.h"
#include "access/htup_details.h"

#include "catalog/catversion.h"
#include "catalog/pg_language.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"

#include "commands/trigger.h"
#include "fmgr.h"
#include "funcapi.h"			/* needed for SRF support */
#include "lib/stringinfo.h"

#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/elog.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"

/*
 * These are defined again in php.h, so undef them to avoid some
 * cpp warnings.
 */
#undef PACKAGE_BUGREPORT
#undef PACKAGE_NAME
#undef PACKAGE_STRING
#undef PACKAGE_TARNAME
#undef PACKAGE_VERSION

/* PHP stuff */
#include "php.h"

#include "php_variables.h"
#include "php_globals.h"
#include "zend_hash.h"
#include "zend_modules.h"

#include "php_ini.h"			/* needed for INI_HARDCODED */
#include "php_main.h"

/* Our own stuff */
#include "plphp_io.h"
#include "plphp_spi.h"

/* system stuff */
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif

#define INI_HARDCODED(name,value) \
		zend_alter_ini_entry(name, sizeof(name), value, strlen(value), \
							 PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE);

/* Check for PostgreSQL version */
#if (CATALOG_VERSION_NO >= 200709301)
#define PG_VERSION_83_COMPAT
#endif
#if (CATALOG_VERSION_NO >= 200611241)
#define PG_VERSION_82_COMPAT
#endif
/* We only support 8.1 and above */
#if (CATALOG_VERSION_NO >= 200510211)
#define PG_VERSION_81_COMPAT
#else
#error "Unsupported PostgreSQL version"
#endif

#undef DEBUG_PLPHP_MEMORY

#ifdef DEBUG_PLPHP_MEMORY
#define REPORT_PHP_MEMUSAGE(where) \
	elog(NOTICE, "PHP mem usage: %s: %u", where, AG(allocated_memory));
#else
#define REPORT_PHP_MEMUSAGE(a)
#endif

/* PostgreSQL starting from v 8.2 requires this define
 * for all modules.
 */
#ifdef PG_VERSION_82_COMPAT
PG_MODULE_MAGIC;
#else
/* Supress warnings on 8.1 and below */
#define ReleaseTupleDesc(tupdesc)
#endif

/* PHP 5.2 and earlier do not contain these definitions */
#ifndef Z_SET_ISREF_P
#define Z_SET_ISREF_P(foo) (foo)->is_ref = 1
#define Z_UNSET_ISREF_P(foo) (foo)->is_ref = 0
#endif

/* 8.2 compatibility */
#ifndef TYPTYPE_PSEUDO
#define TYPTYPE_PSEUDO 'p'
#define TYPTYPE_COMPOSITE 'c'
#endif

/* Check the argument type to expect to accept an initial value */
#define IS_ARGMODE_OUT(mode) ((mode) == PROARGMODE_OUT || \
(mode) == PROARGMODE_TABLE)
/*
 * Return types.  Why on earth is this a bitmask?  Beats me.
 * We should have separate flags instead.
 */
typedef enum pl_type
{
	PL_TUPLE = 1 << 0,
	PL_ARRAY = 1 << 1,
	PL_PSEUDO = 1 << 2
} pl_type;

/*
 * The information we cache about loaded procedures.
 *
 * "proname" is the name of the function, given by the user.
 *
 * fn_xmin and fn_cmin are used to know when a function has been redefined and
 * needs to be recompiled.
 *
 * trusted indicates whether the function was created with a trusted handler.
 *
 * ret_type is a weird bitmask that indicates whether this function returns a
 * tuple, an array or a pseudotype.  ret_oid is the Oid of the return type.
 * retset indicates whether the function was declared to return a set.
 *
 * arg_argmode indicates whether the argument is IN, OUT or both. It follows
 * values in pg_proc.proargmodes.
 *
 * n_out_args - total number of OUT or INOUT arguments.
 * arg_out_tupdesc is a tuple descriptor of the tuple constructed for OUT args.
 *
 * XXX -- maybe this thing needs to be rethought.
 */
typedef struct plphp_proc_desc
{
	char	   *proname;
	TransactionId fn_xmin;
	CommandId	fn_cmin;
	bool		trusted;
	pl_type		ret_type;
	Oid			ret_oid;		/* Oid of returning type */
	bool		retset;
	FmgrInfo	result_in_func;
	Oid			result_typioparam;
	int			n_out_args;
	int			n_total_args;
	int			n_mixed_args;
	FmgrInfo	arg_out_func[FUNC_MAX_ARGS];
	Oid			arg_typioparam[FUNC_MAX_ARGS];
	char		arg_typtype[FUNC_MAX_ARGS];
	char		arg_argmode[FUNC_MAX_ARGS];
	TupleDesc	args_out_tupdesc;
} plphp_proc_desc;

/*
 * Global data
 */
static bool plphp_first_call = true;
static zval *plphp_proc_array = NULL;

/* for PHP write/flush */
static StringInfo currmsg = NULL;

/*
 * for PHP <-> Postgres error message passing
 *
 * XXX -- it would be much better if we could save errcontext,
 * errhint, etc as well.
 */
static char *error_msg = NULL;
/*
 * Forward declarations
 */
static void plphp_init_all(void);
void		plphp_init(void);

PG_FUNCTION_INFO_V1(plphp_call_handler);
Datum plphp_call_handler(PG_FUNCTION_ARGS);

PG_FUNCTION_INFO_V1(plphp_validator);
Datum plphp_validator(PG_FUNCTION_ARGS);

static Datum plphp_trigger_handler(FunctionCallInfo fcinfo,
								   plphp_proc_desc *desc
								   TSRMLS_DC);
static Datum plphp_func_handler(FunctionCallInfo fcinfo,
							    plphp_proc_desc *desc
								TSRMLS_DC);
static Datum plphp_srf_handler(FunctionCallInfo fcinfo,
						   	   plphp_proc_desc *desc
							   TSRMLS_DC);

static plphp_proc_desc *plphp_compile_function(Oid fnoid, bool is_trigger TSRMLS_DC);
static zval *plphp_call_php_func(plphp_proc_desc *desc,
								 FunctionCallInfo fcinfo
								 TSRMLS_DC);
static zval *plphp_call_php_trig(plphp_proc_desc *desc,
								 FunctionCallInfo fcinfo, zval *trigdata
								 TSRMLS_DC);

static void plphp_error_cb(int type, const char *filename, const uint lineno,
								  const char *fmt, va_list args);
static bool is_valid_php_identifier(char *name);

/*
 * FIXME -- this comment is quite misleading actually, which is not surprising
 * since it came verbatim from PL/pgSQL.  Rewrite memory handling here someday
 * and remove it.
 *
 * This routine is a crock, and so is everyplace that calls it.  The problem
 * is that the cached form of plphp functions/queries is allocated permanently
 * (mostly via malloc()) and never released until backend exit.  Subsidiary
 * data structures such as fmgr info records therefore must live forever
 * as well.  A better implementation would store all this stuff in a per-
 * function memory context that could be reclaimed at need.  In the meantime,
 * fmgr_info_cxt must be called specifying TopMemoryContext so that whatever
 * it might allocate, and whatever the eventual function might allocate using
 * fn_mcxt, will live forever too.
 */
static void
perm_fmgr_info(Oid functionId, FmgrInfo *finfo)
{
	fmgr_info_cxt(functionId, finfo, TopMemoryContext);
}

/*
 * sapi_plphp_write
 * 		Called when PHP wants to write something to stdout.
 *
 * We just save the output in a StringInfo until the next Flush call.
 */
static int
sapi_plphp_write(const char *str, uint str_length TSRMLS_DC)
{
	if (currmsg == NULL)
		currmsg = makeStringInfo();

	appendStringInfoString(currmsg, str);

	return str_length;
}

/*
 * sapi_plphp_flush
 * 		Called when PHP wants to flush stdout.
 *
 * The stupid PHP implementation calls write and follows with a Flush right
 * away -- a good implementation would write several times and flush when the
 * message is complete.  To make the output look reasonable in Postgres, we
 * skip the flushing if the accumulated message does not end in a newline.
 */
static void
sapi_plphp_flush(void *sth)
{
	if (currmsg != NULL)
	{
		Assert(currmsg->data != NULL);

		if (currmsg->data[currmsg->len - 1] == '\n')
		{
			/*
			 * remove the trailing newline because elog() inserts another
			 * one
			 */
			currmsg->data[currmsg->len - 1] = '\0';
		}
		elog(LOG, "%s", currmsg->data);

		pfree(currmsg->data);
		pfree(currmsg);
		currmsg = NULL;
	}
	else
		elog(LOG, "attempting to flush a NULL message");
}

static int
sapi_plphp_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
{
	return 1;
}

static void
php_plphp_log_messages(char *message)
{
	elog(LOG, "plphp: %s", message);
}

static sapi_module_struct plphp_sapi_module = {
	"plphp",					/* name */
	"PL/php PostgreSQL Handler",/* pretty name */

	NULL,						/* startup */
	php_module_shutdown_wrapper,/* shutdown */

	NULL,						/* activate */
	NULL,						/* deactivate */

	sapi_plphp_write,			/* unbuffered write */
	sapi_plphp_flush,			/* flush */
	NULL,						/* stat */
	NULL,						/* getenv */

	php_error,					/* sapi_error(int, const char *, ...) */

	NULL,						/* header handler */
	sapi_plphp_send_headers,	/* send headers */
	NULL,						/* send header */

	NULL,						/* read POST */
	NULL,						/* read cookies */

	NULL,						/* register server variables */
	php_plphp_log_messages,		/* log message */

	NULL,						/* Block interrupts */
	NULL,						/* Unblock interrupts */
	STANDARD_SAPI_MODULE_PROPERTIES
};

/*
 * plphp_init_all()		- Initialize all
 *
 * XXX This is called each time a function is invoked.
 */
static void
plphp_init_all(void)
{
	/* Execute postmaster-startup safe initialization */
	if (plphp_first_call)
		plphp_init();

	/*
	 * Any other initialization that must be done each time a new
	 * backend starts -- currently none.
	 */
}

/*
 * This function must not be static, so that it can be used in
 * preload_libraries.  If it is, it will be called by postmaster;
 * otherwise it will be called by each backend the first time a
 * function is called.
 */
void
plphp_init(void)
{
	TSRMLS_FETCH();
	/* Do initialization only once */
	if (!plphp_first_call)
		return;

	/*
	 * Need a Pg try/catch block to prevent an initialization-
	 * failure from bringing the whole server down.
	 */
	PG_TRY();
	{
		zend_try
		{
			/*
			 * XXX This is a hack -- we are replacing the error callback in an
			 * invasive manner that should not be expected to work on future PHP
			 * releases.
			 */
			zend_error_cb = plphp_error_cb;

			/* Omit HTML tags from output */
			plphp_sapi_module.phpinfo_as_text = 1;
			sapi_startup(&plphp_sapi_module);

			if (php_module_startup(&plphp_sapi_module, NULL, 0) == FAILURE)
				elog(ERROR, "php_module_startup call failed");

			/* php_module_startup changed it, so put it back */
			zend_error_cb = plphp_error_cb;

			/*
			 * FIXME -- Figure out what this comment is supposed to mean:
			 *
			 * There is no way to see if we must call zend_ini_deactivate()
			 * since we cannot check if EG(ini_directives) has been initialised
			 * because the executor's constructor does not initialize it.
			 * Apart from that there seems no need for zend_ini_deactivate() yet.
			 * So we error out.
			 */

			/* Init procedure cache */
			MAKE_STD_ZVAL(plphp_proc_array);
			array_init(plphp_proc_array);

			zend_register_functions(
#if PHP_MAJOR_VERSION == 5
									NULL,
#endif
									spi_functions, NULL,
									MODULE_PERSISTENT TSRMLS_CC);

			PG(during_request_startup) = true;

			/* Set some defaults */
			SG(options) |= SAPI_OPTION_NO_CHDIR;

			/* Hard coded defaults which cannot be overwritten in the ini file */
			INI_HARDCODED("register_argc_argv", "0");
			INI_HARDCODED("html_errors", "0");
			INI_HARDCODED("implicit_flush", "1");
			INI_HARDCODED("max_execution_time", "0");
			INI_HARDCODED("max_input_time", "-1");

			/*
			 * Set memory limit to ridiculously high value.  This helps the
			 * server not to crash, because the PHP allocator has the really
			 * stupid idea of calling exit() if the limit is exceeded.
			 */
			{
				char	limit[15];

				snprintf(limit, sizeof(limit), "%d", 1 << 30);
				INI_HARDCODED("memory_limit", limit);
			}

			/* tell the engine we're in non-html mode */
			zend_uv.html_errors = false;

			/* not initialized but needed for several options */
			CG(in_compilation) = false;

			EG(uninitialized_zval_ptr) = NULL;

			if (php_request_startup(TSRMLS_C) == FAILURE)
			{
				SG(headers_sent) = 1;
				SG(request_info).no_headers = 1;
				/* Use Postgres log */
				elog(ERROR, "php_request_startup call failed");
			}

			CG(interactive) = false;
			PG(during_request_startup) = true;

			/* Register the resource for SPI_result */
			SPIres_rtype = zend_register_list_destructors_ex(php_SPIresult_destroy,
															 NULL,
															 "SPI result",
															 0);

			/* Ok, we're done */
			plphp_first_call = false;
		}
		zend_catch
		{
			plphp_first_call = true;
			if (error_msg)
			{
				char	str[1024];

				strncpy(str, error_msg, sizeof(str));
				pfree(error_msg);
				error_msg = NULL;
				elog(ERROR, "fatal error during PL/php initialization: %s",
					 str);
			}
			else
				elog(ERROR, "fatal error during PL/php initialization");
		}
		zend_end_try();
	}
	PG_CATCH();
	{
		PG_RE_THROW();
	}
	PG_END_TRY();
}

/*
 * plphp_call_handler
 *
 * The visible function of the PL interpreter.  The PostgreSQL function manager
 * and trigger manager call this function for execution of php procedures.
 */
Datum
plphp_call_handler(PG_FUNCTION_ARGS)
{
	Datum		retval;
	TSRMLS_FETCH();

	/* Initialize interpreter */
	plphp_init_all();

	PG_TRY();
	{
		/* Connect to SPI manager */
		if (SPI_connect() != SPI_OK_CONNECT)
			ereport(ERROR,
					(errcode(ERRCODE_CONNECTION_FAILURE),
					 errmsg("could not connect to SPI manager")));

		zend_try
		{
			plphp_proc_desc *desc;

			/* Clean up SRF state */
			current_fcinfo = NULL;

			/* Redirect to the appropiate handler */
			if (CALLED_AS_TRIGGER(fcinfo))
			{
				desc = plphp_compile_function(fcinfo->flinfo->fn_oid, true TSRMLS_CC);

				/* Activate PHP safe mode if needed */
				//PG(safe_mode) = desc->trusted;

				retval = plphp_trigger_handler(fcinfo, desc TSRMLS_CC);
			}
			else
			{
				desc = plphp_compile_function(fcinfo->flinfo->fn_oid, false TSRMLS_CC);

				/* Activate PHP safe mode if needed */
				//PG(safe_mode) = desc->trusted;

				if (desc->retset)
					retval = plphp_srf_handler(fcinfo, desc TSRMLS_CC);
				else
					retval = plphp_func_handler(fcinfo, desc TSRMLS_CC);
			}
		}
		zend_catch
		{
			REPORT_PHP_MEMUSAGE("reporting error");
			if (error_msg)
			{
				char	str[1024];

				strncpy(str, error_msg, sizeof(str));
				pfree(error_msg);
				error_msg = NULL;
				elog(ERROR, "%s", str);
			}
			else
				elog(ERROR, "fatal error");

			/* not reached, but keep compiler quiet */
			return 0;
		}
		zend_end_try();
	}
	PG_CATCH();
	{
		PG_RE_THROW();
	}
	PG_END_TRY();

	return retval;
}

/*
 * plphp_validator
 *
 * 		Validator function for checking the function's syntax at creation
 * 		time
 */
Datum
plphp_validator(PG_FUNCTION_ARGS)
{
	Oid				funcoid = PG_GETARG_OID(0);
	Form_pg_proc	procForm;
	HeapTuple		procTup;
	char			tmpname[32];
	char			funcname[NAMEDATALEN];
	char		   *tmpsrc = NULL,
				   *prosrc;
	Datum			prosrcdatum;

	TSRMLS_FETCH();
	/* Initialize interpreter */
	plphp_init_all();

	PG_TRY();
	{
		bool			isnull;
		/* Grab the pg_proc tuple */
		procTup = SearchSysCache(PROCOID,
								 ObjectIdGetDatum(funcoid),
								 0, 0, 0);
		if (!HeapTupleIsValid(procTup))
			elog(ERROR, "cache lookup failed for function %u", funcoid);

		procForm = (Form_pg_proc) GETSTRUCT(procTup);

		/* Get the function source code */
		prosrcdatum = SysCacheGetAttr(PROCOID,
									  procTup,
									  Anum_pg_proc_prosrc,
									  &isnull);
		if (isnull)
			elog(ERROR, "cache lookup yielded NULL prosrc");
		prosrc = DatumGetCString(DirectFunctionCall1(textout,
													 prosrcdatum));

		/* Get the function name, for the error message */
		StrNCpy(funcname, NameStr(procForm->proname), NAMEDATALEN);

		/* Let go of the pg_proc tuple */
		ReleaseSysCache(procTup);

		/* Create a PHP function creation statement */
		snprintf(tmpname, sizeof(tmpname), "plphp_temp_%u", funcoid);
		tmpsrc = (char *) palloc(strlen(prosrc) +
								 strlen(tmpname) +
								 strlen("function  ($args, $argc){ } "));
		sprintf(tmpsrc, "function %s($args, $argc){%s}",
				tmpname, prosrc);

		pfree(prosrc);

		zend_try
		{
			/*
			 * Delete the function from the PHP function table, just in case it
			 * already existed.  This is quite unlikely, but still.
			 */
			zend_hash_del(CG(function_table), tmpname, strlen(tmpname) + 1);

			/*
			 * Let the user see the fireworks.  If the function doesn't validate,
			 * the ERROR will be raised and the function will not be created.
			 */
			if (zend_eval_string(tmpsrc, NULL,
								 "plphp function temp source" TSRMLS_CC) == FAILURE)
				elog(ERROR, "function \"%s\" does not validate", funcname);

			pfree(tmpsrc);
			tmpsrc = NULL;

			/* Delete the newly-created function from the PHP function table. */
			zend_hash_del(CG(function_table), tmpname, strlen(tmpname) + 1);
		}
		zend_catch
		{
			if (tmpsrc != NULL)
				pfree(tmpsrc);

			if (error_msg)
			{
				char	str[1024];

				StrNCpy(str, error_msg, sizeof(str));
				pfree(error_msg);
				error_msg = NULL;
				elog(ERROR, "function \"%s\" does not validate: %s", funcname, str);
			}
			else
				elog(ERROR, "fatal error");

			/* not reached, but keep compiler quiet */
			return 0;
		}
		zend_end_try();

		/* The result of a validator is ignored */
		PG_RETURN_VOID();
	}
	PG_CATCH();
	{
		PG_RE_THROW();
	}
	PG_END_TRY();
}

/*
 * plphp_get_function_tupdesc
 *
 * 		Returns a TupleDesc of the function's return type.
 */
static TupleDesc
plphp_get_function_tupdesc(Oid result_type, Node *rsinfo)
{
	if (result_type == RECORDOID)
	{
		ReturnSetInfo *rs = (ReturnSetInfo *) rsinfo;
		/* We must get the information from call context */
		if (!rsinfo || !IsA(rsinfo, ReturnSetInfo) || rs->expectedDesc == NULL)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("function returning record called in context "
							"that cannot accept type record")));
		return rs->expectedDesc;
	}
	else
		/* ordinary composite type */
		return lookup_rowtype_tupdesc(result_type, -1);
}

/*
 * Build the $_TD array for the trigger function.
 */
static zval *
plphp_trig_build_args(FunctionCallInfo fcinfo)
{
	TriggerData	   *tdata;
	TupleDesc		tupdesc;
	zval		   *retval;
	int				i;

	MAKE_STD_ZVAL(retval);
	array_init(retval);

	tdata = (TriggerData *) fcinfo->context;
	tupdesc = tdata->tg_relation->rd_att;

	/* The basic variables */
	add_assoc_string(retval, "name", tdata->tg_trigger->tgname, 1);
    add_assoc_long(retval, "relid", tdata->tg_relation->rd_id);
	add_assoc_string(retval, "relname", SPI_getrelname(tdata->tg_relation), 1);
	add_assoc_string(retval, "schemaname", SPI_getnspname(tdata->tg_relation), 1);

	/* EVENT */
	if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event))
		add_assoc_string(retval, "event", "INSERT", 1);
	else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event))
		add_assoc_string(retval, "event", "DELETE", 1);
	else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))
		add_assoc_string(retval, "event", "UPDATE", 1);
	else
		elog(ERROR, "unknown firing event for trigger function");

	/* NEW and OLD as appropiate */
	if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
	{
		if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event))
		{
			zval	   *hashref;

			hashref = plphp_build_tuple_argument(tdata->tg_trigtuple, tupdesc);
			add_assoc_zval(retval, "new", hashref);
		}
		else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event))
		{
			zval	   *hashref;

			hashref = plphp_build_tuple_argument(tdata->tg_trigtuple, tupdesc);
			add_assoc_zval(retval, "old", hashref);
		}
		else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))
		{
			zval	   *hashref;

			hashref = plphp_build_tuple_argument(tdata->tg_newtuple, tupdesc);
			add_assoc_zval(retval, "new", hashref);

			hashref = plphp_build_tuple_argument(tdata->tg_trigtuple, tupdesc);
			add_assoc_zval(retval, "old", hashref);
		}
		else
			elog(ERROR, "unknown firing event for trigger function");
	}

	/* ARGC and ARGS */
	add_assoc_long(retval, "argc", tdata->tg_trigger->tgnargs);

	if (tdata->tg_trigger->tgnargs > 0)
	{
		zval	   *hashref;

		MAKE_STD_ZVAL(hashref);
		array_init(hashref);

		for (i = 0; i < tdata->tg_trigger->tgnargs; i++)
			add_index_string(hashref, i, tdata->tg_trigger->tgargs[i], 1);

		zend_hash_update(retval->value.ht, "args", strlen("args") + 1,
						 (void *) &hashref, sizeof(zval *), NULL);
	}

	/* WHEN */
	if (TRIGGER_FIRED_BEFORE(tdata->tg_event))
		add_assoc_string(retval, "when", "BEFORE", 1);
	else if (TRIGGER_FIRED_AFTER(tdata->tg_event))
		add_assoc_string(retval, "when", "AFTER", 1);
	else
		elog(ERROR, "unknown firing time for trigger function");

	/* LEVEL */
	if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
		add_assoc_string(retval, "level", "ROW", 1);
	else if (TRIGGER_FIRED_FOR_STATEMENT(tdata->tg_event))
		add_assoc_string(retval, "level", "STATEMENT", 1);
	else
		elog(ERROR, "unknown firing level for trigger function");

	return retval;
}

/*
 * plphp_trigger_handler
 * 		Handler for trigger function calls
 */
static Datum
plphp_trigger_handler(FunctionCallInfo fcinfo, plphp_proc_desc *desc TSRMLS_DC)
{
	Datum		retval = 0;
	char	   *srv;
	zval	   *phpret,
			   *zTrigData;
	TriggerData *trigdata;

	REPORT_PHP_MEMUSAGE("going to build the trigger arg");

	zTrigData = plphp_trig_build_args(fcinfo);

	REPORT_PHP_MEMUSAGE("going to call the trigger function");

	phpret = plphp_call_php_trig(desc, fcinfo, zTrigData TSRMLS_CC);
	if (!phpret)
		elog(ERROR, "error during execution of function %s", desc->proname);

	REPORT_PHP_MEMUSAGE("trigger called, going to build the return value");

	/*
	 * Disconnect from SPI manager and then create the return values datum (if
	 * the input function does a palloc for it this must not be allocated in
	 * the SPI memory context because SPI_finish would free it).
	 */
	if (SPI_finish() != SPI_OK_FINISH)
		ereport(ERROR,
				(errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
				 errmsg("could not disconnect from SPI manager")));

	trigdata = (TriggerData *) fcinfo->context;

	if (zTrigData->type != IS_ARRAY)
		elog(ERROR, "$_TD is not an array");

	/*
	 * In a BEFORE trigger, compute the return value.  In an AFTER trigger
	 * it'll be ignored, so don't bother.
	 */
	if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
	{
		switch (phpret->type)
		{
			case IS_STRING:
				srv = phpret->value.str.val;
				if (strcasecmp(srv, "SKIP") == 0)
				{
					/* do nothing */
					break;
				}
				else if (strcasecmp(srv, "MODIFY") == 0)
				{
					if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event) ||
						TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
						retval = PointerGetDatum(plphp_modify_tuple(zTrigData,
																	trigdata));
					else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
						ereport(ERROR,
								(errcode(ERRCODE_SYNTAX_ERROR),
								 errmsg("on delete trigger can not modify the the return tuple")));
					else
						elog(ERROR, "unknown event in trigger function");
				}
				else
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("expected trigger function to return NULL, 'SKIP' or 'MODIFY'")));
				break;
			case IS_NULL:
				if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event) ||
					TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
					retval = (Datum) trigdata->tg_trigtuple;
				else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
					retval = (Datum) trigdata->tg_newtuple;
				break;
			default:
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("expected trigger function to return NULL, 'SKIP' or 'MODIFY'")));
				break;
		}
	}

	REPORT_PHP_MEMUSAGE("freeing some variables");

	zval_dtor(zTrigData);
	zval_dtor(phpret);

	FREE_ZVAL(phpret);
	FREE_ZVAL(zTrigData);

	REPORT_PHP_MEMUSAGE("trigger call done");

	return retval;
}

/*
 * plphp_func_handler
 * 		Handler for regular function calls
 */
static Datum
plphp_func_handler(FunctionCallInfo fcinfo, plphp_proc_desc *desc TSRMLS_DC)
{
	zval	   *phpret = NULL;
	Datum		retval;
	char	   *retvalbuffer = NULL;

	/* SRFs are handled separately */
	Assert(!desc->retset);

	/* Call the PHP function.  */
	phpret = plphp_call_php_func(desc, fcinfo TSRMLS_CC);
	if (!phpret)
		elog(ERROR, "error during execution of function %s", desc->proname);

	REPORT_PHP_MEMUSAGE("function invoked");

	/* Basic datatype checks */
	if ((desc->ret_type & PL_ARRAY) && phpret->type != IS_ARRAY)
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("function declared to return array must return an array")));
	if ((desc->ret_type & PL_TUPLE) && phpret->type != IS_ARRAY)
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("function declared to return tuple must return an array")));

	/*
	 * Disconnect from SPI manager and then create the return values datum (if
	 * the input function does a palloc for it this must not be allocated in
	 * the SPI memory context because SPI_finish would free it).
	 */
	if (SPI_finish() != SPI_OK_FINISH)
		ereport(ERROR,
				(errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
				 errmsg("could not disconnect from SPI manager")));
	retval = (Datum) 0;

	if (desc->ret_type & PL_PSEUDO)
	{
		HeapTuple	retTypeTup;
		Form_pg_type retTypeStruct;

		retTypeTup = SearchSysCache(TYPEOID,
									ObjectIdGetDatum(get_fn_expr_rettype(fcinfo->flinfo)),
									0, 0, 0);
		retTypeStruct = (Form_pg_type) GETSTRUCT(retTypeTup);
		perm_fmgr_info(retTypeStruct->typinput, &(desc->result_in_func));
		desc->result_typioparam = retTypeStruct->typelem;
		ReleaseSysCache(retTypeTup);
	}

	if (phpret)
	{
		switch (Z_TYPE_P(phpret))
		{
			case IS_NULL:
				fcinfo->isnull = true;
				break;
			case IS_BOOL:
			case IS_DOUBLE:
			case IS_LONG:
			case IS_STRING:
				retvalbuffer = plphp_zval_get_cstring(phpret, false, false);
				retval = CStringGetDatum(retvalbuffer);
				break;
			case IS_ARRAY:
				if (desc->ret_type & PL_ARRAY)
				{
					retvalbuffer = plphp_convert_to_pg_array(phpret);
					retval = CStringGetDatum(retvalbuffer);
				}
				else if (desc->ret_type & PL_TUPLE)
				{
					TupleDesc	td;
					HeapTuple	tup;

					if (desc->ret_type & PL_PSEUDO)
						td = plphp_get_function_tupdesc(desc->ret_oid,
														fcinfo->resultinfo);
					else
						td = lookup_rowtype_tupdesc(desc->ret_oid, (int32) -1);

					if (!td)
						elog(ERROR, "no TupleDesc info available");

					tup = plphp_htup_from_zval(phpret, td);
					retval = HeapTupleGetDatum(tup);
					ReleaseTupleDesc(td);
				}
				else
					/* FIXME -- should return the thing as a string? */
					elog(ERROR, "this plphp function cannot return arrays");
				break;
			default:
				elog(WARNING,
					 "plphp functions cannot return type %i",
					 phpret->type);
				fcinfo->isnull = true;
				break;
		}
	}
	else
	{
		fcinfo->isnull = true;
		retval = (Datum) 0;
	}

	if (!fcinfo->isnull && !(desc->ret_type & PL_TUPLE))
	{
		retval = FunctionCall3(&desc->result_in_func,
							   PointerGetDatum(retvalbuffer),
							   ObjectIdGetDatum(desc->result_typioparam),
							   Int32GetDatum(-1));
		pfree(retvalbuffer);
	}

	REPORT_PHP_MEMUSAGE("finished calling user function");

	return retval;
}

/*
 * plphp_srf_handler
 * 		Invoke a SRF
 */
static Datum
plphp_srf_handler(FunctionCallInfo fcinfo, plphp_proc_desc *desc TSRMLS_DC)
{
	ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
	TupleDesc	tupdesc;
	zval	   *phpret;
	MemoryContext	oldcxt;

	Assert(desc->retset);

	current_fcinfo = fcinfo;
	current_tuplestore = NULL;

	/* Check context before allowing the call to go through */
	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
		(rsi->allowedModes & SFRM_Materialize) == 0 ||
		rsi->expectedDesc == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("set-valued function called in context that "
						"cannot accept a set")));

	/*
	 * Fetch the function's tuple descriptor.  This will return NULL in the
	 * case of a scalar return type, in which case we will copy the TupleDesc
	 * from the ReturnSetInfo.
	 */
	get_call_result_type(fcinfo, NULL, &tupdesc);
	if (tupdesc == NULL)
		tupdesc = rsi->expectedDesc;

	/*
	 * If the expectedDesc is NULL, bail out, because most likely it's using
	 * IN/OUT parameters.
	 */
	if (tupdesc == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot use IN/OUT parameters in PL/php")));

	oldcxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);

	/* This context is reset once per row in return_next */
	current_memcxt = AllocSetContextCreate(CurTransactionContext,
										   "PL/php SRF context",
										   ALLOCSET_DEFAULT_MINSIZE,
										   ALLOCSET_DEFAULT_INITSIZE,
										   ALLOCSET_DEFAULT_MAXSIZE);

	/* Tuple descriptor and AttInMetadata for return_next */
	current_tupledesc = CreateTupleDescCopy(tupdesc);
	current_attinmeta = TupleDescGetAttInMetadata(current_tupledesc);

	/*
	 * Call the PHP function.  The user code must call return_next, which will
	 * create and populate the tuplestore appropiately.
	 */
	phpret = plphp_call_php_func(desc, fcinfo TSRMLS_CC);

	/* We don't use the return value */
	zval_dtor(phpret);

	/* Close the SPI connection */
	if (SPI_finish() != SPI_OK_FINISH)
		ereport(ERROR,
				(errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
				 errmsg("could not disconnect from SPI manager")));

	/* Now prepare the return values. */
	rsi->returnMode = SFRM_Materialize;

	if (current_tuplestore)
	{
		rsi->setResult = current_tuplestore;
		rsi->setDesc = current_tupledesc;
	}

	MemoryContextDelete(current_memcxt);
	current_memcxt = NULL;
	current_tupledesc = NULL;
	current_attinmeta = NULL;

	MemoryContextSwitchTo(oldcxt);

	/* All done */
	return (Datum) 0;
}

/*
 * plphp_compile_function
 *
 * 		Compile (or hopefully just look up) function
 */
static plphp_proc_desc *
plphp_compile_function(Oid fnoid, bool is_trigger TSRMLS_DC)
{
	HeapTuple	procTup;
	Form_pg_proc procStruct;
	char		internal_proname[64];
	plphp_proc_desc *prodesc = NULL;
	int			i;
	char	   *pointer = NULL;

	/*
	 * We'll need the pg_proc tuple in any case...
	 */
	procTup = SearchSysCache(PROCOID, ObjectIdGetDatum(fnoid), 0, 0, 0);
	if (!HeapTupleIsValid(procTup))
		elog(ERROR, "cache lookup failed for function %u", fnoid);
	procStruct = (Form_pg_proc) GETSTRUCT(procTup);

	/*
	 * Build our internal procedure name from the function's Oid
	 */
	if (is_trigger)
		snprintf(internal_proname, sizeof(internal_proname),
				 "plphp_proc_%u_trigger", fnoid);
	else
		snprintf(internal_proname, sizeof(internal_proname),
				 "plphp_proc_%u", fnoid);

	/*
	 * Look up the internal proc name in the hashtable
	 */
	pointer = plphp_zval_get_cstring(plphp_array_get_elem(plphp_proc_array,
														  internal_proname),
									 false, true);
	if (pointer)
	{
		bool uptodate;
		sscanf(pointer, "%p", &prodesc);

#ifdef PG_VERSION_83_COMPAT
		/* PostgreSQL 8.3 doesn't allow calling GetCmin if a tuple doesn't
		 * originate from the current transaction.
		 */
		uptodate =
			(prodesc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
			 prodesc->fn_cmin == HeapTupleHeaderGetRawCommandId(procTup->t_data));

#else
		uptodate =
			(prodesc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
			 prodesc->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data));

#endif

		/* We need to delete the old entry */
		if (!uptodate)
		{
			/*
			 * FIXME -- use a per-function memory context and fix this
			 * stuff for good
			 */
			free(prodesc->proname);
			free(prodesc);
			prodesc = NULL;
		}
	}

	if (prodesc == NULL)
	{
		HeapTuple	langTup;
		Form_pg_language langStruct;
		Datum		prosrcdatum;
		bool		isnull;
		char	   *proc_source;
		char	   *complete_proc_source;
		char	   *pointer = NULL;
		char	   *aliases = NULL;
		char	   *out_aliases = NULL;
		char	   *out_return_str = NULL;
		int16	typlen;
		char	typbyval,
				typalign,
				typtype,
				typdelim;
		Oid		typioparam,
				typinput,
				typoutput;
		/*
		 * Allocate a new procedure description block
		 */
		prodesc = (plphp_proc_desc *) malloc(sizeof(plphp_proc_desc));
		if (!prodesc)
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of memory")));

		MemSet(prodesc, 0, sizeof(plphp_proc_desc));
		prodesc->proname = strdup(internal_proname);
		if (!prodesc->proname)
		{
			free(prodesc);
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of memory")));
		}

		prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);

#ifdef PG_VERSION_83_COMPAT
		/* PostgreSQL 8.3 doesn't allow calling GetCmin if a tuple doesn't
		 * originate from the current transaction.
		 */
		prodesc->fn_cmin = HeapTupleHeaderGetRawCommandId(procTup->t_data);

#else
		prodesc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);

#endif

		/*
		 * Look up the pg_language tuple by Oid
		 */
		langTup = SearchSysCache(LANGOID,
								 ObjectIdGetDatum(procStruct->prolang),
								 0, 0, 0);
		if (!HeapTupleIsValid(langTup))
		{
			free(prodesc->proname);
			free(prodesc);
			elog(ERROR, "cache lookup failed for language %u",
					   procStruct->prolang);
		}
		langStruct = (Form_pg_language) GETSTRUCT(langTup);
		prodesc->trusted = langStruct->lanpltrusted;
		ReleaseSysCache(langTup);

		/*
		 * Get the required information for input conversion of the return
		 * value, and output conversion of the procedure's arguments.
		 */
		if (!is_trigger)
		{
			char  **argnames;
			char   *argmodes;
			Oid    *argtypes;
			int32	alias_str_end,
					out_str_end;

			typtype = get_typtype(procStruct->prorettype);
			get_type_io_data(procStruct->prorettype,
							 IOFunc_input,
							 &typlen,
							 &typbyval,
							 &typalign,
							 &typdelim,
							 &typioparam,
							 &typinput);

			/*
			 * Disallow pseudotype result, except:
			 * VOID, RECORD, ANYELEMENT or ANYARRAY
			 */
			if (typtype == TYPTYPE_PSEUDO)
			{
				if ((procStruct->prorettype == VOIDOID) ||
					(procStruct->prorettype == RECORDOID) ||
					(procStruct->prorettype == ANYELEMENTOID) ||
					(procStruct->prorettype == ANYARRAYOID))
				{
					/* okay */
					prodesc->ret_type |= PL_PSEUDO;
				}
				else if (procStruct->prorettype == TRIGGEROID)
				{
					free(prodesc->proname);
					free(prodesc);
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("trigger functions may only be called "
									"as triggers")));
				}
				else
				{
					free(prodesc->proname);
					free(prodesc);
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("plphp functions cannot return type %s",
									format_type_be(procStruct->prorettype))));
				}
			}

			prodesc->ret_oid = procStruct->prorettype;
			prodesc->retset = procStruct->proretset;

			if (typtype == TYPTYPE_COMPOSITE ||
				procStruct->prorettype == RECORDOID)
			{
				prodesc->ret_type |= PL_TUPLE;
			}

			if (procStruct->prorettype == ANYARRAYOID)
				prodesc->ret_type |= PL_ARRAY;
			else
			{
				/* function returns a normal (declared) array */
				if (typlen == -1 && get_element_type(procStruct->prorettype))
					prodesc->ret_type |= PL_ARRAY;
			}

			perm_fmgr_info(typinput, &(prodesc->result_in_func));
			prodesc->result_typioparam = typioparam;

			/* Deal with named arguments, OUT, IN/OUT and TABLE arguments */

			prodesc->n_total_args = get_func_arg_info(procTup, &argtypes,
											  		  &argnames, &argmodes);
			prodesc->n_out_args = 0;
			prodesc->n_mixed_args = 0;

			prodesc->args_out_tupdesc = NULL;
			out_return_str = NULL;
			alias_str_end = out_str_end = 0;

			/* Count the number of OUT arguments. Need to do this out of the
			 * main loop, to correctly determine the object to return for OUT args
		     */
			if (argmodes)
				for (i = 0; i < prodesc->n_total_args; i++)
				{
					switch(argmodes[i])
					{
						case PROARGMODE_OUT:
							prodesc->n_out_args++;
							break;
						case PROARGMODE_INOUT:
							prodesc->n_mixed_args++;
							break;
						case PROARGMODE_IN:
							break;
						case PROARGMODE_TABLE:
							break;
						case PROARGMODE_VARIADIC:
							elog(ERROR, "VARIADIC arguments are not supported");
						default:
							elog(ERROR, "Unsupported type %c for argument no %d",
								 argmodes[i], i);
					}
					prodesc->arg_argmode[i] = argmodes[i];
				}
			else
				MemSet(prodesc->arg_argmode, PROARGMODE_IN,
				 	   prodesc->n_total_args);

			/* Allocate memory for argument names unless all of them are OUT*/
			if (argnames && prodesc->n_total_args > 0)
				aliases = palloc((NAMEDATALEN + 32) * prodesc->n_total_args);

			/* Main argument processing loop. */
			for (i = 0; i < prodesc->n_total_args; i++)
			{
				prodesc->arg_typtype[i] = get_typtype(argtypes[i]);
				if (prodesc->arg_typtype[i] != TYPTYPE_COMPOSITE)
				{
					get_type_io_data(argtypes[i],
									 IOFunc_output,
									 &typlen,
									 &typbyval,
									 &typalign,
									 &typdelim,
									 &typioparam,
									 &typoutput);
					perm_fmgr_info(typoutput, &(prodesc->arg_out_func[i]));
					prodesc->arg_typioparam[i] = typioparam;
				}
				if (aliases && argnames[i][0] != '\0')
				{
					if (!is_valid_php_identifier(argnames[i]))
						elog(ERROR, "\"%s\" can not be used as a PHP variable name",
							 argnames[i]);
					/* Deal with argument name */
					alias_str_end += snprintf(aliases + alias_str_end,
										 	  NAMEDATALEN + 32,
								   		 	  " $%s = &$args[%d];",
											  argnames[i], i);
				}
				if ((prodesc->arg_argmode[i] == PROARGMODE_OUT ||
					 prodesc->arg_argmode[i] == PROARGMODE_INOUT) && !prodesc->retset)
				{
					/* Initialiazation for OUT arguments aliases */
					if (!out_return_str)
					{
						/* Generate return statment for a single OUT argument */
						out_return_str = palloc(NAMEDATALEN + 32);
						if (prodesc->n_out_args + prodesc->n_mixed_args == 1)
							snprintf(out_return_str, NAMEDATALEN + 32,
									 "return $args[%d];", i);
						else
						{
							/* PL/PHP deals with multiple OUT arguments by
							 * internally creating an array of references to them.
							 * E.g. out_fn(a out integer, b out integer )
							 * translates into:
							 * $_plphp_ret_out_fn_1234=array(a => $&a,b => $&b);
							 */
							char plphp_ret_array_name[NAMEDATALEN + 16];

							int array_namelen = snprintf(plphp_ret_array_name,
							 						 	 NAMEDATALEN + 16,
									 				 	 "_plphp_ret_%s",
													 	 internal_proname);

							snprintf(out_return_str, array_namelen + 16,
									"return $%s;", plphp_ret_array_name);

							/* 2 NAMEDATALEN for argument names, additional
							 * 16 bytes per each argument for assignment string,
							 * additional 16 bytes for the 'array' prefix string.
							 */
							out_aliases = palloc(array_namelen +
												 (prodesc->n_out_args +
												  prodesc->n_mixed_args) *
												 (2*NAMEDATALEN + 16) + 16);

							out_str_end = snprintf(out_aliases,
							 					   array_namelen +
												   (2 * NAMEDATALEN + 16) + 16,
												   "$%s = array(&$args[%d]",
												   plphp_ret_array_name, i);

						}
					}
					else if (out_aliases)
					{
					   /* Add new elements to the array of aliases for OUT args */
						Assert(prodesc->n_out_args + prodesc->n_mixed_args > 1);
						out_str_end += snprintf(out_aliases+out_str_end,
												2 * NAMEDATALEN + 16,
												",&$args[%d]", i);
					}
				}
			}
			if (aliases)
				strcat(aliases, " ");
			if (out_aliases)
				strcat(out_aliases, ")");
		}

		/*
		 * Create the text of the PHP function.  We do not use the same
		 * function name, because that would prevent function overloading.
		 * Sadly this also prevents PL/php functions from calling each other
		 * easily.
		 */
		prosrcdatum = SysCacheGetAttr(PROCOID, procTup,
									  Anum_pg_proc_prosrc, &isnull);
		if (isnull)
			elog(ERROR, "cache lookup yielded NULL prosrc");

		proc_source = DatumGetCString(DirectFunctionCall1(textout,
														  prosrcdatum));

		/* Create the procedure in the interpreter */
		complete_proc_source =
			(char *) palloc(strlen(proc_source) +
							strlen(internal_proname) +
							(aliases ? strlen(aliases) : 0) +
							(out_aliases ? strlen(out_aliases) : 0) +
							strlen("function  ($args, $argc){ } ") + 32 +
							(out_return_str ? strlen(out_return_str) : 0));

		/* XXX Is this usage of sprintf safe? */
		if (is_trigger)
			sprintf(complete_proc_source, "function %s($_TD){%s}",
					internal_proname, proc_source);
		else
			sprintf(complete_proc_source,
					"function %s($args, $argc){%s %s;%s; %s}",
					internal_proname,
					aliases ? aliases : "",
					out_aliases ? out_aliases : "",
					proc_source,
					out_return_str? out_return_str : "");

		elog(LOG, "complete_proc_source = %s",
				 	 complete_proc_source);

		zend_hash_del(CG(function_table), prodesc->proname,
					  strlen(prodesc->proname) + 1);

		pointer = (char *) palloc(64);
		sprintf(pointer, "%p", (void *) prodesc);
		add_assoc_string(plphp_proc_array, internal_proname,
						 (char *) pointer, 1);

		if (zend_eval_string(complete_proc_source, NULL,
							 "plphp function source" TSRMLS_CC) == FAILURE)
		{
			/* the next compilation will blow it up */
			prodesc->fn_xmin = InvalidTransactionId;
			elog(ERROR, "unable to compile function \"%s\"",
					   prodesc->proname);
		}

		if (aliases)
			pfree(aliases);
		if (out_aliases)
			pfree(out_aliases);
		if (out_return_str)
			pfree(out_return_str);
		pfree(complete_proc_source);
	}

	ReleaseSysCache(procTup);

	return prodesc;
}

/*
 * plphp_func_build_args
 * 		Build a PHP array representing the arguments to the function
 */
static zval *
plphp_func_build_args(plphp_proc_desc *desc, FunctionCallInfo fcinfo TSRMLS_DC)
{
	zval	   *retval;
	int			i,j;

	MAKE_STD_ZVAL(retval);
	array_init(retval);

	/*
	 * The first var iterates over every argument, the second one - over the
	 * IN or INOUT ones only
	 */
	for (i = 0, j = 0; i < desc->n_total_args;
		 (j = IS_ARGMODE_OUT(desc->arg_argmode[i]) ? j : j + 1), i++)
	{
		/* Assing NULLs to OUT or TABLE arguments initially */
		if (IS_ARGMODE_OUT(desc->arg_argmode[i]))
		{
			add_next_index_unset(retval);
			continue;
		}

		if (desc->arg_typtype[i] == TYPTYPE_PSEUDO)
		{
			HeapTuple	typeTup;
			Form_pg_type typeStruct;

			typeTup = SearchSysCache(TYPEOID,
									 ObjectIdGetDatum(get_fn_expr_argtype
													  (fcinfo->flinfo, j)),
									 0, 0, 0);
			typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
			perm_fmgr_info(typeStruct->typoutput,
						   &(desc->arg_out_func[i]));
			desc->arg_typioparam[i] = typeStruct->typelem;
			ReleaseSysCache(typeTup);
		}

		if (desc->arg_typtype[i] == TYPTYPE_COMPOSITE)
		{
			if (fcinfo->argnull[j])
				add_next_index_unset(retval);
			else
			{
				HeapTupleHeader	td;
				Oid				tupType;
				int32			tupTypmod;
				TupleDesc		tupdesc;
				HeapTupleData	tmptup;
				zval		   *hashref;

				td = DatumGetHeapTupleHeader(fcinfo->arg[j]);

				/* Build a temporary HeapTuple control structure */
				tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
				tmptup.t_data = DatumGetHeapTupleHeader(fcinfo->arg[j]);

				/* Extract rowtype info and find a tupdesc */
				tupType = HeapTupleHeaderGetTypeId(tmptup.t_data);
				tupTypmod = HeapTupleHeaderGetTypMod(tmptup.t_data);
				tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);

				/* Build the PHP hash */
				hashref = plphp_build_tuple_argument(&tmptup, tupdesc);
				zend_hash_next_index_insert(retval->value.ht,
											(void *) &hashref,
											sizeof(zval *), NULL);
				/* Finally release the acquired tupledesc */
				ReleaseTupleDesc(tupdesc);
			}
		}
		else
		{
			if (fcinfo->argnull[j])
				add_next_index_unset(retval);
			else
			{
				char	   *tmp;

				/*
				 * TODO room for improvement here: instead of going through the
				 * output function, figure out if we can just use the native
				 * representation to pass to PHP.
				 */

				tmp =
					DatumGetCString(FunctionCall3
									(&(desc->arg_out_func[i]),
									 fcinfo->arg[j],
									 ObjectIdGetDatum(desc->arg_typioparam[i]),
									 Int32GetDatum(-1)));
				/*
				 * FIXME -- this is bogus.  Not every value starting with { is
				 * an array.  Figure out a better method for detecting arrays.
				 */
				if (tmp[0] == '{')
				{
					zval	   *hashref;

					hashref = plphp_convert_from_pg_array(tmp TSRMLS_CC);
					zend_hash_next_index_insert(retval->value.ht,
												(void *) &hashref,
												sizeof(zval *), NULL);
				}
				else
					add_next_index_string(retval, tmp, 1);

				/*
				 * FIXME - figure out which parameters are passed by
				 * reference and need freeing
				 */
				/* pfree(tmp); */
			}
		}
	}

	return retval;
}

/*
 * plphp_call_php_func
 * 		Build the function argument array and call the PHP function.
 *
 * We use a private PHP symbol table, so that we can easily destroy everything
 * used during the execution of the function.  We use it to collect the
 * arguments' zvals as well.  We exclude the return value, because it will be
 * used by the caller -- it must be freed there!
 */
static zval *
plphp_call_php_func(plphp_proc_desc *desc, FunctionCallInfo fcinfo TSRMLS_DC)
{
	zval	   *retval;
	zval	   *args;
	zval	   *argc;
	zval	   *funcname;
	zval	  **params[2];
	char		call[64];
	HashTable  *orig_symbol_table;
	HashTable  *symbol_table;

	REPORT_PHP_MEMUSAGE("going to build function args");

	ALLOC_HASHTABLE(symbol_table);
	zend_hash_init(symbol_table, 0, NULL, ZVAL_PTR_DTOR, 0);

	/*
	 * Build the function arguments.  Save a pointer to each new zval in our
	 * private symbol table, so that we can clean up easily later.
	 */
	args = plphp_func_build_args(desc, fcinfo TSRMLS_CC);
	zend_hash_update(symbol_table, "args", strlen("args") + 1,
					 (void *) &args, sizeof(zval *), NULL);

	REPORT_PHP_MEMUSAGE("args built. Now the rest ...");

	MAKE_STD_ZVAL(argc);
	ZVAL_LONG(argc, desc->n_total_args);
	zend_hash_update(symbol_table, "argc", strlen("argc") + 1,
					 (void *) &argc, sizeof(zval *), NULL);

	params[0] = &args;
	params[1] = &argc;

	/* Build the internal function name, and save for later cleaning */
	sprintf(call, "plphp_proc_%u", fcinfo->flinfo->fn_oid);
	MAKE_STD_ZVAL(funcname);
	ZVAL_STRING(funcname, call, 1);
	zend_hash_update(symbol_table, "funcname", strlen("funcname") + 1,
					 (void *) &funcname, sizeof(zval *), NULL);

	REPORT_PHP_MEMUSAGE("going to call the function");

	orig_symbol_table = EG(active_symbol_table);
	EG(active_symbol_table) = symbol_table;

	saved_symbol_table = EG(active_symbol_table);

	/* XXX: why no_separation param is 1 is this call ? */
	if (call_user_function_ex(CG(function_table), NULL, funcname, &retval,
							  2, params, 1, symbol_table TSRMLS_CC) == FAILURE)
		elog(ERROR, "could not call function \"%s\"", call);

	REPORT_PHP_MEMUSAGE("going to free some vars");

	saved_symbol_table = NULL;

	/* Return to the original symbol table, and clean our private one */
	EG(active_symbol_table) = orig_symbol_table;
	zend_hash_clean(symbol_table);

	REPORT_PHP_MEMUSAGE("function call done");

	return retval;
}

/*
 * plphp_call_php_trig
 * 		Build trigger argument array and call the PHP function as a
 * 		trigger.
 *
 * Note we don't need to change the symbol table here like we do in
 * plphp_call_php_func, because we do manual cleaning of each zval used.
 */
static zval *
plphp_call_php_trig(plphp_proc_desc *desc, FunctionCallInfo fcinfo,
					zval *trigdata TSRMLS_DC)
{
	zval	   *retval;
	zval	   *funcname;
	char		call[64];
	zval	  **params[1];

	params[0] = &trigdata;

	/* Build the internal function name, and save for later cleaning */
	sprintf(call, "plphp_proc_%u_trigger", fcinfo->flinfo->fn_oid);
	MAKE_STD_ZVAL(funcname);
	ZVAL_STRING(funcname, call, 0);

	/*
	 * HACK: mark trigdata as a reference, so it won't be copied in
	 * call_user_function_ex.  This way the user function will be able to
	 * modify it, in order to change NEW.
	 */
	Z_SET_ISREF_P(trigdata);

	if (call_user_function_ex(CG(function_table), NULL, funcname, &retval,
							  1, params, 1, NULL TSRMLS_CC) == FAILURE)
		elog(ERROR, "could not call function \"%s\"", call);

	FREE_ZVAL(funcname);

	/* Return to the original state */
	Z_UNSET_ISREF_P(trigdata);

	return retval;
}

/*
 * plphp_error_cb
 *
 * A callback for PHP error handling.  This is called when the php_error or
 * zend_error function is invoked in our code.  Ideally this function should
 * clean up the PHP state after an ERROR, but zend_try blocks do not seem
 * to work as I'd expect.  So for now, we degrade the error to WARNING and
 * continue executing in the hope that the system doesn't crash later.
 *
 * Note that we do clean up some PHP state by hand but it doesn't seem to
 * work as expected either.
 */
void
plphp_error_cb(int type, const char *filename, const uint lineno,
	   		   const char *fmt, va_list args)
{
	char	str[1024];
	int		elevel;

	vsnprintf(str, 1024, fmt, args);

	/*
	 * PHP error classification is a bitmask, so this conversion is a bit
	 * bogus.  However, most calls to php_error() use a single bit.
	 * Whenever more than one is used, we will default to ERROR, so this is
	 * safe, if a bit excessive.
	 *
	 * XXX -- I wonder whether we should promote the WARNINGs to errors as
	 * well.  PHP has a really stupid way of continuing execution in presence
	 * of severe problems that I don't see why we should maintain.
	 */
	switch (type)
	{
		case E_ERROR:
		case E_CORE_ERROR:
		case E_COMPILE_ERROR:
		case E_USER_ERROR:
		case E_PARSE:
			elevel = ERROR;
			break;
		case E_WARNING:
		case E_CORE_WARNING:
		case E_COMPILE_WARNING:
		case E_USER_WARNING:
		case E_STRICT:
			elevel = WARNING;
			break;
		case E_NOTICE:
		case E_USER_NOTICE:
			elevel = NOTICE;
			break;
		default:
			elevel = ERROR;
			break;
	}

	REPORT_PHP_MEMUSAGE("reporting error");

	/*
	 * If this is a severe problem, we need to make PHP aware of it, so first
	 * save the error message and then bail out of the PHP block.  With luck,
	 * this will be trapped by a zend_try/zend_catch block outwards in PL/php
	 * code, which would translate it to a Postgres elog(ERROR), leaving
	 * everything in a consistent state.
	 *
	 * For this to work, there must be a try/catch block covering every place
	 * where PHP may raise an error!
	 */
	if (elevel >= ERROR)
	{
		if (lineno != 0)
		{
			char	msgline[1024];
			snprintf(msgline, sizeof(msgline), "%s at line %d", str, lineno);
			error_msg = pstrdup(msgline);
		}
		else
			error_msg = pstrdup(str);

		zend_bailout();
	}

	ereport(elevel,
			(errmsg("plphp: %s", str)));
}

/* Check if the name can be a valid PHP variable name */
static bool
is_valid_php_identifier(char *name)
{
	int 	len,
			i;

	Assert(name);

	len = strlen(name);

	/* Should start from the letter */
	if (!isalpha(name[0]))
		return false;
	for (i = 1; i < len; i++)
	{
		/* Only letters, digits and underscores are allowed */
		if (!isalpha(name[i]) && !isdigit(name[i]) && name[i] != '_')
			return false;
	}
	return true;
}

/*
 * vim:ts=4:sw=4:cino=(0
 */

/**********************************************************************
 * plphp_io.c
 *
 * Support functions for PL/php -- mainly functions to convert stuff
 * from the PHP representation to PostgreSQL representation and vice
 * versa, either text or binary representations.
 *
 * $Id$
 *
 **********************************************************************/

#include "postgres.h"
#include "plphp_io.h"

#include "catalog/pg_type.h"
#include "executor/spi.h"
#include "funcapi.h"
#include "lib/stringinfo.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/memutils.h"
#include "access/htup_details.h"

/*
 * plphp_zval_from_tuple
 *		 Build a PHP hash from a tuple.
 */
zval *
plphp_zval_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
{
	int			i;
	char	   *attname = NULL;
	zval	   *array;

	MAKE_STD_ZVAL(array);
	array_init(array);

	for (i = 0; i < tupdesc->natts; i++)
	{
		char *attdata;

		/* Get the attribute name */
		attname = tupdesc->attrs[i]->attname.data;

		/* and get its value */
		if ((attdata = SPI_getvalue(tuple, tupdesc, i + 1)) != NULL)
		{
			/* "true" means strdup the string */
			add_assoc_string(array, attname, attdata, true);
			pfree(attdata);
		}
		else
			add_assoc_null(array, attname);
	}
	return array;
}

/*
 * plphp_htup_from_zval
 * 		Build a HeapTuple from a zval (which must be an array) and a TupleDesc.
 *
 * The return HeapTuple is allocated in the current memory context and must
 * be freed by the caller.
 *
 * If zval doesn't contain any of the element names from the TupleDesc,
 * build a tuple from the first N elements. This allows us to accept
 * arrays in form array(1,2,3) as the result of functions with OUT arguments.
 * XXX -- possible optimization: keep the memory context created and only
 * reset it between calls.
 */
HeapTuple
plphp_htup_from_zval(zval *val, TupleDesc tupdesc)
{
	MemoryContext	oldcxt;
	MemoryContext	tmpcxt;
	HeapTuple		ret;
	AttInMetadata  *attinmeta;
	HashPosition	pos;
	zval		  **element;
	char		  **values;
	int				i;
	bool			allempty = true;

	tmpcxt = AllocSetContextCreate(TopTransactionContext,
								   "htup_from_zval cxt",
								   ALLOCSET_DEFAULT_MINSIZE,
								   ALLOCSET_DEFAULT_INITSIZE,
								   ALLOCSET_DEFAULT_MAXSIZE);
	oldcxt = MemoryContextSwitchTo(tmpcxt);

	values = (char **) palloc(tupdesc->natts * sizeof(char *));

	for (i = 0; i < tupdesc->natts; i++)
	{
		char   *key = SPI_fname(tupdesc, i + 1);
		zval   *scalarval = plphp_array_get_elem(val, key);

		values[i] = plphp_zval_get_cstring(scalarval, true, true);
		/*
		 * Reset the flag is even one of the keys actually exists,
		 * even if it is NULL.
		 */
		if (scalarval != NULL)
			allempty = false;
	}
	/* None of the names from the tuple exists,
	 * try to get 1st N array elements and assign them to the tuple
	 */
	if (allempty)
		for (i = 0,
			 zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(val), &pos);
			 (zend_hash_get_current_data_ex(Z_ARRVAL_P(val),
										   (void **) &element,
											&pos) == SUCCESS) &&
			(i < tupdesc->natts);
			zend_hash_move_forward_ex(Z_ARRVAL_P(val), &pos), i++)
			values[i] = plphp_zval_get_cstring(element[0], true, true);

	attinmeta = TupleDescGetAttInMetadata(tupdesc);

	MemoryContextSwitchTo(oldcxt);
	ret = BuildTupleFromCStrings(attinmeta, values);

	MemoryContextDelete(tmpcxt);

	return ret;
}

/* plphp_srf_htup_from_zval
 * 		Build a tuple from a zval and a TupleDesc, for a SRF.
 *
 * Like above, but we don't use the names of the array attributes;
 * rather we build the tuple in order.  Also, we get a MemoryContext
 * from the caller and just clean it at return, rather than building it each
 * time.
 */
HeapTuple
plphp_srf_htup_from_zval(zval *val, AttInMetadata *attinmeta,
						 MemoryContext cxt)
{
	MemoryContext	oldcxt;
	HeapTuple		ret;
	HashPosition	pos;
	char		  **values;
	zval		  **element;
	int				i = 0;

	oldcxt = MemoryContextSwitchTo(cxt);

	/*
	 * Use palloc0 to initialize values to NULL, just in case the user does
	 * not pass all needed attributes
	 */
	values = (char **) palloc0(attinmeta->tupdesc->natts * sizeof(char *));

	/*
	 * If the input zval is an array, build a tuple using each element as an
	 * attribute.  Exception: if the return tuple has a single element and
	 * it's an array type, use the whole array as a single value.
	 *
	 * If the input zval is a scalar, use it as an element directly.
	 */
	if (Z_TYPE_P(val) == IS_ARRAY)
	{
		if (attinmeta->tupdesc->natts == 1)
		{
			/* Is it an array? */
			if (attinmeta->tupdesc->attrs[0]->attndims != 0 ||
				!OidIsValid(get_element_type(attinmeta->tupdesc->attrs[0]->atttypid)))
			{
				zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(val), &pos);
				zend_hash_get_current_data_ex(Z_ARRVAL_P(val),
											  (void **) &element,
											  &pos);
				values[0] = plphp_zval_get_cstring(element[0], true, true);
			}
			else
				values[0] = plphp_zval_get_cstring(val, true, true);
		}
		else
		{
			/*
			 * Ok, it's an array and the return tuple has more than one
			 * attribute, so scan each array element.
			 */
			for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(val), &pos);
				 zend_hash_get_current_data_ex(Z_ARRVAL_P(val),
											   (void **) &element,
											   &pos) == SUCCESS;
				 zend_hash_move_forward_ex(Z_ARRVAL_P(val), &pos))
			{
				/* avoid overrunning the palloc'ed chunk */
				if (i >= attinmeta->tupdesc->natts)
				{
					elog(WARNING, "more elements in array than attributes in return type");
					break;
				}

				values[i++] = plphp_zval_get_cstring(element[0], true, true);
			}
		}
	}
	else
	{
		/* The passed zval is not an array -- use as the only attribute */
		if (attinmeta->tupdesc->natts != 1)
			ereport(ERROR,
					(errmsg("returned array does not correspond to "
							"declared return value")));

		values[0] = plphp_zval_get_cstring(val, true, true);
	}

	MemoryContextSwitchTo(oldcxt);

	ret = BuildTupleFromCStrings(attinmeta, values);

	MemoryContextReset(cxt);

	return ret;
}

/*
 * plphp_convert_to_pg_array
 * 		Convert a zval into a Postgres text array representation.
 *
 * The return value is palloc'ed in the current memory context and
 * must be freed by the caller.
 */
char *
plphp_convert_to_pg_array(zval *array)
{
	int			arr_size;
	zval	  **element;
	int			i = 0;
	HashPosition 	pos;
	StringInfoData	str;

	initStringInfo(&str);

	arr_size = zend_hash_num_elements(Z_ARRVAL_P(array));

	appendStringInfoChar(&str, '{');
	if (Z_TYPE_P(array) == IS_ARRAY)
	{
		for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(array), &pos);
			 zend_hash_get_current_data_ex(Z_ARRVAL_P(array),
										   (void **) &element,
										   &pos) == SUCCESS;
			 zend_hash_move_forward_ex(Z_ARRVAL_P(array), &pos))
		{
			char *tmp;

			switch (Z_TYPE_P(element[0]))
			{
				case IS_LONG:
					appendStringInfo(&str, "%li", element[0]->value.lval);
					break;
				case IS_DOUBLE:
					appendStringInfo(&str, "%f", element[0]->value.dval);
					break;
				case IS_STRING:
					appendStringInfo(&str, "\"%s\"", element[0]->value.str.val);
					break;
				case IS_ARRAY:
					tmp = plphp_convert_to_pg_array(element[0]);
					appendStringInfo(&str, "%s", tmp);
					pfree(tmp);
					break;
				default:
					elog(ERROR, "unrecognized element type %d",
						 Z_TYPE_P(element[0]));
			}

			if (i != arr_size - 1)
				appendStringInfoChar(&str, ',');
			i++;
		}
	}

	appendStringInfoChar(&str, '}');

	return str.data;
}

/*
 * plphp_convert_from_pg_array
 * 		Convert a Postgres text array representation to a PHP array
 * 		(zval type thing).
 *
 * FIXME -- does not work if there are embedded {'s in the input value.
 *
 * FIXME -- does not correctly quote/dequote the values
 */
zval *
plphp_convert_from_pg_array(char *input TSRMLS_DC)
{
	zval	   *retval = NULL;
	int			i;
	StringInfoData str;

	initStringInfo(&str);

	MAKE_STD_ZVAL(retval);
	array_init(retval);

	for (i = 0; i < strlen(input); i++)
	{
		if (input[i] == '{')
			appendStringInfoString(&str, "array(");
		else if (input[i] == '}')
			appendStringInfoChar(&str, ')');
		else
			appendStringInfoChar(&str, input[i]);
	}
	appendStringInfoChar(&str, ';');

	if (zend_eval_string(str.data, retval,
						 "plphp array input parameter" TSRMLS_CC) == FAILURE)
		elog(ERROR, "plphp: convert to internal representation failure");

	pfree(str.data);

	return retval;
}

/*
 * plphp_array_get_elem
 * 		Return a pointer to the array element with the given key
 */
zval *
plphp_array_get_elem(zval *array, char *key)
{
	zval	  **element;

	if (!array)
		elog(ERROR, "passed zval is not a valid pointer");
	if (Z_TYPE_P(array) != IS_ARRAY)
		elog(ERROR, "passed zval is not an array");

	if (zend_symtable_find(array->value.ht,
					   	   key,
					       strlen(key) + 1,
					       (void **) &element) != SUCCESS)
		return NULL;

	return element[0];
}

/*
 * zval_get_cstring
 *		Get a C-string representation of a zval.
 *
 * All return values, except those that are NULL, are palloc'ed in the current
 * memory context and must be freed by the caller.
 *
 * If the do_array parameter is false, then array values will not be converted
 * and an error will be raised instead.
 *
 * If the null_ok parameter is true, we will return NULL for a NULL zval.
 * Otherwise we raise an error.
 */
char *
plphp_zval_get_cstring(zval *val, bool do_array, bool null_ok)
{
	char *ret;

	if (!val)
	{
		if (null_ok)
			return NULL;
		else
			elog(ERROR, "invalid zval pointer");
	}

	switch (Z_TYPE_P(val))
	{
		case IS_NULL:
			return NULL;
		case IS_LONG:
			ret = palloc(64);
			snprintf(ret, 64, "%ld", Z_LVAL_P(val));
			break;
		case IS_DOUBLE:
			ret = palloc(64);
			snprintf(ret, 64, "%f", Z_DVAL_P(val));
			break;
		case IS_BOOL:
			ret = palloc(8);
			snprintf(ret, 8, "%s", Z_BVAL_P(val) ? "true": "false");
			break;
		case IS_STRING:
			ret = palloc(Z_STRLEN_P(val) + 1);
			snprintf(ret, Z_STRLEN_P(val) + 1, "%s",
					 Z_STRVAL_P(val));
			break;
		case IS_ARRAY:
			if (!do_array)
				elog(ERROR, "can't stringize array value");
			ret = plphp_convert_to_pg_array(val);
			break;
		default:
			/* keep compiler quiet */
			ret = NULL;
			elog(ERROR, "can't stringize value of type %d", val->type);
	}

	return ret;
}

/*
 * plphp_build_tuple_argument
 *
 * Build a PHP array from all attributes of a given tuple
 */
zval *
plphp_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc)
{
	int			i;
	zval	   *output;
	Datum		attr;
	bool		isnull;
	char	   *attname;
	char	   *outputstr;
	HeapTuple	typeTup;
	Oid			typoutput;
	Oid			typioparam;

	MAKE_STD_ZVAL(output);
	array_init(output);

	for (i = 0; i < tupdesc->natts; i++)
	{
		/* Ignore dropped attributes */
		if (tupdesc->attrs[i]->attisdropped)
			continue;

		/* Get the attribute name */
		attname = tupdesc->attrs[i]->attname.data;

		/* Get the attribute value */
		attr = heap_getattr(tuple, i + 1, tupdesc, &isnull);

		/* If it is null, set it to undef in the hash. */
		if (isnull)
		{
			add_next_index_unset(output);
			continue;
		}

		/*
		 * Lookup the attribute type in the syscache for the output function
		 */
		typeTup = SearchSysCache(TYPEOID,
								 ObjectIdGetDatum(tupdesc->attrs[i]->atttypid),
								 0, 0, 0);
		if (!HeapTupleIsValid(typeTup))
		{
			elog(ERROR, "cache lookup failed for type %u",
				 tupdesc->attrs[i]->atttypid);
		}

		typoutput = ((Form_pg_type) GETSTRUCT(typeTup))->typoutput;
		typioparam = getTypeIOParam(typeTup);
		ReleaseSysCache(typeTup);

		/* Append the attribute name and the value to the list. */
		outputstr =
			DatumGetCString(OidFunctionCall3(typoutput, attr,
											 ObjectIdGetDatum(typioparam),
											 Int32GetDatum(tupdesc->attrs[i]->atttypmod)));
		add_assoc_string(output, attname, outputstr, 1);
		pfree(outputstr);
	}

	return output;
}

/*
 * plphp_modify_tuple
 * 		Return the modified NEW tuple, for use as return value in a BEFORE
 * 		trigger.  outdata must point to the $_TD variable from the PHP
 * 		function.
 *
 * The tuple will be allocated in the current memory context and must be freed
 * by the caller.
 *
 * XXX Possible optimization: make this a global context that is not deleted,
 * but only reset each time this function is called.  (Think about triggers
 * calling other triggers though).
 */
HeapTuple
plphp_modify_tuple(zval *outdata, TriggerData *tdata)
{
	TupleDesc	tupdesc;
	HeapTuple	rettuple;
	zval	   *newtup;
	zval	  **element;
	char	  **vals;
	int			i;
	AttInMetadata *attinmeta;
	MemoryContext tmpcxt,
				  oldcxt;

	tmpcxt = AllocSetContextCreate(CurTransactionContext,
								   "PL/php NEW context",
								   ALLOCSET_DEFAULT_MINSIZE,
								   ALLOCSET_DEFAULT_INITSIZE,
								   ALLOCSET_DEFAULT_MAXSIZE);

	oldcxt = MemoryContextSwitchTo(tmpcxt);

	/* Fetch "new" from $_TD */
	if (zend_hash_find(outdata->value.ht,
					   "new", strlen("new") + 1,
					   (void **) &element) != SUCCESS)
		elog(ERROR, "$_TD['new'] not found");

	if (Z_TYPE_P(element[0]) != IS_ARRAY)
		elog(ERROR, "$_TD['new'] must be an array");
	newtup = element[0];

	/* Fetch the tupledesc and metadata */
	tupdesc = tdata->tg_relation->rd_att;
	attinmeta = TupleDescGetAttInMetadata(tupdesc);

	i = zend_hash_num_elements(Z_ARRVAL_P(newtup));

	if (tupdesc->natts > i)
		ereport(ERROR,
				(errmsg("insufficient number of keys in $_TD['new']"),
				 errdetail("At least %d expected, %d found.",
						   tupdesc->natts, i)));

	vals = (char **) palloc(tupdesc->natts * sizeof(char *));

	/*
	 * For each attribute in the tupledesc, get its value from newtup and put
	 * it in an array of cstrings.
	 */
	for (i = 0; i < tupdesc->natts; i++)
	{
		zval  **element;
		char   *attname = NameStr(tupdesc->attrs[i]->attname);

		/* Fetch the attribute value from the zval */
		if (zend_symtable_find(newtup->value.ht, attname, strlen(attname) + 1,
						   	   (void **) &element) != SUCCESS)
			elog(ERROR, "$_TD['new'] does not contain attribute \"%s\"",
				 attname);

		vals[i] = plphp_zval_get_cstring(element[0], true, true);
	}

	/* Return to the original context so that the new tuple will survive */
	MemoryContextSwitchTo(oldcxt);

	/* Build the tuple */
	rettuple = BuildTupleFromCStrings(attinmeta, vals);

	/* Free the memory used */
	MemoryContextDelete(tmpcxt);

	return rettuple;
}

/*
 * vim:ts=4:sw=4:cino=(0
 */

/**********************************************************************
 * plphp_spi.c - SPI-related functions for PL/php.
 *
 * This software is copyright (c) Command Prompt Inc.
 *
 * 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.
 *
 * IDENTIFICATION
 *		$Id$
 *********************************************************************
 */

#include "postgres.h"
#include "plphp_spi.h"
#include "plphp_io.h"

/* PHP stuff */
#include "php.h"

/* PostgreSQL stuff */
#include "access/xact.h"
#include "access/htup_details.h"
#include "miscadmin.h"

#undef DEBUG_PLPHP_MEMORY

#ifdef DEBUG_PLPHP_MEMORY
#define REPORT_PHP_MEMUSAGE(where) \
	elog(NOTICE, "PHP mem usage: «%s»: %u", where, AG(allocated_memory));
#else
#define REPORT_PHP_MEMUSAGE(a)
#endif

/* resource type Id for SPIresult */
int SPIres_rtype;

/* SPI function table */
zend_function_entry spi_functions[] =
{
	ZEND_FE(spi_exec, NULL)
	ZEND_FE(spi_fetch_row, NULL)
	ZEND_FE(spi_processed, NULL)
	ZEND_FE(spi_status, NULL)
	ZEND_FE(spi_rewind, NULL)
	ZEND_FE(pg_raise, NULL)
	ZEND_FE(return_next, NULL)
	{NULL, NULL, NULL}
};

/* SRF support: */
FunctionCallInfo current_fcinfo = NULL;
TupleDesc current_tupledesc = NULL;
AttInMetadata *current_attinmeta = NULL;
MemoryContext current_memcxt = NULL;
Tuplestorestate *current_tuplestore = NULL;

/* A symbol table to save for return_next for the RETURNS TABLE case */
HashTable *saved_symbol_table;

static zval *get_table_arguments(AttInMetadata *attinmeta);

/*
 * spi_exec
 * 		PL/php equivalent to SPI_exec().
 *
 * This function creates and return a PHP resource which describes the result
 * of a user-specified query.  If the query returns tuples, it's possible to
 * retrieve them by using spi_fetch_row.
 *
 * Receives one or two arguments.  The mandatory first argument is the query
 * text.  The optional second argument is the tuple limit.
 *
 * Note that just like PL/Perl, we start a subtransaction before invoking the
 * SPI call, and automatically roll it back if the call fails.
 */
ZEND_FUNCTION(spi_exec)
{
	char	   *query;
	int			query_len;
	long		status;
	long		limit;
	php_SPIresult *SPIres;
	int			spi_id;
	MemoryContext oldcontext = CurrentMemoryContext;
	ResourceOwner oldowner = CurrentResourceOwner;

	REPORT_PHP_MEMUSAGE("spi_exec called");

	if ((ZEND_NUM_ARGS() > 2) || (ZEND_NUM_ARGS() < 1))
		WRONG_PARAM_COUNT;

	/* Parse arguments */
	if (ZEND_NUM_ARGS() == 2)
	{
		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl",
								  &query, &query_len, &limit) == FAILURE)
		{
			zend_error(E_WARNING, "Can not parse parameters in %s",
					   get_active_function_name(TSRMLS_C));
			RETURN_FALSE;
		}
	}
	else if (ZEND_NUM_ARGS() == 1)
	{
		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",
								  &query, &query_len) == FAILURE)
		{
			zend_error(E_WARNING, "Can not parse parameters in %s",
					   get_active_function_name(TSRMLS_C));
			RETURN_FALSE;
		}
		limit = 0;
	}
	else
	{
		zend_error(E_WARNING, "Incorrect number of parameters to %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}

	BeginInternalSubTransaction(NULL);
	MemoryContextSwitchTo(oldcontext);

	/* Call SPI */
	PG_TRY();
	{
		status = SPI_exec(query, limit);

		ReleaseCurrentSubTransaction();
		MemoryContextSwitchTo(oldcontext);
		CurrentResourceOwner = oldowner;

		/*
		 * AtEOSubXact_SPI() should not have popped any SPI context, but just
		 * in case it did, make sure we remain connected.
		 */
		SPI_restore_connection();
	}
	PG_CATCH();
	{
		ErrorData	*edata;

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

		/* Abort the inner trasaction */
		RollbackAndReleaseCurrentSubTransaction();
		MemoryContextSwitchTo(oldcontext);
		CurrentResourceOwner = oldowner;

		/*
		 * If AtEOSubXact_SPI() popped any SPI context of the subxact, it will
		 * have left us in a disconnected state.  We need this hack to return
		 * to connected state.
		 */
		SPI_restore_connection();

		/* bail PHP out */
		zend_error(E_ERROR, "%s", strdup(edata->message));

		/* Can't get here, but keep compiler quiet */
		return;
	}
	PG_END_TRY();

	/* This malloc'ed chunk is freed in php_SPIresult_destroy */
	SPIres = (php_SPIresult *) malloc(sizeof(php_SPIresult));
	if (!SPIres)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory")));

	/* Prepare the return resource */
	SPIres->SPI_processed = SPI_processed;
	if (status == SPI_OK_SELECT)
		SPIres->SPI_tuptable = SPI_tuptable;
	else
		SPIres->SPI_tuptable = NULL;
	SPIres->current_row = 0;
	SPIres->status = status;

	REPORT_PHP_MEMUSAGE("spi_exec: creating resource");

	/* Register the resource to PHP so it will be able to free it */
	spi_id = ZEND_REGISTER_RESOURCE(return_value, (void *) SPIres,
					 				SPIres_rtype);

	REPORT_PHP_MEMUSAGE("spi_exec: returning");

	RETURN_RESOURCE(spi_id);
}

/*
 * spi_fetch_row
 * 		Grab a row from a SPI result (from spi_exec).
 *
 * This function receives a resource Id and returns a PHP hash representing the
 * next tuple in the result, or false if no tuples remain.
 *
 * XXX Apparently this is leaking memory.  How do we tell PHP to free the tuple
 * once the user is done with it?
 */
ZEND_FUNCTION(spi_fetch_row)
{
	zval	   *row = NULL;
	zval	  **z_spi = NULL;
	php_SPIresult	*SPIres;

	REPORT_PHP_MEMUSAGE("spi_fetch_row: called");

	if (ZEND_NUM_ARGS() != 1)
		WRONG_PARAM_COUNT;

	if (zend_get_parameters_ex(1, &z_spi) == FAILURE)
	{
		zend_error(E_WARNING, "Can not parse parameters in %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}
	if (z_spi == NULL)
	{
		zend_error(E_WARNING, "Could not get SPI resource in %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}

	ZEND_FETCH_RESOURCE(SPIres, php_SPIresult *, z_spi, -1, "SPI result",
						SPIres_rtype);

	if (SPIres->status != SPI_OK_SELECT)
	{
		zend_error(E_WARNING, "SPI status is not good");
		RETURN_FALSE;
	}

	if (SPIres->current_row < SPIres->SPI_processed)
	{
		row = plphp_zval_from_tuple(SPIres->SPI_tuptable->vals[SPIres->current_row],
			  						SPIres->SPI_tuptable->tupdesc);
		SPIres->current_row++;

		*return_value = *row;

		zval_copy_ctor(return_value);
		zval_dtor(row);
		FREE_ZVAL(row);

	}
	else
		RETURN_FALSE;

	REPORT_PHP_MEMUSAGE("spi_fetch_row: finish");
}

/*
 * spi_processed
 * 		Return the number of tuples returned in a spi_exec call.
 */
ZEND_FUNCTION(spi_processed)
{
	zval	   **z_spi = NULL;
	php_SPIresult	*SPIres;

	REPORT_PHP_MEMUSAGE("spi_processed: start");

	if (ZEND_NUM_ARGS() != 1)
		WRONG_PARAM_COUNT;

	if (zend_get_parameters_ex(1, &z_spi) == FAILURE)
	{
		zend_error(E_WARNING, "Cannot parse parameters in %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}
	if (z_spi == NULL)
	{
		zend_error(E_WARNING, "Could not get SPI resource in %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}

	ZEND_FETCH_RESOURCE(SPIres, php_SPIresult *, z_spi, -1, "SPI result",
						SPIres_rtype);

	REPORT_PHP_MEMUSAGE("spi_processed: finish");

	RETURN_LONG(SPIres->SPI_processed);
}

/*
 * spi_status
 * 		Return the status returned by a previous spi_exec call, as a string.
 */
ZEND_FUNCTION(spi_status)
{
	zval	   **z_spi = NULL;
	php_SPIresult	*SPIres;

	REPORT_PHP_MEMUSAGE("spi_status: start");

	if (ZEND_NUM_ARGS() != 1)
		WRONG_PARAM_COUNT;

	if (zend_get_parameters_ex(1, &z_spi) == FAILURE)
	{
		zend_error(E_WARNING, "Cannot parse parameters in %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}
	if (z_spi == NULL)
	{
		zend_error(E_WARNING, "Could not get SPI resource in %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}

	ZEND_FETCH_RESOURCE(SPIres, php_SPIresult *, z_spi, -1, "SPI result",
						SPIres_rtype);

	REPORT_PHP_MEMUSAGE("spi_status: finish");

	/*
	 * XXX The cast is wrong, but we use it to prevent a compiler warning.
	 * Note that the second parameter to RETURN_STRING is "duplicate", so
	 * we are returning a copy of the string anyway.
	 */
	RETURN_STRING((char *) SPI_result_code_string(SPIres->status), true);
}

/*
 * spi_rewind
 * 		Resets the internal counter for spi_fetch_row, so the next
 * 		spi_fetch_row call will start fetching from the beginning.
 */
ZEND_FUNCTION(spi_rewind)
{
	zval	   **z_spi = NULL;
	php_SPIresult	*SPIres;

	if (ZEND_NUM_ARGS() != 1)
		WRONG_PARAM_COUNT;

	if (zend_get_parameters_ex(1, &z_spi) == FAILURE)
	{
		zend_error(E_WARNING, "Cannot parse parameters in %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}
	if (z_spi == NULL)
	{
		zend_error(E_WARNING, "Could not get SPI resource in %s",
				   get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}

	ZEND_FETCH_RESOURCE(SPIres, php_SPIresult *, z_spi, -1, "SPI result",
						SPIres_rtype);

	SPIres->current_row = 0;

	RETURN_NULL();
}
/*
 * pg_raise
 *      User-callable function for sending messages to the Postgres log.
 */
ZEND_FUNCTION(pg_raise)
{
	char       *level = NULL,
			   *message = NULL;
	int         level_len,
				message_len,
				elevel = 0;

	if (ZEND_NUM_ARGS() != 2)
	{
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("wrong number of arguments to %s", "pg_raise")));
	}

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",
							  &level, &level_len,
							  &message, &message_len) == FAILURE)
	{
		zend_error(E_WARNING, "cannot parse parameters in %s",
				   get_active_function_name(TSRMLS_C));
	}

	if (strcasecmp(level, "ERROR") == 0)
		elevel = E_ERROR;
	else if (strcasecmp(level, "WARNING") == 0)
		elevel = E_WARNING;
	else if (strcasecmp(level, "NOTICE") == 0)
		elevel = E_NOTICE;
	else
		zend_error(E_ERROR, "incorrect log level");

	zend_error(elevel, "%s", message);
}

/*
 * return_next
 * 		Add a tuple to the current tuplestore
 */
ZEND_FUNCTION(return_next)
{
	MemoryContext	oldcxt;
	zval	   *param;
	HeapTuple	tup;
	ReturnSetInfo *rsi;

	/*
	 * Disallow use of return_next inside non-SRF functions
	 */
	if (current_fcinfo == NULL || current_fcinfo->flinfo == NULL ||
		!current_fcinfo->flinfo->fn_retset)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot use return_next in functions not declared to "
						"return a set")));

	rsi = (ReturnSetInfo *) current_fcinfo->resultinfo;

	Assert(current_tupledesc != NULL);
	Assert(rsi != NULL);

	if (ZEND_NUM_ARGS() > 1)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("wrong number of arguments to %s", "return_next")));

	if (ZEND_NUM_ARGS() == 0)
	{
		/*
		 * Called from the function declared with RETURNS TABLE
	     */
		param = get_table_arguments(current_attinmeta);
	}
	else if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z",
							  &param) == FAILURE)
	{
		zend_error(E_WARNING, "cannot parse parameters in %s",
				   get_active_function_name(TSRMLS_C));
	}

	/* Use the per-query context so that the tuplestore survives */
	oldcxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);

	/* Form the tuple */
	tup = plphp_srf_htup_from_zval(param, current_attinmeta, current_memcxt);

	/* First call?  Create the tuplestore. */
	if (!current_tuplestore)
		current_tuplestore = tuplestore_begin_heap(true, false, work_mem);

	/* Save the tuple and clean up */
	tuplestore_puttuple(current_tuplestore, tup);
	heap_freetuple(tup);

	MemoryContextSwitchTo(oldcxt);
}

/*
 * php_SPIresult_destroy
 * 		Free the resources allocated by a spi_exec call.
 *
 * This is automatically called when the resource goes out of scope
 * or is overwritten by another resource.
 */
void
php_SPIresult_destroy(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
	php_SPIresult *res = (php_SPIresult *) rsrc->ptr;

	if (res->SPI_tuptable != NULL)
		SPI_freetuptable(res->SPI_tuptable);

	free(res);
}

/* Return an array of TABLE argument values for return_next */
static
zval *get_table_arguments(AttInMetadata *attinmeta)
{
	zval   *retval = NULL;
	int		i;

	MAKE_STD_ZVAL(retval);
	array_init(retval);

	Assert(attinmeta->tupdesc);
	Assert(saved_symbol_table != NULL);
	/* Extract OUT argument names */
	for (i = 0; i < attinmeta->tupdesc->natts; i++)
	{
		zval 	**val;
		char 	*attname;

		Assert(!attinmeta->tupdesc->attrs[i]->attisdropped);

		attname = NameStr(attinmeta->tupdesc->attrs[i]->attname);

		if (zend_hash_find(saved_symbol_table,
						   attname, strlen(attname) + 1,
						   (void **)&val) == SUCCESS)

			add_next_index_zval(retval, *val);
		else
			add_next_index_unset(retval);
	}
	return retval;
}

/*
 * vim:ts=4:sw=4:cino=(0
 */