*** a/doc/src/sgml/plpgsql.sgml
--- b/doc/src/sgml/plpgsql.sgml
***************
*** 3528,3533 **** RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;
--- 3528,3596 ----
+
+ Assertions
+
+
+ Assertions provide a way to check that the
+ internal state of a function is as expected. For example:
+
+ CREATE FUNCTION delete_user(username text) RETURNS VOID AS $$
+ BEGIN
+ DELETE FROM users WHERE users.username = delete_user.username;
+ ASSERT FOUND;
+ END
+ $$ LANGUAGE plpgsql;
+
+ SELECT delete_user('mia');
+ ERROR: Assertion on line 4 failed
+ CONTEXT: PL/pgSQL function delete_user(text) line 4 at ASSERT
+
+
+ One could implement the equivalent functionality with a conditional
+ RAISE EXCEPTION statement, but assertions have two major differences:
+
+
+
+ They're a lot faster to write than the equivalent IF .. THEN RAISE EXCEPTION .. END IF constructs.
+
+
+
+
+ They can be (and are by default) disabled in production
+ environments. A disabled assertion only incurs a negligible
+ compile-time overhead and no execution time overhead, so you
+ can write complex checks for development environments without
+ having to worry about performance.
+
+
+
+
+
+
+ The configuration parameter plpgsql.enable_assertions>
+ controls whether assertions are enabled. Note that the value of
+ this parameter only affects subsequent compilations of
+ PL/pgSQL> functions, but not statements already
+ compiled in the current session.
+
+
+
+ It is also possible to enable assertions in a single function
+ (possibly overriding the global setting) by using a compile
+ option:
+
+ CREATE FUNCTION delete_user(username text) RETURNS VOID AS $$
+ #enable_assertions
+ BEGIN
+ DELETE FROM users WHERE users.username = delete_user.username;
+ ASSERT FOUND;
+ END
+ $$ LANGUAGE plpgsql;
+
+
+
+
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
***************
*** 351,356 **** do_compile(FunctionCallInfo fcinfo,
--- 351,357 ----
function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */
function->resolve_option = plpgsql_variable_conflict;
+ function->enable_assertions = plpgsql_enable_assertions;
if (is_dml_trigger)
function->fn_is_trigger = PLPGSQL_DML_TRIGGER;
***************
*** 847,852 **** plpgsql_compile_inline(char *proc_source)
--- 848,854 ----
function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */
function->resolve_option = plpgsql_variable_conflict;
+ function->enable_assertions = plpgsql_enable_assertions;
plpgsql_ns_init();
plpgsql_ns_push(func_name);
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
***************
*** 133,138 **** static int exec_stmt_dynexecute(PLpgSQL_execstate *estate,
--- 133,140 ----
PLpgSQL_stmt_dynexecute *stmt);
static int exec_stmt_dynfors(PLpgSQL_execstate *estate,
PLpgSQL_stmt_dynfors *stmt);
+ static int exec_stmt_assert(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_assert *stmt);
static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
PLpgSQL_function *func,
***************
*** 1466,1471 **** exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
--- 1468,1477 ----
rc = exec_stmt_close(estate, (PLpgSQL_stmt_close *) stmt);
break;
+ case PLPGSQL_STMT_ASSERT:
+ rc = exec_stmt_assert(estate, (PLpgSQL_stmt_assert *) stmt);
+ break;
+
default:
estate->err_stmt = save_estmt;
elog(ERROR, "unrecognized cmdtype: %d", stmt->cmd_type);
***************
*** 3629,3634 **** exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
--- 3635,3655 ----
return rc;
}
+ static int
+ exec_stmt_assert(PLpgSQL_execstate *estate, PLpgSQL_stmt_assert *stmt)
+ {
+ bool value;
+ bool isnull;
+
+ value = exec_eval_boolean(estate, stmt->expr, &isnull);
+ exec_eval_cleanup(estate);
+ if (isnull || !value)
+ ereport(ERROR,
+ (errcode(ERRCODE_ASSERTION_FAILURE),
+ errmsg("Assertion on line %d failed", stmt->lineno)));
+
+ return PLPGSQL_RC_OK;
+ }
/* ----------
* exec_stmt_open Execute an OPEN cursor statement
*** a/src/pl/plpgsql/src/pl_funcs.c
--- b/src/pl/plpgsql/src/pl_funcs.c
***************
*** 260,265 **** plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
--- 260,267 ----
return "CLOSE";
case PLPGSQL_STMT_PERFORM:
return "PERFORM";
+ case PLPGSQL_STMT_ASSERT:
+ return "ASSERT";
}
return "unknown";
***************
*** 338,343 **** static void free_open(PLpgSQL_stmt_open *stmt);
--- 340,346 ----
static void free_fetch(PLpgSQL_stmt_fetch *stmt);
static void free_close(PLpgSQL_stmt_close *stmt);
static void free_perform(PLpgSQL_stmt_perform *stmt);
+ static void free_assert(PLpgSQL_stmt_assert *stmt);
static void free_expr(PLpgSQL_expr *expr);
***************
*** 415,420 **** free_stmt(PLpgSQL_stmt *stmt)
--- 418,426 ----
case PLPGSQL_STMT_PERFORM:
free_perform((PLpgSQL_stmt_perform *) stmt);
break;
+ case PLPGSQL_STMT_ASSERT:
+ free_assert((PLpgSQL_stmt_assert *) stmt);
+ break;
default:
elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
break;
***************
*** 563,568 **** free_perform(PLpgSQL_stmt_perform *stmt)
--- 569,580 ----
}
static void
+ free_assert(PLpgSQL_stmt_assert *stmt)
+ {
+ free_expr(stmt->expr);
+ }
+
+ static void
free_exit(PLpgSQL_stmt_exit *stmt)
{
free_expr(stmt->cond);
***************
*** 742,747 **** static void dump_cursor_direction(PLpgSQL_stmt_fetch *stmt);
--- 754,760 ----
static void dump_close(PLpgSQL_stmt_close *stmt);
static void dump_perform(PLpgSQL_stmt_perform *stmt);
static void dump_expr(PLpgSQL_expr *expr);
+ static void dump_assert(PLpgSQL_stmt_assert *stmt);
static void
***************
*** 828,833 **** dump_stmt(PLpgSQL_stmt *stmt)
--- 841,849 ----
case PLPGSQL_STMT_PERFORM:
dump_perform((PLpgSQL_stmt_perform *) stmt);
break;
+ case PLPGSQL_STMT_ASSERT:
+ dump_assert((PLpgSQL_stmt_assert *) stmt);
+ break;
default:
elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
break;
***************
*** 1205,1210 **** dump_perform(PLpgSQL_stmt_perform *stmt)
--- 1221,1235 ----
}
static void
+ dump_assert(PLpgSQL_stmt_assert *stmt)
+ {
+ dump_ind();
+ printf("ASSERT expr = ");
+ dump_expr(stmt->expr);
+ printf("\n");
+ }
+
+ static void
dump_exit(PLpgSQL_stmt_exit *stmt)
{
dump_ind();
*** a/src/pl/plpgsql/src/pl_gram.y
--- b/src/pl/plpgsql/src/pl_gram.y
***************
*** 193,198 **** static List *read_raise_options(void);
--- 193,199 ----
%type stmt_assign stmt_if stmt_loop stmt_while stmt_exit
%type stmt_return stmt_raise stmt_execsql
%type stmt_dynexecute stmt_for stmt_perform stmt_getdiag
+ %type stmt_assert
%type stmt_open stmt_fetch stmt_move stmt_close stmt_null
%type stmt_case stmt_foreach_a
***************
*** 245,250 **** static List *read_raise_options(void);
--- 246,252 ----
%token K_ALIAS
%token K_ALL
%token K_ARRAY
+ %token K_ASSERT
%token K_BACKWARD
%token K_BEGIN
%token K_BY
***************
*** 268,273 **** static List *read_raise_options(void);
--- 270,276 ----
%token K_DUMP
%token K_ELSE
%token K_ELSIF
+ %token K_ENABLE_ASSERTIONS
%token K_END
%token K_ERRCODE
%token K_ERROR
***************
*** 354,359 **** comp_option : '#' K_OPTION K_DUMP
--- 357,366 ----
{
plpgsql_DumpExecTree = true;
}
+ | '#' K_ENABLE_ASSERTIONS
+ {
+ plpgsql_curr_compile->enable_assertions = true;
+ }
| '#' K_VARIABLE_CONFLICT K_ERROR
{
plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_ERROR;
***************
*** 833,838 **** proc_stmt : pl_block ';'
--- 840,847 ----
{ $$ = $1; }
| stmt_getdiag
{ $$ = $1; }
+ | stmt_assert
+ { $$ = $1; }
| stmt_open
{ $$ = $1; }
| stmt_fetch
***************
*** 934,939 **** stmt_getdiag : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';'
--- 943,969 ----
}
;
+ stmt_assert : K_ASSERT expr_until_semi
+ {
+ if (plpgsql_curr_compile->enable_assertions)
+ {
+ PLpgSQL_stmt_assert *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_assert));
+ new->cmd_type = PLPGSQL_STMT_ASSERT;
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->expr = $2;
+
+ $$ = (PLpgSQL_stmt *)new;
+ }
+ else
+ {
+ /* skip if assertions are disabled */
+ $$ = NULL;
+ }
+ }
+ ;
+
getdiag_area_opt :
{
$$ = false;
*** a/src/pl/plpgsql/src/pl_handler.c
--- b/src/pl/plpgsql/src/pl_handler.c
***************
*** 36,41 **** static const struct config_enum_entry variable_conflict_options[] = {
--- 36,42 ----
};
int plpgsql_variable_conflict = PLPGSQL_RESOLVE_ERROR;
+ bool plpgsql_enable_assertions = false;
/* Hook for plugins */
PLpgSQL_plugin **plugin_ptr = NULL;
***************
*** 66,71 **** _PG_init(void)
--- 67,81 ----
PGC_SUSET, 0,
NULL, NULL, NULL);
+ DefineCustomBoolVariable("plpgsql.enable_assertions",
+ gettext_noop("Enables assertions in PL/PgSQL functions."),
+ NULL,
+ &plpgsql_enable_assertions,
+ false,
+ PGC_USERSET, 0,
+ NULL, NULL, NULL);
+
+
EmitWarningsOnPlaceholders("plpgsql");
plpgsql_HashTableInit();
*** a/src/pl/plpgsql/src/pl_scanner.c
--- b/src/pl/plpgsql/src/pl_scanner.c
***************
*** 60,65 **** IdentifierLookup plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
--- 60,66 ----
static const ScanKeyword reserved_keywords[] = {
PG_KEYWORD("all", K_ALL, RESERVED_KEYWORD)
+ PG_KEYWORD("assert", K_ASSERT, RESERVED_KEYWORD)
PG_KEYWORD("begin", K_BEGIN, RESERVED_KEYWORD)
PG_KEYWORD("by", K_BY, RESERVED_KEYWORD)
PG_KEYWORD("case", K_CASE, RESERVED_KEYWORD)
***************
*** 120,125 **** static const ScanKeyword unreserved_keywords[] = {
--- 121,127 ----
PG_KEYWORD("debug", K_DEBUG, UNRESERVED_KEYWORD)
PG_KEYWORD("detail", K_DETAIL, UNRESERVED_KEYWORD)
PG_KEYWORD("dump", K_DUMP, UNRESERVED_KEYWORD)
+ PG_KEYWORD("enable_assertions", K_ENABLE_ASSERTIONS, UNRESERVED_KEYWORD)
PG_KEYWORD("errcode", K_ERRCODE, UNRESERVED_KEYWORD)
PG_KEYWORD("error", K_ERROR, UNRESERVED_KEYWORD)
PG_KEYWORD("first", K_FIRST, UNRESERVED_KEYWORD)
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
***************
*** 100,106 **** enum PLpgSQL_stmt_types
PLPGSQL_STMT_OPEN,
PLPGSQL_STMT_FETCH,
PLPGSQL_STMT_CLOSE,
! PLPGSQL_STMT_PERFORM
};
--- 100,107 ----
PLPGSQL_STMT_OPEN,
PLPGSQL_STMT_FETCH,
PLPGSQL_STMT_CLOSE,
! PLPGSQL_STMT_PERFORM,
! PLPGSQL_STMT_ASSERT
};
***************
*** 403,408 **** typedef struct
--- 404,416 ----
List *diag_items; /* List of PLpgSQL_diag_item */
} PLpgSQL_stmt_getdiag;
+ typedef struct
+ { /* ASSERT statement */
+ int cmd_type;
+ int lineno;
+ PLpgSQL_expr *expr;
+ } PLpgSQL_stmt_assert;
+
typedef struct
{ /* IF statement */
***************
*** 736,741 **** typedef struct PLpgSQL_function
--- 744,750 ----
int tg_tag_varno;
PLpgSQL_resolve_option resolve_option;
+ bool enable_assertions;
int ndatums;
PLpgSQL_datum **datums;
***************
*** 873,878 **** extern IdentifierLookup plpgsql_IdentifierLookup;
--- 882,889 ----
extern int plpgsql_variable_conflict;
+ extern bool plpgsql_enable_assertions;
+
extern bool plpgsql_check_syntax;
extern bool plpgsql_DumpExecTree;
*** a/src/test/regress/expected/plpgsql.out
--- b/src/test/regress/expected/plpgsql.out
***************
*** 5085,5087 **** NOTICE: outer_func() done
--- 5085,5161 ----
drop function outer_outer_func(int);
drop function outer_func(int);
drop function inner_func(int);
+ -- check assertions
+ set plpgsql.enable_assertions to true;
+ create function foof()
+ returns void
+ as $$
+ begin
+ -- failure
+ assert 1 > 2;
+ end
+ $$ language plpgsql;
+ select foof();
+ ERROR: Assertion on line 4 failed
+ CONTEXT: PL/pgSQL function foof() line 4 at ASSERT
+ drop function foof();
+ create function foof()
+ returns void
+ as $$
+ begin
+ -- no failure
+ assert 1 < 2;
+ end
+ $$ language plpgsql;
+ select foof();
+ foof
+ ------
+
+ (1 row)
+
+ drop function foof();
+ create function foof()
+ returns void
+ as $$
+ begin
+ -- NULL is failure
+ assert 1 < NULL;
+ end
+ $$ language plpgsql;
+ select foof();
+ ERROR: Assertion on line 4 failed
+ CONTEXT: PL/pgSQL function foof() line 4 at ASSERT
+ reset plpgsql.enable_assertions;
+ -- still enabled for foof
+ select foof();
+ ERROR: Assertion on line 4 failed
+ CONTEXT: PL/pgSQL function foof() line 4 at ASSERT
+ drop function foof();
+ create function foof()
+ returns void
+ as $$
+ #enable_assertions
+ begin
+ -- failure
+ assert 1 > 2;
+ end
+ $$ language plpgsql;
+ select foof();
+ ERROR: Assertion on line 5 failed
+ CONTEXT: PL/pgSQL function foof() line 5 at ASSERT
+ drop function foof();
+ create function foof()
+ returns void
+ as $$
+ begin
+ -- failure, but assertions are disabled
+ assert 1 > 2;
+ end
+ $$ language plpgsql;
+ select foof();
+ foof
+ ------
+
+ (1 row)
+
+ drop function foof();
*** a/src/test/regress/sql/plpgsql.sql
--- b/src/test/regress/sql/plpgsql.sql
***************
*** 3983,3985 **** drop function outer_outer_func(int);
--- 3983,4055 ----
drop function outer_func(int);
drop function inner_func(int);
+ -- check assertions
+
+ set plpgsql.enable_assertions to true;
+
+ create function foof()
+ returns void
+ as $$
+ begin
+ -- failure
+ assert 1 > 2;
+ end
+ $$ language plpgsql;
+
+ select foof();
+ drop function foof();
+
+ create function foof()
+ returns void
+ as $$
+ begin
+ -- no failure
+ assert 1 < 2;
+ end
+ $$ language plpgsql;
+
+ select foof();
+ drop function foof();
+
+ create function foof()
+ returns void
+ as $$
+ begin
+ -- NULL is failure
+ assert 1 < NULL;
+ end
+ $$ language plpgsql;
+
+ select foof();
+
+ reset plpgsql.enable_assertions;
+ -- still enabled for foof
+ select foof();
+
+ drop function foof();
+
+ create function foof()
+ returns void
+ as $$
+ #enable_assertions
+ begin
+ -- failure
+ assert 1 > 2;
+ end
+ $$ language plpgsql;
+
+ select foof();
+ drop function foof();
+
+ create function foof()
+ returns void
+ as $$
+ begin
+ -- failure, but assertions are disabled
+ assert 1 > 2;
+ end
+ $$ language plpgsql;
+
+ select foof();
+ drop function foof();
+