Newer
Older
Heikki Linnakangas
committed
/**********************************************************************
* plpgsql_debugger.c - Debugger for the PL/pgSQL procedural language
*
* Copyright (c) 2004-2018 EnterpriseDB Corporation. All Rights Reserved.
Heikki Linnakangas
committed
*
* Licensed under the Artistic License v2.0, see
* https://opensource.org/licenses/artistic-license-2.0
Heikki Linnakangas
committed
* 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
Heikki Linnakangas
committed
/*
* We use a var_value structure to record a little extra information about
Heikki Linnakangas
committed
*/
typedef struct
{
bool isnull; /* TRUE -> this variable IS NULL */
Heikki Linnakangas
committed
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 */
Heikki Linnakangas
committed
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 );
Heikki Linnakangas
committed
#if INCLUDE_PACKAGE_SUPPORT
static const char * plugin_name = "spl_plugin";
#else
static const char * plugin_name = "PLpgSQL_plugin";
#endif
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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
};
Heikki Linnakangas
committed
/* Install this module as an PL/pgSQL instrumentation plugin */
static void
plpgsql_debugger_init(void)
Heikki Linnakangas
committed
{
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)
Heikki Linnakangas
committed
{
return (frame->callback == plugin_funcs.error_callback);
}
Heikki Linnakangas
committed
/*
* plpgsql_send_stack_frame()
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
* 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 );
Heikki Linnakangas
committed
}
/*
* 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)
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#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
Heikki Linnakangas
committed
/*
* 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)
Heikki Linnakangas
committed
{
PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg;
dbg_ctx * dbg_info = (dbg_ctx *) estate->plugin_info;
int i;
Heikki Linnakangas
committed
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);
Heikki Linnakangas
committed
if( datumIsNull((PLpgSQL_datum *)var ))
val = "NULL";
else
val = get_text_val( var, NULL, NULL );
Heikki Linnakangas
committed
dbg_send( "%s:%c:%d:%c:%c:%c:%d:%s",
isArg ? 'A' : 'L',
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:
}
}
}
Heikki Linnakangas
committed
#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;
Heikki Linnakangas
committed
for( varIndex = 0; varIndex < package->ndatums; ++varIndex )
{
PLpgSQL_datum * datum = package->datums[varIndex];
Heikki Linnakangas
committed
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",
'P', /* variable class - P means package var */
'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 */
}
Heikki Linnakangas
committed
static void
plpgsql_select_frame(ErrorContextCallback *frame)
{
PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg;
Heikki Linnakangas
committed
/*
* 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
}
Heikki Linnakangas
committed
}
/*
* ---------------------------------------------------------------------
* find_var_by_name()
*
* This function returns the PLpgSQL_var pointer that corresponds to
Heikki Linnakangas
committed
* 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
Heikki Linnakangas
committed
* 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)
Heikki Linnakangas
committed
{
dbg_ctx * dbg_info = (dbg_ctx *)estate->plugin_info;
PLpgSQL_function * func = dbg_info->func;
int i;
Heikki Linnakangas
committed
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))
Heikki Linnakangas
committed
continue;
Heikki Linnakangas
committed
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;
}
Heikki Linnakangas
committed
return( var );
}
}
/* We can't find the variable named by the caller - return NULL */
Heikki Linnakangas
committed
}
static PLpgSQL_datum *
find_datum_by_name(const PLpgSQL_execstate *frame, const char *var_name,
int lineNo, int *index)
Heikki Linnakangas
committed
{
dbg_ctx * dbg_info = (dbg_ctx *)frame->plugin_info;
int i;
Heikki Linnakangas
committed
#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
Heikki Linnakangas
committed
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
Heikki Linnakangas
committed
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);
Heikki Linnakangas
committed
break;
}
case PLPGSQL_DTYPE_RECFIELD:
Heikki Linnakangas
committed
case PLPGSQL_DTYPE_ARRAYELEM:
Heikki Linnakangas
committed
case PLPGSQL_DTYPE_EXPR:
Heikki Linnakangas
committed
#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 )
Heikki Linnakangas
committed
if( index )
*index = i;
return( frame->datums[i] );
}
}
}
return( NULL );
}
/*
* ---------------------------------------------------------------------
Heikki Linnakangas
committed
*
* This function will print (that is, send to the debugger client) the
Heikki Linnakangas
committed
* 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)
Heikki Linnakangas
committed
{
char * extval;
HeapTuple typeTup;
Form_pg_type typeStruct;
FmgrInfo finfo_output;
dbg_ctx * dbg_info = (dbg_ctx *)frame->plugin_info;
if( tgt->isnull )
{
Heikki Linnakangas
committed
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;
Heikki Linnakangas
committed
/* Find the output function for this data type */
Heikki Linnakangas
committed
typeTup = SearchSysCache( TYPEOID, ObjectIdGetDatum( tgt->datatype->typoid ), 0, 0, 0 );
Heikki Linnakangas
committed
Heikki Linnakangas
committed
dbg_send( "v:%s(%d):***can't find type\n", var_name, lineno );
return;
typeStruct = (Form_pg_type)GETSTRUCT( typeTup );
Heikki Linnakangas
committed
/* Now invoke the output function to convert the variable into a null-terminated string */
fmgr_info( typeStruct->typoutput, &finfo_output );
Heikki Linnakangas
committed
extval = DatumGetCString( FunctionCall3( &finfo_output, tgt->value, ObjectIdGetDatum(typeStruct->typelem), Int32GetDatum(-1)));
Heikki Linnakangas
committed
/* 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 );
Heikki Linnakangas
committed
}
static void
print_row(const PLpgSQL_execstate *frame, const char *var_name, int lineno,
const PLpgSQL_row * tgt)
Heikki Linnakangas
committed
{
Heikki Linnakangas
committed
}
static void
print_rec(const PLpgSQL_execstate *frame, const char *var_name, int lineno,
const PLpgSQL_rec *tgt)
Heikki Linnakangas
committed
{
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
Heikki Linnakangas
committed
if (tgt->tupdesc == NULL)
return;
rec_tupdesc = tgt->tupdesc;
tuple = tgt->tup;
#endif
for( attNo = 0; attNo < rec_tupdesc->natts; ++attNo )
Heikki Linnakangas
committed
{
char * extval = SPI_getvalue( tuple, rec_tupdesc, attNo + 1 );
Heikki Linnakangas
committed
#if (PG_VERSION_NUM >= 110000)
dbg_send( "v:%s.%s:%s\n", var_name, NameStr( rec_tupdesc->attrs[attNo].attname ), extval ? extval : "NULL" );
dbg_send( "v:%s.%s:%s\n", var_name, NameStr( rec_tupdesc->attrs[attNo]->attname ), extval ? extval : "NULL" );
Heikki Linnakangas
committed
if( extval )
pfree( extval );
}
}
static void
print_recfield(const PLpgSQL_execstate *frame, const char *var_name,
int lineno, const PLpgSQL_recfield *tgt)
Heikki Linnakangas
committed
{
Heikki Linnakangas
committed
}
static void
plpgsql_print_var(ErrorContextCallback *frame, const char *var_name,
int lineno)
Heikki Linnakangas
committed
{
PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg;
Heikki Linnakangas
committed
PLpgSQL_variable * generic = NULL;
/* Try to find the given variable */
if(( generic = (PLpgSQL_variable*) find_var_by_name( estate, var_name, lineno, NULL )) == NULL )
{
Heikki Linnakangas
committed
dbg_send( "v:%s(%d):Unknown variable (or not in scope)\n", var_name, lineno );
return;
Heikki Linnakangas
committed
switch( generic->dtype )
{
#if (PG_VERSION_NUM >= 110000)
case PLPGSQL_DTYPE_PROMISE:
#endif
Heikki Linnakangas
committed
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;
/**
* FIXME::
* Hmm.. Shall we print the values for expression/array element?
**/
break;
Heikki Linnakangas
committed
}
}
/*
* ---------------------------------------------------------------------
* 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.
Heikki Linnakangas
committed
*
* When we display a variable to the user, we want to show an undecorated
* name unless the given variable has duplicate declarations (in nested
Heikki Linnakangas
committed
* scopes). If we detect that a variable has duplicate declarations, we
* decorate the name with the line number at which each instance is
Heikki Linnakangas
committed
* 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)
Heikki Linnakangas
committed
{
dbg_ctx * dbg_info = (dbg_ctx *)estate->plugin_info;
Heikki Linnakangas
committed
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
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;
Heikki Linnakangas
committed
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
Heikki Linnakangas
committed
* are duplicates.
*/
static void
completeFrame(PLpgSQL_execstate *frame)
Heikki Linnakangas
committed
{
dbg_ctx * dbg_info = (dbg_ctx *)frame->plugin_info;
PLpgSQL_function * func = dbg_info->func;
int i;
Heikki Linnakangas
committed
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
Heikki Linnakangas
committed
* 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
Heikki Linnakangas
committed
* 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)
Heikki Linnakangas
committed
{
#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
Heikki Linnakangas
committed
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 );
Heikki Linnakangas
committed
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 );
Heikki Linnakangas
committed
}
static char *
get_text_val(PLpgSQL_var *var, char **name, char **type)
Heikki Linnakangas
committed
{
HeapTuple typeTup;
Form_pg_type typeStruct;
FmgrInfo finfo_output;
Heikki Linnakangas
committed
char * text_value = NULL;
/* Find the output function for this data type */
typeTup = SearchSysCache( TYPEOID, ObjectIdGetDatum( var->datatype->typoid ), 0, 0, 0 );
Heikki Linnakangas
committed
Heikki Linnakangas
committed
return( NULL );
typeStruct = (Form_pg_type)GETSTRUCT( typeTup );
Heikki Linnakangas
committed
/* Now invoke the output function to convert the variable into a null-terminated string */
fmgr_info( typeStruct->typoutput, &finfo_output );
Heikki Linnakangas
committed
text_value = DatumGetCString( FunctionCall3( &finfo_output, var->value, ObjectIdGetDatum(typeStruct->typelem), Int32GetDatum(-1)));
Heikki Linnakangas
committed
ReleaseSysCache( typeTup );
if( name )
*name = var->refname;
if( type )
*type = var->datatype->typname;
return( text_value );
}
static Oid
plpgsql_get_func_oid(ErrorContextCallback *frame)
Heikki Linnakangas
committed
{
PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg;
dbg_ctx * dbg_info = (dbg_ctx *) estate->plugin_info;
Heikki Linnakangas
committed
return dbg_info->func->fn_oid;
Heikki Linnakangas
committed
}
static void
dbg_startup(PLpgSQL_execstate *estate, PLpgSQL_function *func)
Heikki Linnakangas
committed
{
if( func == NULL )
{
/*
* In general, this should never happen, but it seems to in the
Heikki Linnakangas
committed
* 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)
Heikki Linnakangas
committed
{
Heikki Linnakangas
committed
/* Allocate a context structure and record the address in the estate */
estate->plugin_info = dbg_info = (dbg_ctx *) palloc( sizeof( dbg_ctx ));
Heikki Linnakangas
committed
Heikki Linnakangas
committed
* 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;
Heikki Linnakangas
committed
/*
* 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