Skip to content
Snippets Groups Projects
plpgsql_debugger.c 40.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • /**********************************************************************
     * 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.
    
    	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);
    }
    
     * 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.
     */
    
    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,
    
    							  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 : "" );
    
    Ashesh Vashi's avatar
    Ashesh Vashi committed
    				case PLPGSQL_DTYPE_ROW:
    				case PLPGSQL_DTYPE_REC:
    				case PLPGSQL_DTYPE_RECFIELD:
    
    #if (PG_VERSION_NUM < 140000)
    
    Ashesh Vashi's avatar
    Ashesh Vashi committed
    				case PLPGSQL_DTYPE_ARRAYELEM:
    
    #endif
    
    #if (PG_VERSION_NUM < 110000)
    
    Ashesh Vashi's avatar
    Ashesh Vashi committed
    				case PLPGSQL_DTYPE_EXPR:
    
    #endif
    
    Ashesh Vashi's avatar
    Ashesh Vashi committed
    				{
    					/* 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,
    
    							  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[]
     */
    
    
    Robert Haas's avatar
    Robert Haas committed
    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;
    
    	{
    		PLpgSQL_var * var = (PLpgSQL_var *) estate->datums[i];
    		size_t 		  len = strlen(var->refname);
    
    		if(len != strlen(var_name))
    
    		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 );
    
    Robert Haas's avatar
    Robert Haas committed
    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);
    
    #if (PG_VERSION_NUM < 140000)
    
    #endif
    
    #if (PG_VERSION_NUM < 110000)
    
    #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 );
    }
    
    /*
     * ---------------------------------------------------------------------
    
    Robert Haas's avatar
    Robert Haas committed
     * print_var()
    
     *	This function will print (that is, send to the debugger client) the
    
    Robert Haas's avatar
    Robert Haas committed
    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 );
    
    Robert Haas's avatar
    Robert Haas committed
    static void
    print_row(const PLpgSQL_execstate *frame, const char *var_name, int lineno,
    		  const PLpgSQL_row * tgt)
    
    Robert Haas's avatar
    Robert Haas committed
    	/* XXX.  Shouldn't there be some code here? */
    
    Robert Haas's avatar
    Robert Haas committed
    static void
    print_rec(const PLpgSQL_execstate *frame, const char *var_name, int lineno,
    		  const PLpgSQL_rec *tgt)
    
    	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
    
    	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" );
    
    Ashesh Vashi's avatar
    Ashesh Vashi committed
    #else
    
    		dbg_send( "v:%s.%s:%s\n", var_name, NameStr( rec_tupdesc->attrs[attNo]->attname ), extval ? extval : "NULL" );
    
    Ashesh Vashi's avatar
    Ashesh Vashi committed
    #endif
    
    Robert Haas's avatar
    Robert Haas committed
    static void
    print_recfield(const PLpgSQL_execstate *frame, const char *var_name,
    			   int lineno, const PLpgSQL_recfield *tgt)
    
    Robert Haas's avatar
    Robert Haas committed
    	/* XXX.  Shouldn't there be some code here? */
    
    Robert Haas's avatar
    Robert Haas committed
    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;
    
    #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)
    
    Ashesh Vashi's avatar
    Ashesh Vashi committed
    		case PLPGSQL_DTYPE_ARRAYELEM:
    
    #endif
    
    #if (PG_VERSION_NUM < 110000)
    
    Ashesh Vashi's avatar
    Ashesh Vashi committed
    		case PLPGSQL_DTYPE_EXPR:
    
    #endif
    
    Ashesh Vashi's avatar
    Ashesh Vashi committed
    			/**
    			 * 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.
     */
    
    Robert Haas's avatar
    Robert Haas committed
    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
    
    Robert Haas's avatar
    Robert Haas committed
    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
     */
    
    Robert Haas's avatar
    Robert Haas committed
    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
    
    Robert Haas's avatar
    Robert Haas committed
    static char *
    get_text_val(PLpgSQL_var *var, char **name, char **type)
    
    	Form_pg_type       typeStruct;
    	FmgrInfo	       finfo_output;
    
    	/* Find the output function for this data type */
    	typeTup = SearchSysCache( TYPEOID, ObjectIdGetDatum( var->datatype->typoid ), 0, 0, 0 );
    
    	if( !HeapTupleIsValid( typeTup ))
    
    	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;
    
    Robert Haas's avatar
    Robert Haas committed
    static void
    dbg_startup(PLpgSQL_execstate *estate, PLpgSQL_function *func)
    
    		/*
    		 * 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);
    }
    
    
    Robert Haas's avatar
    Robert Haas committed
    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