Skip to content

Commit a4847fc

Browse files
committed
Add an ASSERT statement in plpgsql.
This is meant to make it easier to insert simple debugging cross-checks in plpgsql functions. Pavel Stehule, reviewed by Jim Nasby
1 parent 83ff161 commit a4847fc

File tree

10 files changed

+316
-16
lines changed

10 files changed

+316
-16
lines changed

doc/src/sgml/plpgsql.sgml

+73-4
Original file line numberDiff line numberDiff line change
@@ -2562,8 +2562,9 @@ END;
25622562
those shown in <xref linkend="errcodes-appendix">. A category
25632563
name matches any error within its category. The special
25642564
condition name <literal>OTHERS</> matches every error type except
2565-
<literal>QUERY_CANCELED</>. (It is possible, but often unwise,
2566-
to trap <literal>QUERY_CANCELED</> by name.) Condition names are
2565+
<literal>QUERY_CANCELED</> and <literal>ASSERT_FAILURE</>.
2566+
(It is possible, but often unwise, to trap those two error types
2567+
by name.) Condition names are
25672568
not case-sensitive. Also, an error condition can be specified
25682569
by <literal>SQLSTATE</> code; for example these are equivalent:
25692570
<programlisting>
@@ -3387,8 +3388,12 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>;
33873388
<sect1 id="plpgsql-errors-and-messages">
33883389
<title>Errors and Messages</title>
33893390

3391+
<sect2 id="plpgsql-statements-raise">
3392+
<title>Reporting Errors and Messages</title>
3393+
33903394
<indexterm>
33913395
<primary>RAISE</primary>
3396+
<secondary>in PL/pgSQL</secondary>
33923397
</indexterm>
33933398

33943399
<indexterm>
@@ -3580,6 +3585,67 @@ RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;
35803585
</para>
35813586
</note>
35823587

3588+
</sect2>
3589+
3590+
<sect2 id="plpgsql-statements-assert">
3591+
<title>Checking Assertions</title>
3592+
3593+
<indexterm>
3594+
<primary>ASSERT</primary>
3595+
<secondary>in PL/pgSQL</secondary>
3596+
</indexterm>
3597+
3598+
<indexterm>
3599+
<primary>assertions</primary>
3600+
<secondary>in PL/pgSQL</secondary>
3601+
</indexterm>
3602+
3603+
<indexterm>
3604+
<primary><varname>plpgsql.check_asserts</> configuration parameter</primary>
3605+
</indexterm>
3606+
3607+
<para>
3608+
The <command>ASSERT</command> statement is a convenient shorthand for
3609+
inserting debugging checks into <application>PL/pgSQL</application>
3610+
functions.
3611+
3612+
<synopsis>
3613+
ASSERT <replaceable class="parameter">condition</replaceable> <optional> , <replaceable class="parameter">message</replaceable> </optional>;
3614+
</synopsis>
3615+
3616+
The <replaceable class="parameter">condition</replaceable> is a boolean
3617+
expression that is expected to always evaluate to TRUE; if it does,
3618+
the <command>ASSERT</command> statement does nothing further. If the
3619+
result is FALSE or NULL, then an <literal>ASSERT_FAILURE</> exception
3620+
is raised. (If an error occurs while evaluating
3621+
the <replaceable class="parameter">condition</replaceable>, it is
3622+
reported as a normal error.)
3623+
</para>
3624+
3625+
<para>
3626+
If the optional <replaceable class="parameter">message</replaceable> is
3627+
provided, it is an expression whose result (if not null) replaces the
3628+
default error message text <quote>assertion failed</>, should
3629+
the <replaceable class="parameter">condition</replaceable> fail.
3630+
The <replaceable class="parameter">message</replaceable> expression is
3631+
not evaluated in the normal case where the assertion succeeds.
3632+
</para>
3633+
3634+
<para>
3635+
Testing of assertions can be enabled or disabled via the configuration
3636+
parameter <literal>plpgsql.check_asserts</>, which takes a boolean
3637+
value; the default is <literal>on</>. If this parameter
3638+
is <literal>off</> then <command>ASSERT</> statements do nothing.
3639+
</para>
3640+
3641+
<para>
3642+
Note that <command>ASSERT</command> is meant for detecting program
3643+
bugs, not for reporting ordinary error conditions. Use
3644+
the <command>RAISE</> statement, described above, for that.
3645+
</para>
3646+
3647+
</sect2>
3648+
35833649
</sect1>
35843650

35853651
<sect1 id="plpgsql-trigger">
@@ -5075,8 +5141,7 @@ $func$ LANGUAGE plpgsql;
50755141
<productname>PostgreSQL</> does not have a built-in
50765142
<function>instr</function> function, but you can create one
50775143
using a combination of other
5078-
functions.<indexterm><primary>instr</></indexterm> In <xref
5079-
linkend="plpgsql-porting-appendix"> there is a
5144+
functions. In <xref linkend="plpgsql-porting-appendix"> there is a
50805145
<application>PL/pgSQL</application> implementation of
50815146
<function>instr</function> that you can use to make your porting
50825147
easier.
@@ -5409,6 +5474,10 @@ $$ LANGUAGE plpgsql STRICT IMMUTABLE;
54095474
your porting efforts.
54105475
</para>
54115476

5477+
<indexterm>
5478+
<primary><function>instr</> function</primary>
5479+
</indexterm>
5480+
54125481
<programlisting>
54135482
--
54145483
-- instr functions that mimic Oracle's counterpart

src/backend/utils/errcodes.txt

+1
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@ P0000 E ERRCODE_PLPGSQL_ERROR plp
454454
P0001 E ERRCODE_RAISE_EXCEPTION raise_exception
455455
P0002 E ERRCODE_NO_DATA_FOUND no_data_found
456456
P0003 E ERRCODE_TOO_MANY_ROWS too_many_rows
457+
P0004 E ERRCODE_ASSERT_FAILURE assert_failure
457458

458459
Section: Class XX - Internal Error
459460

src/pl/plpgsql/src/pl_exec.c

+59-9
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ static int exec_stmt_return_query(PLpgSQL_execstate *estate,
153153
PLpgSQL_stmt_return_query *stmt);
154154
static int exec_stmt_raise(PLpgSQL_execstate *estate,
155155
PLpgSQL_stmt_raise *stmt);
156+
static int exec_stmt_assert(PLpgSQL_execstate *estate,
157+
PLpgSQL_stmt_assert *stmt);
156158
static int exec_stmt_execsql(PLpgSQL_execstate *estate,
157159
PLpgSQL_stmt_execsql *stmt);
158160
static int exec_stmt_dynexecute(PLpgSQL_execstate *estate,
@@ -363,8 +365,8 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
363365
estate.err_text = NULL;
364366

365367
/*
366-
* Provide a more helpful message if a CONTINUE or RAISE has been used
367-
* outside the context it can work in.
368+
* Provide a more helpful message if a CONTINUE has been used outside
369+
* the context it can work in.
368370
*/
369371
if (rc == PLPGSQL_RC_CONTINUE)
370372
ereport(ERROR,
@@ -730,8 +732,8 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
730732
estate.err_text = NULL;
731733

732734
/*
733-
* Provide a more helpful message if a CONTINUE or RAISE has been used
734-
* outside the context it can work in.
735+
* Provide a more helpful message if a CONTINUE has been used outside
736+
* the context it can work in.
735737
*/
736738
if (rc == PLPGSQL_RC_CONTINUE)
737739
ereport(ERROR,
@@ -862,8 +864,8 @@ plpgsql_exec_event_trigger(PLpgSQL_function *func, EventTriggerData *trigdata)
862864
estate.err_text = NULL;
863865

864866
/*
865-
* Provide a more helpful message if a CONTINUE or RAISE has been used
866-
* outside the context it can work in.
867+
* Provide a more helpful message if a CONTINUE has been used outside
868+
* the context it can work in.
867869
*/
868870
if (rc == PLPGSQL_RC_CONTINUE)
869871
ereport(ERROR,
@@ -1027,12 +1029,14 @@ exception_matches_conditions(ErrorData *edata, PLpgSQL_condition *cond)
10271029
int sqlerrstate = cond->sqlerrstate;
10281030

10291031
/*
1030-
* OTHERS matches everything *except* query-canceled; if you're
1031-
* foolish enough, you can match that explicitly.
1032+
* OTHERS matches everything *except* query-canceled and
1033+
* assert-failure. If you're foolish enough, you can match those
1034+
* explicitly.
10321035
*/
10331036
if (sqlerrstate == 0)
10341037
{
1035-
if (edata->sqlerrcode != ERRCODE_QUERY_CANCELED)
1038+
if (edata->sqlerrcode != ERRCODE_QUERY_CANCELED &&
1039+
edata->sqlerrcode != ERRCODE_ASSERT_FAILURE)
10361040
return true;
10371041
}
10381042
/* Exact match? */
@@ -1471,6 +1475,10 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
14711475
rc = exec_stmt_raise(estate, (PLpgSQL_stmt_raise *) stmt);
14721476
break;
14731477

1478+
case PLPGSQL_STMT_ASSERT:
1479+
rc = exec_stmt_assert(estate, (PLpgSQL_stmt_assert *) stmt);
1480+
break;
1481+
14741482
case PLPGSQL_STMT_EXECSQL:
14751483
rc = exec_stmt_execsql(estate, (PLpgSQL_stmt_execsql *) stmt);
14761484
break;
@@ -3117,6 +3125,48 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
31173125
return PLPGSQL_RC_OK;
31183126
}
31193127

3128+
/* ----------
3129+
* exec_stmt_assert Assert statement
3130+
* ----------
3131+
*/
3132+
static int
3133+
exec_stmt_assert(PLpgSQL_execstate *estate, PLpgSQL_stmt_assert *stmt)
3134+
{
3135+
bool value;
3136+
bool isnull;
3137+
3138+
/* do nothing when asserts are not enabled */
3139+
if (!plpgsql_check_asserts)
3140+
return PLPGSQL_RC_OK;
3141+
3142+
value = exec_eval_boolean(estate, stmt->cond, &isnull);
3143+
exec_eval_cleanup(estate);
3144+
3145+
if (isnull || !value)
3146+
{
3147+
char *message = NULL;
3148+
3149+
if (stmt->message != NULL)
3150+
{
3151+
Datum val;
3152+
Oid typeid;
3153+
int32 typmod;
3154+
3155+
val = exec_eval_expr(estate, stmt->message,
3156+
&isnull, &typeid, &typmod);
3157+
if (!isnull)
3158+
message = convert_value_to_string(estate, val, typeid);
3159+
/* we mustn't do exec_eval_cleanup here */
3160+
}
3161+
3162+
ereport(ERROR,
3163+
(errcode(ERRCODE_ASSERT_FAILURE),
3164+
message ? errmsg_internal("%s", message) :
3165+
errmsg("assertion failed")));
3166+
}
3167+
3168+
return PLPGSQL_RC_OK;
3169+
}
31203170

31213171
/* ----------
31223172
* Initialize a mostly empty execution state

src/pl/plpgsql/src/pl_funcs.c

+36
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
244244
return "RETURN QUERY";
245245
case PLPGSQL_STMT_RAISE:
246246
return "RAISE";
247+
case PLPGSQL_STMT_ASSERT:
248+
return "ASSERT";
247249
case PLPGSQL_STMT_EXECSQL:
248250
return _("SQL statement");
249251
case PLPGSQL_STMT_DYNEXECUTE:
@@ -330,6 +332,7 @@ static void free_return(PLpgSQL_stmt_return *stmt);
330332
static void free_return_next(PLpgSQL_stmt_return_next *stmt);
331333
static void free_return_query(PLpgSQL_stmt_return_query *stmt);
332334
static void free_raise(PLpgSQL_stmt_raise *stmt);
335+
static void free_assert(PLpgSQL_stmt_assert *stmt);
333336
static void free_execsql(PLpgSQL_stmt_execsql *stmt);
334337
static void free_dynexecute(PLpgSQL_stmt_dynexecute *stmt);
335338
static void free_dynfors(PLpgSQL_stmt_dynfors *stmt);
@@ -391,6 +394,9 @@ free_stmt(PLpgSQL_stmt *stmt)
391394
case PLPGSQL_STMT_RAISE:
392395
free_raise((PLpgSQL_stmt_raise *) stmt);
393396
break;
397+
case PLPGSQL_STMT_ASSERT:
398+
free_assert((PLpgSQL_stmt_assert *) stmt);
399+
break;
394400
case PLPGSQL_STMT_EXECSQL:
395401
free_execsql((PLpgSQL_stmt_execsql *) stmt);
396402
break;
@@ -610,6 +616,13 @@ free_raise(PLpgSQL_stmt_raise *stmt)
610616
}
611617
}
612618

619+
static void
620+
free_assert(PLpgSQL_stmt_assert *stmt)
621+
{
622+
free_expr(stmt->cond);
623+
free_expr(stmt->message);
624+
}
625+
613626
static void
614627
free_execsql(PLpgSQL_stmt_execsql *stmt)
615628
{
@@ -732,6 +745,7 @@ static void dump_return(PLpgSQL_stmt_return *stmt);
732745
static void dump_return_next(PLpgSQL_stmt_return_next *stmt);
733746
static void dump_return_query(PLpgSQL_stmt_return_query *stmt);
734747
static void dump_raise(PLpgSQL_stmt_raise *stmt);
748+
static void dump_assert(PLpgSQL_stmt_assert *stmt);
735749
static void dump_execsql(PLpgSQL_stmt_execsql *stmt);
736750
static void dump_dynexecute(PLpgSQL_stmt_dynexecute *stmt);
737751
static void dump_dynfors(PLpgSQL_stmt_dynfors *stmt);
@@ -804,6 +818,9 @@ dump_stmt(PLpgSQL_stmt *stmt)
804818
case PLPGSQL_STMT_RAISE:
805819
dump_raise((PLpgSQL_stmt_raise *) stmt);
806820
break;
821+
case PLPGSQL_STMT_ASSERT:
822+
dump_assert((PLpgSQL_stmt_assert *) stmt);
823+
break;
807824
case PLPGSQL_STMT_EXECSQL:
808825
dump_execsql((PLpgSQL_stmt_execsql *) stmt);
809826
break;
@@ -1353,6 +1370,25 @@ dump_raise(PLpgSQL_stmt_raise *stmt)
13531370
dump_indent -= 2;
13541371
}
13551372

1373+
static void
1374+
dump_assert(PLpgSQL_stmt_assert *stmt)
1375+
{
1376+
dump_ind();
1377+
printf("ASSERT ");
1378+
dump_expr(stmt->cond);
1379+
printf("\n");
1380+
1381+
dump_indent += 2;
1382+
if (stmt->message != NULL)
1383+
{
1384+
dump_ind();
1385+
printf(" MESSAGE = ");
1386+
dump_expr(stmt->message);
1387+
printf("\n");
1388+
}
1389+
dump_indent -= 2;
1390+
}
1391+
13561392
static void
13571393
dump_execsql(PLpgSQL_stmt_execsql *stmt)
13581394
{

src/pl/plpgsql/src/pl_gram.y

+28-1
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
192192
%type <loop_body> loop_body
193193
%type <stmt> proc_stmt pl_block
194194
%type <stmt> stmt_assign stmt_if stmt_loop stmt_while stmt_exit
195-
%type <stmt> stmt_return stmt_raise stmt_execsql
195+
%type <stmt> stmt_return stmt_raise stmt_assert stmt_execsql
196196
%type <stmt> stmt_dynexecute stmt_for stmt_perform stmt_getdiag
197197
%type <stmt> stmt_open stmt_fetch stmt_move stmt_close stmt_null
198198
%type <stmt> stmt_case stmt_foreach_a
@@ -247,6 +247,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
247247
%token <keyword> K_ALIAS
248248
%token <keyword> K_ALL
249249
%token <keyword> K_ARRAY
250+
%token <keyword> K_ASSERT
250251
%token <keyword> K_BACKWARD
251252
%token <keyword> K_BEGIN
252253
%token <keyword> K_BY
@@ -871,6 +872,8 @@ proc_stmt : pl_block ';'
871872
{ $$ = $1; }
872873
| stmt_raise
873874
{ $$ = $1; }
875+
| stmt_assert
876+
{ $$ = $1; }
874877
| stmt_execsql
875878
{ $$ = $1; }
876879
| stmt_dynexecute
@@ -1847,6 +1850,29 @@ stmt_raise : K_RAISE
18471850
}
18481851
;
18491852

1853+
stmt_assert : K_ASSERT
1854+
{
1855+
PLpgSQL_stmt_assert *new;
1856+
int tok;
1857+
1858+
new = palloc(sizeof(PLpgSQL_stmt_assert));
1859+
1860+
new->cmd_type = PLPGSQL_STMT_ASSERT;
1861+
new->lineno = plpgsql_location_to_lineno(@1);
1862+
1863+
new->cond = read_sql_expression2(',', ';',
1864+
", or ;",
1865+
&tok);
1866+
1867+
if (tok == ',')
1868+
new->message = read_sql_expression(';', ";");
1869+
else
1870+
new->message = NULL;
1871+
1872+
$$ = (PLpgSQL_stmt *) new;
1873+
}
1874+
;
1875+
18501876
loop_body : proc_sect K_END K_LOOP opt_label ';'
18511877
{
18521878
$$.stmts = $1;
@@ -2315,6 +2341,7 @@ unreserved_keyword :
23152341
K_ABSOLUTE
23162342
| K_ALIAS
23172343
| K_ARRAY
2344+
| K_ASSERT
23182345
| K_BACKWARD
23192346
| K_CLOSE
23202347
| K_COLLATE

0 commit comments

Comments
 (0)