Newer
Older
Heikki Linnakangas
committed
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
* 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;
}