1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-27 12:41:57 +03:00

plpgsql: Add new option print_strict_params.

This option provides more detailed error messages when STRICT is used
and the number of rows returned is not one.

Marko Tiikkaja, reviewed by Ian Lawrence Barwick
This commit is contained in:
Robert Haas
2013-10-07 15:38:49 -04:00
parent c01262a824
commit 689746c045
9 changed files with 426 additions and 8 deletions

View File

@ -351,6 +351,7 @@ do_compile(FunctionCallInfo fcinfo,
function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */
function->resolve_option = plpgsql_variable_conflict;
function->print_strict_params = plpgsql_print_strict_params;
if (is_dml_trigger)
function->fn_is_trigger = PLPGSQL_DML_TRIGGER;
@ -847,6 +848,7 @@ plpgsql_compile_inline(char *proc_source)
function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */
function->resolve_option = plpgsql_variable_conflict;
function->print_strict_params = plpgsql_print_strict_params;
plpgsql_ns_init();
plpgsql_ns_push(func_name);

View File

@ -221,6 +221,11 @@ static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
PLpgSQL_expr *dynquery, List *params,
const char *portalname, int cursorOptions);
static char *format_expr_params(PLpgSQL_execstate *estate,
const PLpgSQL_expr *expr);
static char *format_preparedparamsdata(PLpgSQL_execstate *estate,
const PreparedParamsData *ppd);
/* ----------
* plpgsql_exec_function Called by the call handler for
@ -3391,18 +3396,40 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
if (n == 0)
{
if (stmt->strict)
{
char *errdetail;
if (estate->func->print_strict_params)
errdetail = format_expr_params(estate, expr);
else
errdetail = NULL;
ereport(ERROR,
(errcode(ERRCODE_NO_DATA_FOUND),
errmsg("query returned no rows")));
errmsg("query returned no rows"),
errdetail ?
errdetail_internal("parameters: %s", errdetail) : 0));
}
/* set the target to NULL(s) */
exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
}
else
{
if (n > 1 && (stmt->strict || stmt->mod_stmt))
{
char *errdetail;
if (estate->func->print_strict_params)
errdetail = format_expr_params(estate, expr);
else
errdetail = NULL;
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ROWS),
errmsg("query returned more than one row")));
errmsg("query returned more than one row"),
errdetail ?
errdetail_internal("parameters: %s", errdetail) : 0));
}
/* Put the first result row into the target */
exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
}
@ -3442,6 +3469,7 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
Oid restype;
char *querystr;
int exec_res;
PreparedParamsData *ppd = NULL;
/*
* First we evaluate the string expression after the EXECUTE keyword. Its
@ -3466,14 +3494,11 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
*/
if (stmt->params)
{
PreparedParamsData *ppd;
ppd = exec_eval_using_params(estate, stmt->params);
exec_res = SPI_execute_with_args(querystr,
ppd->nargs, ppd->types,
ppd->values, ppd->nulls,
estate->readonly_func, 0);
free_params_data(ppd);
}
else
exec_res = SPI_execute(querystr, estate->readonly_func, 0);
@ -3565,18 +3590,41 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
if (n == 0)
{
if (stmt->strict)
{
char *errdetail;
if (estate->func->print_strict_params)
errdetail = format_preparedparamsdata(estate, ppd);
else
errdetail = NULL;
ereport(ERROR,
(errcode(ERRCODE_NO_DATA_FOUND),
errmsg("query returned no rows")));
errmsg("query returned no rows"),
errdetail ?
errdetail_internal("parameters: %s", errdetail) : 0));
}
/* set the target to NULL(s) */
exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
}
else
{
if (n > 1 && stmt->strict)
{
char *errdetail;
if (estate->func->print_strict_params)
errdetail = format_preparedparamsdata(estate, ppd);
else
errdetail = NULL;
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ROWS),
errmsg("query returned more than one row")));
errmsg("query returned more than one row"),
errdetail ?
errdetail_internal("parameters: %s", errdetail) : 0));
}
/* Put the first result row into the target */
exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
}
@ -3592,6 +3640,9 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
*/
}
if (ppd)
free_params_data(ppd);
/* Release any result from SPI_execute, as well as the querystring */
SPI_freetuptable(SPI_tuptable);
pfree(querystr);
@ -6456,3 +6507,103 @@ exec_dynquery_with_params(PLpgSQL_execstate *estate,
return portal;
}
/*
* Return a formatted string with information about an expression's parameters,
* or NULL if the expression does not take any parameters.
*/
static char *
format_expr_params(PLpgSQL_execstate *estate,
const PLpgSQL_expr *expr)
{
int paramno;
int dno;
StringInfoData paramstr;
Bitmapset *tmpset;
if (!expr->paramnos)
return NULL;
initStringInfo(&paramstr);
tmpset = bms_copy(expr->paramnos);
paramno = 0;
while ((dno = bms_first_member(tmpset)) >= 0)
{
Datum paramdatum;
Oid paramtypeid;
bool paramisnull;
int32 paramtypmod;
PLpgSQL_var *curvar;
curvar = (PLpgSQL_var *) estate->datums[dno];
exec_eval_datum(estate, (PLpgSQL_datum *) curvar, &paramtypeid,
&paramtypmod, &paramdatum, &paramisnull);
appendStringInfo(&paramstr, "%s%s = ",
paramno > 0 ? ", " : "",
curvar->refname);
if (paramisnull)
appendStringInfoString(&paramstr, "NULL");
else
{
char *value = convert_value_to_string(estate, paramdatum, paramtypeid);
char *p;
appendStringInfoCharMacro(&paramstr, '\'');
for (p = value; *p; p++)
{
if (*p == '\'') /* double single quotes */
appendStringInfoCharMacro(&paramstr, *p);
appendStringInfoCharMacro(&paramstr, *p);
}
appendStringInfoCharMacro(&paramstr, '\'');
}
paramno++;
}
bms_free(tmpset);
return paramstr.data;
}
/*
* Return a formatted string with information about PreparedParamsData, or NULL
* if the there are no parameters.
*/
static char *
format_preparedparamsdata(PLpgSQL_execstate *estate,
const PreparedParamsData *ppd)
{
int paramno;
StringInfoData paramstr;
if (!ppd)
return NULL;
initStringInfo(&paramstr);
for (paramno = 0; paramno < ppd->nargs; paramno++)
{
appendStringInfo(&paramstr, "%s$%d = ",
paramno > 0 ? ", " : "",
paramno + 1);
if (ppd->nulls[paramno] == 'n')
appendStringInfoString(&paramstr, "NULL");
else
{
char *value = convert_value_to_string(estate, ppd->values[paramno], ppd->types[paramno]);
char *p;
appendStringInfoCharMacro(&paramstr, '\'');
for (p = value; *p; p++)
{
if (*p == '\'') /* double single quotes */
appendStringInfoCharMacro(&paramstr, *p);
appendStringInfoCharMacro(&paramstr, *p);
}
appendStringInfoCharMacro(&paramstr, '\'');
}
}
return paramstr.data;
}

View File

@ -185,7 +185,7 @@ static List *read_raise_options(void);
%type <forvariable> for_variable
%type <stmt> for_control
%type <str> any_identifier opt_block_label opt_label
%type <str> any_identifier opt_block_label opt_label option_value
%type <list> proc_sect proc_stmts stmt_elsifs stmt_else
%type <loop_body> loop_body
@ -308,6 +308,7 @@ static List *read_raise_options(void);
%token <keyword> K_PG_EXCEPTION_CONTEXT
%token <keyword> K_PG_EXCEPTION_DETAIL
%token <keyword> K_PG_EXCEPTION_HINT
%token <keyword> K_PRINT_STRICT_PARAMS
%token <keyword> K_PRIOR
%token <keyword> K_QUERY
%token <keyword> K_RAISE
@ -354,6 +355,15 @@ comp_option : '#' K_OPTION K_DUMP
{
plpgsql_DumpExecTree = true;
}
| '#' K_PRINT_STRICT_PARAMS option_value
{
if (strcmp($3, "on") == 0)
plpgsql_curr_compile->print_strict_params = true;
else if (strcmp($3, "off") == 0)
plpgsql_curr_compile->print_strict_params = false;
else
elog(ERROR, "unrecognized print_strict_params option %s", $3);
}
| '#' K_VARIABLE_CONFLICT K_ERROR
{
plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_ERROR;
@ -368,6 +378,15 @@ comp_option : '#' K_OPTION K_DUMP
}
;
option_value : T_WORD
{
$$ = $1.ident;
}
| unreserved_keyword
{
$$ = pstrdup($1);
}
opt_semi :
| ';'
;
@ -2300,6 +2319,7 @@ unreserved_keyword :
| K_PG_EXCEPTION_DETAIL
| K_PG_EXCEPTION_HINT
| K_PRIOR
| K_PRINT_STRICT_PARAMS
| K_QUERY
| K_RELATIVE
| K_RESULT_OID

View File

@ -37,6 +37,8 @@ static const struct config_enum_entry variable_conflict_options[] = {
int plpgsql_variable_conflict = PLPGSQL_RESOLVE_ERROR;
bool plpgsql_print_strict_params = false;
/* Hook for plugins */
PLpgSQL_plugin **plugin_ptr = NULL;
@ -66,6 +68,14 @@ _PG_init(void)
PGC_SUSET, 0,
NULL, NULL, NULL);
DefineCustomBoolVariable("plpgsql.print_strict_params",
gettext_noop("Print information about parameters in the DETAIL part of the error messages generated on INTO .. STRICT failures."),
NULL,
&plpgsql_print_strict_params,
false,
PGC_USERSET, 0,
NULL, NULL, NULL);
EmitWarningsOnPlaceholders("plpgsql");
plpgsql_HashTableInit();

View File

@ -140,6 +140,7 @@ static const ScanKeyword unreserved_keywords[] = {
PG_KEYWORD("pg_exception_context", K_PG_EXCEPTION_CONTEXT, UNRESERVED_KEYWORD)
PG_KEYWORD("pg_exception_detail", K_PG_EXCEPTION_DETAIL, UNRESERVED_KEYWORD)
PG_KEYWORD("pg_exception_hint", K_PG_EXCEPTION_HINT, UNRESERVED_KEYWORD)
PG_KEYWORD("print_strict_params", K_PRINT_STRICT_PARAMS, UNRESERVED_KEYWORD)
PG_KEYWORD("prior", K_PRIOR, UNRESERVED_KEYWORD)
PG_KEYWORD("query", K_QUERY, UNRESERVED_KEYWORD)
PG_KEYWORD("relative", K_RELATIVE, UNRESERVED_KEYWORD)

View File

@ -737,6 +737,8 @@ typedef struct PLpgSQL_function
PLpgSQL_resolve_option resolve_option;
bool print_strict_params;
int ndatums;
PLpgSQL_datum **datums;
PLpgSQL_stmt_block *action;
@ -873,6 +875,8 @@ extern IdentifierLookup plpgsql_IdentifierLookup;
extern int plpgsql_variable_conflict;
extern bool plpgsql_print_strict_params;
extern bool plpgsql_check_syntax;
extern bool plpgsql_DumpExecTree;