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
* 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
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
/*
* 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;
Heikki Linnakangas
committed
#endif
}
/*
* ---------------------------------------------------------------------
Heikki Linnakangas
committed
*
* 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
Heikki Linnakangas
committed
* the local-variables window.
*
* NOTE: For the convenience of the user, we first assume that the
Heikki Linnakangas
committed
* 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.
Heikki Linnakangas
committed
*/
static bool
plpgsql_do_deposit(ErrorContextCallback *frame, const char *var_name,
int lineno, const char *value)
Heikki Linnakangas
committed
{
PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg;
dbg_ctx *dbg_info = estate->plugin_info;
PLpgSQL_datum *target;
Heikki Linnakangas
committed
char *select;
PLpgSQL_expr *expr;
MemoryContext curContext = CurrentMemoryContext;
ResourceOwner curOwner = CurrentResourceOwner;
bool retval = false;
Heikki Linnakangas
committed
target = find_datum_by_name(estate, var_name, lineno, NULL);
return false;
Heikki Linnakangas
committed
/*
* 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
Heikki Linnakangas
committed
* 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
Heikki Linnakangas
committed
* transaction so we don't free it out from under spl_plpgsql_xact_cb()
*/
expr = (PLpgSQL_expr *) palloc0( sizeof( *expr ));
Heikki Linnakangas
committed
#if (PG_VERSION_NUM < 110000)
expr->dtype = PLPGSQL_DTYPE_EXPR;
expr->dno = -1;
#endif
expr->query = select;
expr->plan = NULL;
Heikki Linnakangas
committed
#if (PG_VERSION_NUM <= 80400)
expr->plan_argtypes = NULL;
expr->nparams = 0;
Heikki Linnakangas
committed
#endif
Heikki Linnakangas
committed
BeginInternalSubTransaction( NULL );
MemoryContextSwitchTo( curContext );
PG_TRY();
{
if( target )
dbg_info->assign_expr( estate, target, expr );
Heikki Linnakangas
committed
/* Commit the inner transaction, return to outer xact context */
ReleaseCurrentSubTransaction();
MemoryContextSwitchTo( curContext );
CurrentResourceOwner = curOwner;
/* That worked, don't try again */
retval = true;
Heikki Linnakangas
committed
}
PG_CATCH();
Heikki Linnakangas
committed
MemoryContextSwitchTo( curContext );
FlushErrorState();
/* Abort the inner transaction */
RollbackAndReleaseCurrentSubTransaction();
MemoryContextSwitchTo( curContext );
CurrentResourceOwner = curOwner;
/* That failed - try again as a literal */
retval = false;
Heikki Linnakangas
committed
}
PG_END_TRY();
Heikki Linnakangas
committed
* If the given value is not a valid expression, try converting
* the value into a literal by sinqle-quoting it.
*/
if (!retval)
Heikki Linnakangas
committed
{
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;
Heikki Linnakangas
committed
#if (PG_VERSION_NUM <= 80400)
expr->plan_argtypes = NULL;
expr->nparams = 0;
Heikki Linnakangas
committed
#endif
BeginInternalSubTransaction( NULL );
MemoryContextSwitchTo( curContext );
PG_TRY();
{
if( target )
dbg_info->assign_expr( estate, target, expr );
Heikki Linnakangas
committed
/* Commit the inner transaction, return to outer xact context */
ReleaseCurrentSubTransaction();
MemoryContextSwitchTo( curContext );
CurrentResourceOwner = curOwner;
retval = true;
Heikki Linnakangas
committed
}
PG_CATCH();
Heikki Linnakangas
committed
MemoryContextSwitchTo( curContext );
FlushErrorState();
/* Abort the inner transaction */
RollbackAndReleaseCurrentSubTransaction();
MemoryContextSwitchTo( curContext );
CurrentResourceOwner = curOwner;
retval = false;
Heikki Linnakangas
committed
}
PG_END_TRY();
}
pfree( select );
return retval;
Heikki Linnakangas
committed
}
/*
* ---------------------------------------------------------------------
* is_datum_visible()
*
* This function determines whether the given datum is 'visible' to the
* debugger client. We want to hide a few undocumented/internally
Heikki Linnakangas
committed
* 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)
Heikki Linnakangas
committed
{
Heikki Linnakangas
committed
{
"found",
"rowcount",
"sqlcode",
"sqlerrm",
"_found",
"_rowcount",
};
/*
* All of the hidden variables are scalars at the moment so
Heikki Linnakangas
committed
* assume that anything else is visible regardless of name
*/
Heikki Linnakangas
committed
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
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
Heikki Linnakangas
committed
* DMBS.PUTLINE statements - we want to hide those variables too.
* The generated variables are of the form 'txtnnn...' where
Heikki Linnakangas
committed
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
* '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 -
Heikki Linnakangas
committed
* 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)
Heikki Linnakangas
committed
{
dbg_ctx * dbg_info = (dbg_ctx *)frame->plugin_info;
Heikki Linnakangas
committed
if (dbg_info->symbols == NULL)
completeFrame(frame);
return( dbg_info->symbols[var_no].visible );
}
/*
* plpgsql_send_cur_line()
Heikki Linnakangas
committed
*
* 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).
Heikki Linnakangas
committed
*/
static void
plpgsql_send_cur_line(ErrorContextCallback *frame)
Heikki Linnakangas
committed
{
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;
Heikki Linnakangas
committed
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
Heikki Linnakangas
committed
* given function.
*/
static bool
isFirstStmt(PLpgSQL_stmt *stmt, PLpgSQL_function *func)
Heikki Linnakangas
committed
{
if( stmt == linitial( func->action->body ))
return( TRUE );
else
return( FALSE );
}
Heikki Linnakangas
committed
/*
* ---------------------------------------------------------------------
* dbg_newstmt()
*
* The PL/pgSQL executor calls plpgsql_dbg_newstmt() just before executing each
Heikki Linnakangas
committed
*
* 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
Heikki Linnakangas
committed
* the user.
*
* NOTE: it is very important that this function should impose negligible
Heikki Linnakangas
committed
* 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)
Heikki Linnakangas
committed
{
PLpgSQL_execstate * frame = estate;
Heikki Linnakangas
committed
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
* 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 -
Heikki Linnakangas
committed
* 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
Heikki Linnakangas
committed
* 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 ))
{
Heikki Linnakangas
committed
* 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.
Heikki Linnakangas
committed
*/
if( breakpoint )
BreakpointDelete( breakpointScope, &(breakpoint->key));
/*
* In any case, if we don't have a proxy to work with, we can't
Heikki Linnakangas
committed
* 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
Heikki Linnakangas
committed
* 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
Heikki Linnakangas
committed
* 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
Heikki Linnakangas
committed
* 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)
Heikki Linnakangas
committed
* send the current line number to the debugger client and report any
* variable modifications
*/
if (!plugin_debugger_main_loop())
dbg_info->stepping = FALSE;
Heikki Linnakangas
committed
}
}
}
/* ---------------------------------------------------------------------
* datumIsNull()
Heikki Linnakangas
committed
* determine whether datum is NULL or not.
* TODO: consider datatypes other than PLPGSQL_DTYPE_VAR as well
*/
static bool
datumIsNull(PLpgSQL_datum *datum)
Heikki Linnakangas
committed
{
switch (datum->dtype)
{
case PLPGSQL_DTYPE_VAR:
{
PLpgSQL_var *var = (PLpgSQL_var *) datum;
Heikki Linnakangas
committed
if (var->isnull)
return true;
}
break;
/* other data types are not currently handled, we just return true */
Heikki Linnakangas
committed
case PLPGSQL_DTYPE_REC:
case PLPGSQL_DTYPE_ROW:
return true;
Heikki Linnakangas
committed
default:
return true;
}
Heikki Linnakangas
committed
return false;
}