/**********************************************************************
 * plpgsql_debugger.c	- Debugger for the PL/pgSQL procedural language
 *
 * Copyright (c) 2004-2018 EnterpriseDB Corporation. All Rights Reserved.
 *
 * Licensed under the Artistic License v2.0, see
 *		https://opensource.org/licenses/artistic-license-2.0
 * for full details
 *
 **********************************************************************/

#include "postgres.h"

#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <setjmp.h>
#include <signal.h>

#include "lib/stringinfo.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "globalbp.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/syscache.h"
#include "miscadmin.h"

#if INCLUDE_PACKAGE_SUPPORT
#include "spl.h"
#include "catalog/edb_variable.h"
#else
#include "plpgsql.h"
#endif

#include "pldebugger.h"

/* Include header for GETSTRUCT */
#if (PG_VERSION_NUM >= 90300)
#include "access/htup_details.h"
#endif

/*
 * We use a var_value structure to record  a little extra information about
 * each variable.
 */

typedef struct
{
	bool	    isnull;			/* TRUE -> this variable IS NULL */
	bool		visible;		/* hidden or visible? see is_visible_datum() */
	bool		duplicate_name;	/* Is this one of many vars with same name? */
} var_value;

/*
 * When the debugger decides that it needs to step through (or into) a
 * particular function invocation, it allocates a dbg_ctx and records the
 * address of that structure in the executor's context structure
 * (estate->plugin_info).
 *
 * The dbg_ctx keeps track of all of the information we need to step through
 * code and display variable values
 */

typedef struct
{
	PLpgSQL_function *	func;		/* Function definition */
	bool				stepping;	/* If TRUE, stop at next statement */
	var_value	     *  symbols;	/* Extra debugger-private info about variables */
	char			 ** argNames;	/* Argument names */
	int					argNameCount; /* Number of names pointed to by argNames */
	void 			 (* error_callback)(void *arg);
	void 			 (* assign_expr)( PLpgSQL_execstate *estate, PLpgSQL_datum *target, PLpgSQL_expr *expr );
#if INCLUDE_PACKAGE_SUPPORT
	PLpgSQL_package   * package;
#endif
} dbg_ctx;

static void 		 dbg_startup( PLpgSQL_execstate * estate, PLpgSQL_function * func );
static void 		 dbg_newstmt( PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt );
static void 		 initialize_plugin_info( PLpgSQL_execstate * estate, PLpgSQL_function * func );

static char       ** fetchArgNames( PLpgSQL_function * func, int * nameCount );
static PLpgSQL_var * find_var_by_name( const PLpgSQL_execstate * estate, const char * var_name, int lineno, int * index );

static bool 		 is_datum_visible( PLpgSQL_datum * datum );
static bool			 is_var_visible( PLpgSQL_execstate * frame, int var_no );
static bool			 datumIsNull(PLpgSQL_datum *datum);
static bool          varIsArgument(const PLpgSQL_execstate *estate, PLpgSQL_function *func, int varNo, char **p_argname);
static char		   * get_text_val( PLpgSQL_var * var, char ** name, char ** type );

#if INCLUDE_PACKAGE_SUPPORT
static const char * plugin_name  = "spl_plugin";
#else
static const char * plugin_name  = "PLpgSQL_plugin";
#endif

static PLpgSQL_plugin plugin_funcs = { dbg_startup, NULL, NULL, dbg_newstmt, NULL };

/*
 * pldebugger_language_t interface.
 */
static void plpgsql_debugger_init(void);
static bool plpgsql_frame_belongs_to_me(ErrorContextCallback *frame);
static void plpgsql_send_stack_frame(ErrorContextCallback *frame);
static void plpgsql_send_vars(ErrorContextCallback *frame);
static void plpgsql_select_frame(ErrorContextCallback *frame);
static void plpgsql_print_var(ErrorContextCallback *frame, const char *var_name, int lineno);
static bool plpgsql_do_deposit(ErrorContextCallback *frame, const char *var_name, int line_number, const char *value);
static Oid plpgsql_get_func_oid(ErrorContextCallback *frame);
static void plpgsql_send_cur_line(ErrorContextCallback *frame);

#if INCLUDE_PACKAGE_SUPPORT
debugger_language_t spl_debugger_lang =
#else
debugger_language_t plpgsql_debugger_lang =
#endif
{
	plpgsql_debugger_init,
	plpgsql_frame_belongs_to_me,
	plpgsql_send_stack_frame,
	plpgsql_send_vars,
	plpgsql_select_frame,
	plpgsql_print_var,
	plpgsql_do_deposit,
	plpgsql_get_func_oid,
	plpgsql_send_cur_line
};

/* Install this module as an PL/pgSQL instrumentation plugin */
static void
plpgsql_debugger_init(void)
{
	PLpgSQL_plugin ** var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable( plugin_name );

	*var_ptr = &plugin_funcs;
}


/**********************************************************************
 * Functions implemeting the pldebugger_language_t interface
 **********************************************************************/

static bool
plpgsql_frame_belongs_to_me(ErrorContextCallback *frame)
{
	return (frame->callback == plugin_funcs.error_callback);
}

/*
 * plpgsql_send_stack_frame()
 *
 * This function sends information about a single stack frame to the debugger
 * client.  This function is called by send_stack() whenever send_stack()
 * finds a PL/pgSQL call in the stack (remember, the call stack may contain
 * stack frames for functions written in other languages like PL/Tcl).
 */
static void
plpgsql_send_stack_frame(ErrorContextCallback *frame)
{
	PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg;
#if (PG_VERSION_NUM >= 80500)
	PLpgSQL_function  * func     = estate->func;
#else
	PLpgSQL_function  * func     = estate->err_func;
#endif
	PLpgSQL_stmt	  * stmt 	 = estate->err_stmt;
	int					argNameCount;
	char             ** argNames = fetchArgNames( func, &argNameCount );
	StringInfo		    result   = makeStringInfo();
	char              * delimiter = "";
	int				    arg;

	/*
	 * Send the name, function OID, and line number for this frame
	 */

	appendStringInfo( result, "%s:%d:%d:",
#if (PG_VERSION_NUM >= 90200)
					  func->fn_signature,
#else
					  func->fn_name,
#endif
					  func->fn_oid,
					  stmt->lineno );

	/*
	 * Now assemble a string that shows the argument names and value for this frame
	 */

	for( arg = 0; arg < func->fn_nargs; ++arg )
	{
		int					index   = func->fn_argvarnos[arg];
		PLpgSQL_datum		*argDatum = (PLpgSQL_datum *)estate->datums[index];
		char 				*value;

		/* value should be an empty string if argDatum is null*/
		if( datumIsNull( argDatum ))
			value = pstrdup( "" );
		else
			value = get_text_val((PLpgSQL_var*)argDatum, NULL, NULL );

		if( argNames && argNames[arg] && argNames[arg][0] )
			appendStringInfo( result,  "%s%s=%s", delimiter, argNames[arg], value );
		else
			appendStringInfo( result,  "%s$%d=%s", delimiter, arg+1, value );

		pfree( value );

		delimiter = ", ";
	}

	dbg_send( "%s", result->data );
}

/*
 * varIsArgument() :
 *
 * Returns true if it's an argument of the function. In case the function is an
 * EDB-SPL nested function, it returns true only if it is an argument of the
 * current nested function; in all other cases it returns false, even if it's
 * an argument of any of the outer nested functions in the current nested
 * function call stack.
 *
 * If the variable is a *named* argument, the argument name is passed
 * through 'p_argname'. In case of nested functions, p_argname is set if the
 * variable name is a named argument of any of the nested functions in the
 * current nested function call stack.
 *
 * varNo is the estate->datums index.
 */
static bool
varIsArgument(const PLpgSQL_execstate *estate, PLpgSQL_function *func,
		   int varNo, char **p_argname)
{
#if INCLUDE_PACKAGE_SUPPORT && (PG_VERSION_NUM >= 90600)

	/*
	 * If this variable is actually an argument of this function, return the
	 * argument name. Such argument variables have '$n' refname, so we need to
	 * use the function argument name if it's a named argument.
	 */
	if (func->fn_nallargs > 0 &&
		varNo >= func->fn_first_argno &&
		varNo < func->fn_first_argno + func->fn_nallargs)
	{
		char *argname = func->fn_argnames[varNo - func->fn_first_argno];

		if (argname && argname[0])
			*p_argname = argname;

		return true;
	}

	/*
	 * Now check if it is an argument of any of the outer functions. If yes,
	 * we need to get the argument name in case it is a named argument.
	 */
	if (func->parent)
		(void) varIsArgument(estate, func->parent, varNo, p_argname);

	/* It is not an argument of the current function. */
	return false;

#else

	dbg_ctx	   *dbg_info = (dbg_ctx *) estate->plugin_info;
	bool		isArg = false;

	if (varNo < dbg_info->func->fn_nargs)
		isArg = true;

	/* Get it's name if it's a named argument. */
	if (varNo < dbg_info->argNameCount)
	{
		isArg = true;

		if (dbg_info->argNames && dbg_info->argNames[varNo] &&
			dbg_info->argNames[varNo][0])
		{
			*p_argname  = dbg_info->argNames[varNo];
		}
	}

	return isArg;

#endif
}

/*
 * plpgsql_send_vars()
 *
 * This function sends a list of variables (names, types, values...) to
 * the proxy process.  We send information about the variables defined in
 * the given frame (local variables) and parameter values.
 */
static void
plpgsql_send_vars(ErrorContextCallback *frame)
{
	PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg;
	dbg_ctx * dbg_info = (dbg_ctx *) estate->plugin_info;
	int       i;

	for( i = 0; i < estate->ndatums; i++ )
	{
		if( is_var_visible( estate, i ))
		{
			switch( estate->datums[i]->dtype )
			{
#if (PG_VERSION_NUM >= 110000)
				case PLPGSQL_DTYPE_PROMISE:
#endif
				case PLPGSQL_DTYPE_VAR:
				{
					PLpgSQL_var * var = (PLpgSQL_var *) estate->datums[i];
					char        * val;
					char		* name = var->refname;
					bool		  isArg;

					isArg = varIsArgument(estate, dbg_info->func, i, &name);

					if( datumIsNull((PLpgSQL_datum *)var ))
						val = "NULL";
					else
						val = get_text_val( var, NULL, NULL );

					dbg_send( "%s:%c:%d:%c:%c:%c:%d:%s",
							  name,
							  isArg ? 'A' : 'L',
							  var->lineno,
							  dbg_info->symbols[i].duplicate_name ? 'f' : 't',
							  var->isconst ? 't':'f',
							  var->notnull ? 't':'f',
							  var->datatype ? var->datatype->typoid : InvalidOid,
							  val );

					break;
				}
#if 0
			FIXME: implement other types

				case PLPGSQL_DTYPE_REC:
				{
					PLpgSQL_rec * rec = (PLpgSQL_rec *) estate->datums[i];
					int		      att;
					char        * typeName;

					if (rec->tupdesc != NULL)
					{
						for( att = 0; att < rec->tupdesc->natts; ++att )
						{
							typeName = SPI_gettype( rec->tupdesc, att + 1 );

							dbg_send( "o:%s.%s:%d:%d:%d:%d:%s\n",
									  rec->refname, NameStr( rec->tupdesc->attrs[att]->attname ),
									  0, rec->lineno, 0, rec->tupdesc->attrs[att]->attnotnull, typeName ? typeName : "" );

							if( typeName )
								pfree( typeName );
						}
					}
					break;
				}
#endif
				case PLPGSQL_DTYPE_ROW:
				case PLPGSQL_DTYPE_REC:
				case PLPGSQL_DTYPE_RECFIELD:
#if (PG_VERSION_NUM < 140000)
				case PLPGSQL_DTYPE_ARRAYELEM:
#endif
#if (PG_VERSION_NUM < 110000)
				case PLPGSQL_DTYPE_EXPR:
#endif
				{
					/* FIXME: implement other types */
					break;
				}
			}
		}
	}

#if INCLUDE_PACKAGE_SUPPORT
	/* If this frame represents a package function/procedure, send the package variables too */
	if( dbg_info->package != NULL )
	{
		PLpgSQL_package * package = dbg_info->package;
		int				  varIndex;

		for( varIndex = 0; varIndex < package->ndatums; ++varIndex )
		{
			PLpgSQL_datum * datum = package->datums[varIndex];

			switch( datum->dtype )
			{
				case PLPGSQL_DTYPE_VAR:
				{
					PLpgSQL_var * var = (PLpgSQL_var *) datum;
					char        * val;
					char		* name = var->refname;

					if( datumIsNull((PLpgSQL_datum *)var ))
						val = "NULL";
					else
						val = get_text_val( var, NULL, NULL );

					dbg_send( "%s:%c:%d:%c:%c:%c:%d:%s",
							  name,
							  'P',				/* variable class - P means package var */
							  var->lineno,
							  'f',				/* duplicate name?						*/
							  var->isconst ? 't':'f',
							  var->notnull ? 't':'f',
							  var->datatype ? var->datatype->typoid : InvalidOid,
							  val );

					break;
				}
			}
		}
	}
#endif

	dbg_send( "%s", "" );	/* empty string indicates end of list */
}

static void
plpgsql_select_frame(ErrorContextCallback *frame)
{
	PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg;

	/*
	 * When a frame is selected, ensure that we've initialized its
	 * plugin_info.
	 */
	if (estate->plugin_info == NULL)
	{
#if (PG_VERSION_NUM >= 80500)
		initialize_plugin_info(estate, estate->func);
#else
		initialize_plugin_info(estate, estate->err_func);
#endif
	}
}

/*
 * ---------------------------------------------------------------------
 * find_var_by_name()
 *
 *	This function returns the PLpgSQL_var pointer that corresponds to
 *	named variable (var_name).  If the named variable can't be found,
 *  find_var_by_name() returns NULL.
 *
 *  If the index is non-NULL, this function will set *index to the
 *  named variables index withing estate->datums[]
 */

static PLpgSQL_var *
find_var_by_name(const PLpgSQL_execstate * estate, const char * var_name, int lineno, int * index)
{
	dbg_ctx          * dbg_info = (dbg_ctx *)estate->plugin_info;
	PLpgSQL_function * func     = dbg_info->func;
	int                i;

	for( i = 0; i < func->ndatums; i++ )
	{
		PLpgSQL_var * var = (PLpgSQL_var *) estate->datums[i];
		size_t 		  len = strlen(var->refname);

		if(len != strlen(var_name))
			continue;

		if( strncmp( var->refname, var_name, len) == 0 )
		{
		 	if(( lineno == -1 ) || ( var->lineno == lineno ))
			{
				/* Found the named variable - return the index if the caller wants it */

				if( index )
					*index = i;
			}

			return( var );
		}
	}

	/* We can't find the variable named by the caller - return NULL */

	return( NULL );

}

static PLpgSQL_datum *
find_datum_by_name(const PLpgSQL_execstate *frame, const char *var_name,
				   int lineNo, int *index)
{
	dbg_ctx * dbg_info = (dbg_ctx *)frame->plugin_info;
	int		  i;

#if INCLUDE_PACKAGE_SUPPORT

	if( var_name[0] == '@' )
	{
		/* This is a package variable (it's name starts with a '@') */
		int		  varIndex;

		if( dbg_info == NULL )
			return( NULL );

		if( dbg_info->package == NULL )
			return( NULL );

		for( varIndex = 0; varIndex < dbg_info->package->ndatums; ++varIndex )
		{
			PLpgSQL_datum * datum = dbg_info->package->datums[varIndex];

			switch( datum->dtype )
			{
#if (PG_VERSION_NUM >= 110000)
				case PLPGSQL_DTYPE_PROMISE:
#endif
				case PLPGSQL_DTYPE_VAR:
				{
					PLpgSQL_var * var = (PLpgSQL_var *) datum;

					if( strcmp( var->refname, var_name+1 ) == 0 )
						return( datum );
					break;
				}
			}
		}

		return( NULL );
	}
#endif

	for( i = 0; i < frame->ndatums; ++i )
	{
		char	*	datumName = NULL;
		int			datumLineno = -1;

		switch( frame->datums[i]->dtype )
		{
#if (PG_VERSION_NUM >= 110000)
			case PLPGSQL_DTYPE_PROMISE:
#endif
			case PLPGSQL_DTYPE_VAR:
			case PLPGSQL_DTYPE_ROW:
			case PLPGSQL_DTYPE_REC:
			{
				PLpgSQL_variable * var = (PLpgSQL_variable *)frame->datums[i];

				datumName   = var->refname;
				datumLineno = var->lineno;

				(void) varIsArgument(frame, dbg_info->func, i, &datumName);

				break;
			}

			case PLPGSQL_DTYPE_RECFIELD:
#if (PG_VERSION_NUM < 140000)
			case PLPGSQL_DTYPE_ARRAYELEM:
#endif
#if (PG_VERSION_NUM < 110000)
			case PLPGSQL_DTYPE_EXPR:
#endif
#if (PG_VERSION_NUM <= 80400)
			case PLPGSQL_DTYPE_TRIGARG:
#endif
			{
				break;
			}
		}

		if( datumName == NULL )
			continue;

		if( strcmp( var_name, datumName ) == 0 )
		{
			if( lineNo == -1 || lineNo == datumLineno )
			{
				if( index )
					*index = i;

				return( frame->datums[i] );
			}
		}
	}

	return( NULL );
}

/*
 * ---------------------------------------------------------------------
 * print_var()
 *
 *	This function will print (that is, send to the debugger client) the
 *  type and value of the given variable.
 */
static void
print_var(const PLpgSQL_execstate *frame, const char *var_name, int lineno,
		  const PLpgSQL_var *tgt)
{
	char	     	 * extval;
	HeapTuple	       typeTup;
	Form_pg_type       typeStruct;
	FmgrInfo	       finfo_output;
	dbg_ctx 		 * dbg_info = (dbg_ctx *)frame->plugin_info;

	if( tgt->isnull )
	{
		if( dbg_info->symbols[tgt->dno].duplicate_name )
			dbg_send( "v:%s(%d):NULL\n", var_name, lineno );
		else
			dbg_send( "v:%s:NULL\n", var_name );
		return;
	}

	/* Find the output function for this data type */

	typeTup = SearchSysCache( TYPEOID, ObjectIdGetDatum( tgt->datatype->typoid ), 0, 0, 0 );

	if( !HeapTupleIsValid( typeTup ))
	{
		dbg_send( "v:%s(%d):***can't find type\n", var_name, lineno );
		return;
	}

	typeStruct = (Form_pg_type)GETSTRUCT( typeTup );

	/* Now invoke the output function to convert the variable into a null-terminated string */

	fmgr_info( typeStruct->typoutput, &finfo_output );

	extval = DatumGetCString( FunctionCall3( &finfo_output, tgt->value, ObjectIdGetDatum(typeStruct->typelem), Int32GetDatum(-1)));

	/* Send the name:value to the debugger client */

	if( dbg_info->symbols[tgt->dno].duplicate_name )
		dbg_send( "v:%s(%d):%s\n", var_name, lineno, extval );
	else
		dbg_send( "v:%s:%s\n", var_name, extval );

	pfree( extval );
	ReleaseSysCache( typeTup );
}

static void
print_row(const PLpgSQL_execstate *frame, const char *var_name, int lineno,
		  const PLpgSQL_row * tgt)
{
	/* XXX.  Shouldn't there be some code here? */
}

static void
print_rec(const PLpgSQL_execstate *frame, const char *var_name, int lineno,
		  const PLpgSQL_rec *tgt)
{
	int		attNo;

	TupleDesc	rec_tupdesc;
	HeapTuple	tuple;

#if (PG_VERSION_NUM >= 110000) && !defined(INCLUDE_PACKAGE_SUPPORT)
	if (tgt->erh == NULL ||
		ExpandedRecordIsEmpty(tgt->erh))
		return;

	rec_tupdesc = expanded_record_get_tupdesc(tgt->erh);
	tuple = expanded_record_get_tuple(tgt->erh);
#else
	if (tgt->tupdesc == NULL)
		return;

	rec_tupdesc = tgt->tupdesc;
	tuple = tgt->tup;
#endif

	for( attNo = 0; attNo < rec_tupdesc->natts; ++attNo )
	{
		char * extval = SPI_getvalue( tuple, rec_tupdesc, attNo + 1 );

#if (PG_VERSION_NUM >= 110000)
		dbg_send( "v:%s.%s:%s\n", var_name, NameStr( rec_tupdesc->attrs[attNo].attname ), extval ? extval : "NULL" );
#else
		dbg_send( "v:%s.%s:%s\n", var_name, NameStr( rec_tupdesc->attrs[attNo]->attname ), extval ? extval : "NULL" );
#endif

		if( extval )
			pfree( extval );
	}
}

static void
print_recfield(const PLpgSQL_execstate *frame, const char *var_name,
			   int lineno, const PLpgSQL_recfield *tgt)
{
	/* XXX.  Shouldn't there be some code here? */
}

static void
plpgsql_print_var(ErrorContextCallback *frame, const char *var_name,
				  int lineno)
{
	PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg;
	PLpgSQL_variable * generic = NULL;

	/* Try to find the given variable */

	if(( generic = (PLpgSQL_variable*) find_var_by_name( estate, var_name, lineno, NULL )) == NULL )
	{
		dbg_send( "v:%s(%d):Unknown variable (or not in scope)\n", var_name, lineno );
		return;
	}

	switch( generic->dtype )
	{
#if (PG_VERSION_NUM >= 110000)
		case PLPGSQL_DTYPE_PROMISE:
#endif
		case PLPGSQL_DTYPE_VAR:
			print_var( estate, var_name, lineno, (PLpgSQL_var *) generic );
			break;

		case PLPGSQL_DTYPE_ROW:
			print_row( estate, var_name, lineno, (PLpgSQL_row *) generic );
			break;

		case PLPGSQL_DTYPE_REC:
			print_rec( estate, var_name, lineno, (PLpgSQL_rec *) generic );
			break;

		case PLPGSQL_DTYPE_RECFIELD:
			print_recfield( estate, var_name, lineno, (PLpgSQL_recfield *) generic );
			break;

#if (PG_VERSION_NUM < 140000)
		case PLPGSQL_DTYPE_ARRAYELEM:
#endif
#if (PG_VERSION_NUM < 110000)
		case PLPGSQL_DTYPE_EXPR:
#endif
			/**
			 * FIXME::
			 * Hmm..  Shall we print the values for expression/array element?
			 **/
			break;
	}
}

/*
 * ---------------------------------------------------------------------
 * mark_duplicate_names()
 *
 *	In a PL/pgSQL function/procedure you can declare many variables with
 *  the same name as long as the name is unique within a scope.  The PL
 *	compiler co-mingles all variables into a single symbol table without
 *  indicating (at run-time) when a variable comes into scope.
 *
 *  When we display a variable to the user, we want to show an undecorated
 *  name unless the given variable has duplicate declarations (in nested
 *  scopes).  If we detect that a variable has duplicate declarations, we
 *	decorate the name with the line number at which each instance is
 *  declared.  This function detects duplicate names and marks duplicates
 *  in our private symbol table.
 */
static void
mark_duplicate_names(const PLpgSQL_execstate *estate, int var_no)
{
	dbg_ctx * dbg_info = (dbg_ctx *)estate->plugin_info;

	if( dbg_info->symbols[var_no].duplicate_name )
	{
		/* already detected as a duplicate name - just go home */
		return;
	}

	/*
	 * FIXME: Handle other dtypes here too - for now, we just assume
	 *		  that all other types have duplicate names
	 */

	if( estate->datums[var_no]->dtype != PLPGSQL_DTYPE_VAR )
	{
		dbg_info->symbols[var_no].duplicate_name = TRUE;
		return;
	}
	else
	{
		PLpgSQL_var * var	   = (PLpgSQL_var *)estate->datums[var_no];
		char        * var_name = var->refname;
		int			  i;

		for( i = 0; i < estate->ndatums; ++i )
		{
			if( i != var_no )
			{
				if( estate->datums[i]->dtype != PLPGSQL_DTYPE_VAR )
					continue;

				var = (PLpgSQL_var *)estate->datums[i];

				if( strcmp( var_name, var->refname ) == 0 )
				{
					dbg_info->symbols[var_no].duplicate_name  = TRUE;
					dbg_info->symbols[i].duplicate_name  = TRUE;
				}
			}
		}
	}
}

/*
 * ---------------------------------------------------------------------
 * completeFrame()
 *
 *	This function ensures that the given execution frame contains
 *	all of the information we need in order to debug it.  In particular,
 *	we create an array that extends the frame->datums[] array.
 *	We need to know which variables should be visible to the
 *	debugger client (we hide some of them by convention) and
 *	we need to figure out which names are unique and which
 *	are duplicates.
 */
static void
completeFrame(PLpgSQL_execstate *frame)
{
	dbg_ctx 		 * dbg_info = (dbg_ctx *)frame->plugin_info;
	PLpgSQL_function * func     = dbg_info->func;
	int		           i;

	if( dbg_info->symbols == NULL )
	{
		dbg_info->symbols = (var_value *) palloc( sizeof( var_value ) * func->ndatums );

		for( i = 0; i < func->ndatums; ++i )
		{
			dbg_info->symbols[i].isnull = TRUE;

			/*
			 * Note: in SPL, we hide a few variables from the debugger since
			 *       they are internally generated (that is, not declared by
			 *		 the user).  Decide whether this particular variable should
			 *       be visible to the debugger client.
			 */

			dbg_info->symbols[i].visible 		= is_datum_visible( frame->datums[i] );
			dbg_info->symbols[i].duplicate_name = FALSE;
		}

		for( i = 0; i < func->ndatums; ++i )
			mark_duplicate_names( frame, i );

		dbg_info->argNames = fetchArgNames( func, &dbg_info->argNameCount );
	}
}


/* ------------------------------------------------------------------
 * fetchArgNames()
 *
 *   This function returns the name of each argument for the given
 *   function or procedure. If the function/procedure does not have
 *	 named arguments, this function returns NULL
 *
 *	 The argument names are returned as an array of string pointers
 */
static char **
fetchArgNames(PLpgSQL_function *func, int *nameCount)
{
#if INCLUDE_PACKAGE_SUPPORT && (PG_VERSION_NUM >= 90600)
	/*
	 * Argument names are now available in func, and we anyway can't fetch from
	 * pg_proc in case of a nested function.
	 */
	*nameCount = func->fn_nallargs;
	return func->fn_argnames;
#else
	HeapTuple	tup;
	Datum		argnamesDatum;
	bool		isNull;
	Datum	   *elems;
	bool	   *nulls;
	char	  **result;
	int			i;

	if( func->fn_nargs == 0 )
		return( NULL );

	tup = SearchSysCache( PROCOID, ObjectIdGetDatum( func->fn_oid ), 0, 0, 0 );

	if( !HeapTupleIsValid( tup ))
		elog( ERROR, "cache lookup for function %u failed", func->fn_oid );

	argnamesDatum = SysCacheGetAttr( PROCOID, tup, Anum_pg_proc_proargnames, &isNull );

	if( isNull )
	{
		ReleaseSysCache( tup );
		return( NULL );
	}

	deconstruct_array( DatumGetArrayTypeP( argnamesDatum ), TEXTOID, -1, false, 'i', &elems, &nulls, nameCount );

	result = (char **) palloc( sizeof(char *) * (*nameCount));

	for( i = 0; i < (*nameCount); i++ )
		result[i] = DatumGetCString( DirectFunctionCall1( textout, elems[i] ));

	ReleaseSysCache( tup );

	return( result );
#endif
}

static char *
get_text_val(PLpgSQL_var *var, char **name, char **type)
{
	HeapTuple	       typeTup;
	Form_pg_type       typeStruct;
	FmgrInfo	       finfo_output;
	char            *  text_value = NULL;

	/* Find the output function for this data type */
	typeTup = SearchSysCache( TYPEOID, ObjectIdGetDatum( var->datatype->typoid ), 0, 0, 0 );

	if( !HeapTupleIsValid( typeTup ))
		return( NULL );

	typeStruct = (Form_pg_type)GETSTRUCT( typeTup );

	/* Now invoke the output function to convert the variable into a null-terminated string */
	fmgr_info( typeStruct->typoutput, &finfo_output );

	text_value = DatumGetCString( FunctionCall3( &finfo_output, var->value, ObjectIdGetDatum(typeStruct->typelem), Int32GetDatum(-1)));

	ReleaseSysCache( typeTup );

	if( name )
		*name = var->refname;

	if( type )
		*type = var->datatype->typname;

	return( text_value );
}

static Oid
plpgsql_get_func_oid(ErrorContextCallback *frame)
{
	PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg;
	dbg_ctx 		* dbg_info = (dbg_ctx *) estate->plugin_info;

	return dbg_info->func->fn_oid;
}

static void
dbg_startup(PLpgSQL_execstate *estate, PLpgSQL_function *func)
{
	if( func == NULL )
	{
		/*
		 * In general, this should never happen, but it seems to in the
		 * case of package constructors
		 */
		estate->plugin_info = NULL;
		return;
	}

	if( !breakpointsForFunction( func->fn_oid ) && !per_session_ctx.step_into_next_func)
	{
		estate->plugin_info = NULL;
		return;
	}
	initialize_plugin_info(estate, func);
}

static void
initialize_plugin_info(PLpgSQL_execstate *estate, PLpgSQL_function *func)
{
	dbg_ctx * dbg_info;

	/* Allocate a context structure and record the address in the estate */
	estate->plugin_info = dbg_info = (dbg_ctx *) palloc( sizeof( dbg_ctx ));

	/*
	 * As soon as we hit the first statement, we'll allocate space for each
	 * local variable. For now, we set symbols to NULL so we know to report all
	 * variables the next time we stop...
	 */
	dbg_info->symbols  		 = NULL;
	dbg_info->stepping 		 = FALSE;
	dbg_info->func     		 = func;

	/*
	 * The PL interpreter filled in two member of our plugin_funcs
	 * structure for us - we compare error_callback to the callback
	 * in the error_context_stack to make sure that we only deal with
	 * PL/pgSQL (or SPL) stack frames (hokey, but it works).  We use
	 * assign_expr when we need to deposit a value in variable.
	 */
	dbg_info->error_callback = plugin_funcs.error_callback;
	dbg_info->assign_expr    = plugin_funcs.assign_expr;

#if INCLUDE_PACKAGE_SUPPORT
	/*
	 * Look up the package this function belongs to.
	 *
	 * Inline code blocks have invalid fn_oid. They never belong to packages.
	 */
	if (OidIsValid(dbg_info->func->fn_oid))
	{
		/*
		 * Find the namespace in which this function/procedure is defined
		 */
		HeapTuple	htup;
		Oid			namespaceOid;

		htup = SearchSysCache(PROCOID, ObjectIdGetDatum(dbg_info->func->fn_oid), 0, 0, 0);

		if (!HeapTupleIsValid(htup))
			elog(ERROR, "cache lookup failed for procedure %d", dbg_info->func->fn_oid);

		namespaceOid = ((Form_pg_proc)GETSTRUCT(htup))->pronamespace;

		ReleaseSysCache(htup);

		/*
		 * Now figure out if this namespace is a package or a schema
		 *
		 * NOTE: we could read the pg_namespace tuple and check pg_namespace.nspparent,
		 *		 but it's faster to just search for the namespaceOid in the global
		 *		 package array instead; we have to do that anyway to find the package
		 */

		dbg_info->package = plugin_funcs.get_package( namespaceOid );
	}
	else
		dbg_info->package = InvalidOid;
#endif
}


/*
 * ---------------------------------------------------------------------
 * plpgsql_do_deposit()
 *
 *  This function handles the 'deposit' feature - that is, this function
 *  sets a given PL variable to a new value, supplied by the client.
 *
 *	do_deposit() is called when you type a new value into a variable in
 *  the local-variables window.
 *
 *  NOTE: For the convenience of the user, we first assume that the
 *		  provided value is an expression.  If it doesn't evaluate,
 *		  we convert the value into a literal by surrounding it with
 *		  single quotes.  That may be surprising if you happen to make
 *		  a typo, but it will "do the right thing" in most cases.
 *
 * Returns true on success, false on failure.
 */
static bool
plpgsql_do_deposit(ErrorContextCallback *frame, const char *var_name,
				   int lineno, const char *value)
{
	PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg;
	dbg_ctx       *dbg_info   = estate->plugin_info;
	PLpgSQL_datum *target;
	char      	  *select;
	PLpgSQL_expr  *expr;
	MemoryContext  curContext = CurrentMemoryContext;
	ResourceOwner  curOwner   = CurrentResourceOwner;
	bool		retval = false;

	target = find_datum_by_name(estate, var_name, lineno, NULL);
	if (!target)
		return false;

	/*
	 * Now build a SELECT statement that returns the requested value
	 *
	 * NOTE: we allocate 2 extra bytes for quoting just in case we
	 *       need to later (see the retry logic below)
	 */

	select = palloc( strlen( "SELECT " ) + strlen( value ) + 2 + 1 );

	sprintf( select, "SELECT %s", value );

	/*
	 * Note: we must create a dynamically allocated PLpgSQL_expr here - we
	 *       can't create one on the stack because exec_assign_expr()
	 *       links this expression into a list (active_simple_exprs) and
	 *       this expression must survive until the end of the current
	 *	     transaction so we don't free it out from under spl_plpgsql_xact_cb()
	 */

	expr = (PLpgSQL_expr *) palloc0( sizeof( *expr ));

#if (PG_VERSION_NUM < 110000)
	expr->dtype			= PLPGSQL_DTYPE_EXPR;
	expr->dno			= -1;
#endif
	expr->query			= select;
	expr->plan			= NULL;
#if (PG_VERSION_NUM <= 80400)
	expr->plan_argtypes		= NULL;
	expr->nparams			= 0;
#endif
	expr->expr_simple_expr		= NULL;

	BeginInternalSubTransaction( NULL );

	MemoryContextSwitchTo( curContext );

	PG_TRY();
	{
		if( target )
			dbg_info->assign_expr( estate, target, expr );

		/* Commit the inner transaction, return to outer xact context */
		ReleaseCurrentSubTransaction();
		MemoryContextSwitchTo( curContext );
		CurrentResourceOwner = curOwner;

		SPI_restore_connection();

		/* That worked, don't try again */
		retval = true;
	}
	PG_CATCH();
	{
		MemoryContextSwitchTo( curContext );

		FlushErrorState();

		/* Abort the inner transaction */
		RollbackAndReleaseCurrentSubTransaction();
		MemoryContextSwitchTo( curContext );
		CurrentResourceOwner = curOwner;

		SPI_restore_connection();

		/* That failed - try again as a literal */
		retval = false;
	}
	PG_END_TRY();

	/*
	 * If the given value is not a valid expression, try converting
	 * the value into a literal by sinqle-quoting it.
	 */

	if (!retval)
	{
		sprintf( select, "SELECT '%s'", value );

#if (PG_VERSION_NUM < 110000)
		expr->dtype		= PLPGSQL_DTYPE_EXPR;
		expr->dno		= -1;
#endif
		expr->query		= select;
		expr->plan		= NULL;
		expr->expr_simple_expr	= NULL;
#if (PG_VERSION_NUM <= 80400)
		expr->plan_argtypes	= NULL;
		expr->nparams		= 0;
#endif

		BeginInternalSubTransaction( NULL );

		MemoryContextSwitchTo( curContext );

		PG_TRY();
		{
			if( target )
				dbg_info->assign_expr( estate, target, expr );

			/* Commit the inner transaction, return to outer xact context */
			ReleaseCurrentSubTransaction();
			MemoryContextSwitchTo( curContext );
			CurrentResourceOwner = curOwner;

			SPI_restore_connection();

			retval = true;
		}
		PG_CATCH();
		{
			MemoryContextSwitchTo( curContext );

			FlushErrorState();

			/* Abort the inner transaction */
			RollbackAndReleaseCurrentSubTransaction();
			MemoryContextSwitchTo( curContext );
			CurrentResourceOwner = curOwner;

			SPI_restore_connection();

			retval = false;
		}
		PG_END_TRY();
	}

	pfree( select );

	return retval;
}

/*
 * ---------------------------------------------------------------------
 * is_datum_visible()
 *
 *	This function determines whether the given datum is 'visible' to the
 *  debugger client.  We want to hide a few undocumented/internally
 *  generated variables from the user - this is the function that hides
 *  them.  We set a flag in the symbols entry for this datum
 *  to indicate whether this variable is hidden or visible - that way,
 *  only have to do the expensive stuff once per invocation.
 */
static bool
is_datum_visible(PLpgSQL_datum *datum)
{
	static const char * hidden_variables[] =
	{
		"found",
		"rowcount",
		"sqlcode",
		"sqlerrm",
		"_found",
		"_rowcount",
	};

	/*
	 * All of the hidden variables are scalars at the moment so
	 * assume that anything else is visible regardless of name
	 */

	if( datum->dtype != PLPGSQL_DTYPE_VAR )
		return( TRUE );
	else
	{
		PLpgSQL_var * var = (PLpgSQL_var *)datum;
		int			  i;

		for( i = 0; i < sizeof( hidden_variables ) / sizeof( hidden_variables[0] ); ++i )
		{
			if( strcmp( var->refname, hidden_variables[i] ) == 0 )
			{
				/*
				 * We found this variable in our list of hidden names -
				 * this variable is *not* visible
				 */

				return( FALSE );
			}
		}

		/*
		 * The SPL pre-processor generates a few variable names for
		 * DMBS.PUTLINE statements - we want to hide those variables too.
		 * The generated variables are of the form 'txtnnn...' where
		 * 'nnn...' is a sequence of one or more digits.
		 */

		if( strncmp( var->refname, "txt", 3 ) == 0 )
		{
			int	   i;

			/*
			 * Starts with 'txt' - see if the rest of the string is composed
			 * entirely of digits
			 */

			for( i = 3; var->refname[i] != '\0'; ++i )
			{
				if( var->refname[i] < '0' || var->refname[i] > '9' )
					return( TRUE );
			}

			return( FALSE );
		}

		return( TRUE );
	}
}

/*
 * ---------------------------------------------------------------------
 * is_var_visible()
 *
 *	This function determines whether the given variable is 'visible' to the
 *  debugger client. We hide some variables from the user (see the
 *  is_datum_visible() function for more info).  This function is quick -
 *  we do the slow work in is_datum_visible() and simply check the results
 *  here.
 */
static bool
is_var_visible(PLpgSQL_execstate *frame, int var_no)
{
	dbg_ctx * dbg_info = (dbg_ctx *)frame->plugin_info;

	if (dbg_info->symbols == NULL)
		completeFrame(frame);

	return( dbg_info->symbols[var_no].visible );
}


/*
 * plpgsql_send_cur_line()
 *
 * This function sends the current position to the debugger client. We
 * send the function's OID, xmin, cmin, and the current line number
 * (we're telling the client which line of code we're about to execute).
 */
static void
plpgsql_send_cur_line(ErrorContextCallback *frame)
{
	PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg;
	PLpgSQL_stmt *stmt = estate->err_stmt;
	dbg_ctx	   *dbg_info = (dbg_ctx *) estate->plugin_info;
	PLpgSQL_function *func = dbg_info->func;

	dbg_send( "%d:%d:%s",
			  func->fn_oid,
			  stmt->lineno+1,
#if (PG_VERSION_NUM >= 90200)
			  func->fn_signature
#else
			  func->fn_name
#endif
		);
}

/*
 * ---------------------------------------------------------------------
 * isFirstStmt()
 *
 *	Returns true if the given statement is the first statement in the
 *  given function.
 */
static bool
isFirstStmt(PLpgSQL_stmt *stmt, PLpgSQL_function *func)
{
	if( stmt == linitial( func->action->body ))
		return( TRUE );
	else
		return( FALSE );
}

/*
 * ---------------------------------------------------------------------
 * dbg_newstmt()
 *
 *	The PL/pgSQL executor calls plpgsql_dbg_newstmt() just before executing each
 *	statement.
 *
 *	This function is the heart of the debugger.  If you're single-stepping,
 *	or you hit a breakpoint, plpgsql_dbg_newstmt() sends a message to the debugger
 *  client indicating the current line and then waits for a command from
 *	the user.
 *
 *	NOTE: it is very important that this function should impose negligible
 *	      overhead when a debugger client is *not* attached.  In other words
 *		  if you're running PL/pgSQL code without a debugger, you notice no
 *		  performance penalty.
 */
static void
dbg_newstmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
{
	PLpgSQL_execstate * frame = estate;

	/*
	 * If there's no debugger attached, go home as quickly as possible.
	 */
	if( frame->plugin_info == NULL )
		return;
	else
	{
		dbg_ctx 		  * dbg_info = (dbg_ctx *)frame->plugin_info;
		Breakpoint		  * breakpoint = NULL;
		eBreakpointScope	breakpointScope = 0;

		/*
		 * The PL compiler marks certain statements as 'invisible' to the
		 * debugger. In particular, the compiler may generate statements
		 * that do not appear in the source code. Such a statement is
		 * marked with a line number of -1: if we're looking at an invisible
		 * statement, just return to the caller.
		 */

		if( stmt->lineno == -1 )
			return;

		/*
		 * Now set up an error handler context so we can intercept any
		 * networking errors (errors communicating with the proxy).
		 */

		if( sigsetjmp( client_lost.m_savepoint, 1 ) != 0 )
		{
			/*
			 *  The connection to the debugger client has slammed shut -
			 *	just pretend like there's no debugger attached and return
			 *
			 *	NOTE: we no longer have a connection to the debugger proxy -
			 *		  that means that we cannot interact with the proxy, we
			 *		  can't wait for another command, nothing.  We let the
			 *		  executor continue execution - anything else will hang
			 *		  this backend, waiting for a debugger command that will
			 *		  never arrive.
			 *
			 *		  If, however, we hit a breakpoint again, we'll stop and
			 *		  wait for another debugger proxy to connect to us.  If
			 *		  that's not the behavior you're looking for, you can
			 *		  drop the breakpoint, or call free_function_breakpoints()
			 *		  here to get rid of all breakpoints in this backend.
			 */
			per_session_ctx.client_w = 0; 		/* No client connection */
			dbg_info->stepping 		 = FALSE; 	/* No longer stepping   */
		}

		if(( dbg_info->stepping ) || breakAtThisLine( &breakpoint, &breakpointScope, dbg_info->func->fn_oid, isFirstStmt( stmt, dbg_info->func ) ? -1 : stmt->lineno ))
			dbg_info->stepping = TRUE;
		else
			return;

		per_session_ctx.step_into_next_func = FALSE;

		/* We found a breakpoint for this function (or we're stepping into) */
		/* Make contact with the debugger client */

		if( !attach_to_proxy( breakpoint ))
		{
			/*
			 * Can't attach to the proxy, maybe we found a stale breakpoint?
			 * That can happen if you set a global breakpoint on a function,
			 * invoke that function from a client application, debug the target
			 * kill the debugger client, and then re-invoke the function from
			 * the same client application - we will find the stale global
			 * breakpoint on the second invocation.
			 *
			 * We want to remove that breakpoint so that we don't keep trying
			 * to attach to a phantom proxy process.
			 */
			if( breakpoint )
				BreakpointDelete( breakpointScope, &(breakpoint->key));

			/*
			 * In any case, if we don't have a proxy to work with, we can't
			 * do any debugging so give up.
			 */
			pfree( frame->plugin_info );
			frame->plugin_info       = NULL; /* No debugger context  */
			per_session_ctx.client_w = 0; /* No client connection */

			return;
		}

		if( stmt->cmd_type == PLPGSQL_STMT_BLOCK )
			return;

		/*
		 * The PL/pgSQL compiler inserts an automatic RETURN statement at the
		 * end of each function (unless the last statement in the function is
		 * already a RETURN). If we run into that statement, we don't really
		 * want to wait for the user to STEP across it. Remember, the user won't
		 * see the RETURN statement in the source-code listing for his function.
		 *
		 * Fortunately, the automatic RETURN statement has a line-number of 0
		 * so it's easy to spot.
		 */
		if( stmt->lineno == 0 )
			return;

		/*
		 * If we're in step mode, tell the debugger client, read a command from the client and
		 * execute the command
		 */

		if( dbg_info->stepping )
		{
			/*
			 * Make sure that we have all of the debug info that we need in this stack frame
			 */
			completeFrame( frame );

			/*
			 * We're in single-step mode (or at a breakpoint)
			 * send the current line number to the debugger client and report any
			 * variable modifications
			 */

			if (!plugin_debugger_main_loop())
				dbg_info->stepping = FALSE;
		}
	}
}

/* ---------------------------------------------------------------------
 *	datumIsNull()
 *
 *	determine whether datum is NULL or not.
 *	TODO: consider datatypes other than PLPGSQL_DTYPE_VAR as well
 */
static bool
datumIsNull(PLpgSQL_datum *datum)
{
	switch (datum->dtype)
	{
		case PLPGSQL_DTYPE_VAR:
		{
			PLpgSQL_var *var = (PLpgSQL_var *) datum;

			if (var->isnull)
				return true;
		}
		break;

		/* other data types are not currently handled, we just return true */
		case PLPGSQL_DTYPE_REC:
		case PLPGSQL_DTYPE_ROW:
			return true;

		default:
			return true;
	}

	return false;
}
