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

Lines changed: 73 additions & 4 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 59 additions & 9 deletions
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

Lines changed: 36 additions & 0 deletions
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

Lines changed: 28 additions & 1 deletion
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy