2015-01-28 0:13 GMT+01:00 Jim Nasby <jim.na...@bluetreble.com>: > On 1/27/15 1:30 PM, Pavel Stehule wrote: > >> I don't see the separate warning as being helpful. I'd just do >> something like >> >> + (err_hint != NULL) ? errhint("%s", >> err_hint) : errhint("Message attached to failed assertion is null") )); >> >> >> done >> >> >> There should also be a test case for a NULL message. >> >> >> is there, if I understand well >> > > I see it now. Looks good.
updated version with Jim Nasby's doc and rebase against last changes in plpgsql. Regards Pavel > > -- > Jim Nasby, Data Architect, Blue Treble Consulting > Data in Trouble? Get it in Treble! http://BlueTreble.com >
commit 93163d078e61a603ca3d34e9a0f888f097b0ec0a Author: Pavel Stehule <pavel.steh...@gooddata.com> Date: Mon Mar 23 06:32:22 2015 +0100 fix missing typmod diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index b30c68d..9bd9f1b 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -6999,6 +6999,20 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir' <variablelist> + <varlistentry id="guc-enable-user-asserts" xreflabel="enable_user_asserts"> + <term><varname>enable_user_asserts</varname> (<type>boolean</type>) + <indexterm> + <primary><varname>enable_user_asserts</> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + If true, any user assertions are evaluated. By default, this + is set to true. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-exit-on-error" xreflabel="exit_on_error"> <term><varname>exit_on_error</varname> (<type>boolean</type>) <indexterm> diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index 158d9d2..0a80ecf 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -3373,6 +3373,9 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>; <sect1 id="plpgsql-errors-and-messages"> <title>Errors and Messages</title> + <sect2 id="plpgsql-statements-raise"> + <title>RAISE statement</title> + <indexterm> <primary>RAISE</primary> </indexterm> @@ -3565,7 +3568,33 @@ RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id; the whole category. </para> </note> + </sect2> + + <sect2 id="plpgsql-statements-assert"> + <title>ASSERT statement</title> + <indexterm> + <primary>ASSERT</primary> + </indexterm> + + <indexterm> + <primary>assertions</primary> + <secondary>in PL/pgSQL</secondary> + </indexterm> + + <para> + Use the <command>ASSERT</command> statement to ensure the + predicate is allways true. If the predicate is false or is null, + then a assertion exception is raised. + +<synopsis> +ASSERT <replaceable class="parameter">expression</replaceable> <optional>, <replaceable class="parameter">message expression</replaceable> </optional>; +</synopsis> + + The user assertions can be enabled or disabled via + <xref linkend="guc-enable-user-asserts">. + </para> + </sect2> </sect1> <sect1 id="plpgsql-trigger"> diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt index 28c8c40..da12428 100644 --- a/src/backend/utils/errcodes.txt +++ b/src/backend/utils/errcodes.txt @@ -454,6 +454,7 @@ P0000 E ERRCODE_PLPGSQL_ERROR plp P0001 E ERRCODE_RAISE_EXCEPTION raise_exception P0002 E ERRCODE_NO_DATA_FOUND no_data_found P0003 E ERRCODE_TOO_MANY_ROWS too_many_rows +P0004 E ERRCODE_ASSERT_EXCEPTION assert_exception Section: Class XX - Internal Error diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c index 23e594e..32f4c2c 100644 --- a/src/backend/utils/init/globals.c +++ b/src/backend/utils/init/globals.c @@ -99,6 +99,7 @@ bool IsBinaryUpgrade = false; bool IsBackgroundWorker = false; bool ExitOnAnyError = false; +bool enable_user_asserts = true; int DateStyle = USE_ISO_DATES; int DateOrder = DATEORDER_MDY; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 26275bd..5c3596b 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -1058,6 +1058,15 @@ static struct config_bool ConfigureNamesBool[] = }, { + {"enable_user_asserts", PGC_USERSET, ERROR_HANDLING_OPTIONS, + gettext_noop("Enable user assert checks."), + NULL + }, + &enable_user_asserts, + true, + NULL, NULL, NULL + }, + { {"exit_on_error", PGC_USERSET, ERROR_HANDLING_OPTIONS, gettext_noop("Terminate session on any error."), NULL diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index eacfccb..b20efac 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -149,6 +149,7 @@ extern bool IsBackgroundWorker; extern PGDLLIMPORT bool IsBinaryUpgrade; extern bool ExitOnAnyError; +extern bool enable_user_asserts; extern PGDLLIMPORT char *DataDir; diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 6a93540..b8c4ac9 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -153,6 +153,8 @@ static int exec_stmt_return_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_return_query *stmt); static int exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt); +static int exec_stmt_assert(PLpgSQL_execstate *estate, + PLpgSQL_stmt_assert *stmt); static int exec_stmt_execsql(PLpgSQL_execstate *estate, PLpgSQL_stmt_execsql *stmt); static int exec_stmt_dynexecute(PLpgSQL_execstate *estate, @@ -1027,12 +1029,14 @@ exception_matches_conditions(ErrorData *edata, PLpgSQL_condition *cond) int sqlerrstate = cond->sqlerrstate; /* - * OTHERS matches everything *except* query-canceled; if you're - * foolish enough, you can match that explicitly. + * OTHERS matches everything *except* query-canceled and + * assert-exception. if you're foolish enough, you can + * match those explicitly. */ if (sqlerrstate == 0) { - if (edata->sqlerrcode != ERRCODE_QUERY_CANCELED) + if (edata->sqlerrcode != ERRCODE_QUERY_CANCELED && + edata->sqlerrcode != ERRCODE_ASSERT_EXCEPTION) return true; } /* Exact match? */ @@ -1471,6 +1475,10 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) rc = exec_stmt_raise(estate, (PLpgSQL_stmt_raise *) stmt); break; + case PLPGSQL_STMT_ASSERT: + rc = exec_stmt_assert(estate, (PLpgSQL_stmt_assert *) stmt); + break; + case PLPGSQL_STMT_EXECSQL: rc = exec_stmt_execsql(estate, (PLpgSQL_stmt_execsql *) stmt); break; @@ -3117,6 +3125,64 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt) return PLPGSQL_RC_OK; } +/* ---------- + * exec_stmt_assert Assert statement + * ---------- + */ +static int +exec_stmt_assert(PLpgSQL_execstate *estate, PLpgSQL_stmt_assert *stmt) +{ + bool value; + bool isnull; + + /* do nothing when asserts are not enabled */ + if (!enable_user_asserts) + return PLPGSQL_RC_OK; + + value = exec_eval_boolean(estate, stmt->cond, &isnull); + exec_eval_cleanup(estate); + + if (isnull || !value) + { + StringInfoData ds; + char *err_hint = NULL; + + initStringInfo(&ds); + + if (isnull) + appendStringInfo(&ds, "\"%s\" is null", stmt->cond->query + 7); + else + appendStringInfo(&ds, "\"%s\" is false", stmt->cond->query + 7); + + if (stmt->hint != NULL) + { + Oid expr_typeid; + int32 expr_typmod; + bool expr_isnull; + Datum expr_val; + + expr_val = exec_eval_expr(estate, stmt->hint, + &expr_isnull, + &expr_typeid, + &expr_typmod); + + if (!expr_isnull) + err_hint = pstrdup(convert_value_to_string(estate, expr_val, expr_typeid)); + else + err_hint = pstrdup("Message attached to failed assertion is null"); + + exec_eval_cleanup(estate); + } + + ereport(ERROR, + (errcode(ERRCODE_ASSERT_EXCEPTION), + errmsg("Assertion failure"), + errdetail("%s", ds.data), + (err_hint != NULL) ? errhint("%s", err_hint) : 0)); + } + + return PLPGSQL_RC_OK; +} /* ---------- * Initialize a mostly empty execution state diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index b6023cc..4e7e0df 100644 --- a/src/pl/plpgsql/src/pl_funcs.c +++ b/src/pl/plpgsql/src/pl_funcs.c @@ -244,6 +244,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt) return "RETURN QUERY"; case PLPGSQL_STMT_RAISE: return "RAISE"; + case PLPGSQL_STMT_ASSERT: + return "ASSERT"; case PLPGSQL_STMT_EXECSQL: return _("SQL statement"); case PLPGSQL_STMT_DYNEXECUTE: @@ -330,6 +332,7 @@ static void free_return(PLpgSQL_stmt_return *stmt); static void free_return_next(PLpgSQL_stmt_return_next *stmt); static void free_return_query(PLpgSQL_stmt_return_query *stmt); static void free_raise(PLpgSQL_stmt_raise *stmt); +static void free_assert(PLpgSQL_stmt_assert *stmt); static void free_execsql(PLpgSQL_stmt_execsql *stmt); static void free_dynexecute(PLpgSQL_stmt_dynexecute *stmt); static void free_dynfors(PLpgSQL_stmt_dynfors *stmt); @@ -391,6 +394,9 @@ free_stmt(PLpgSQL_stmt *stmt) case PLPGSQL_STMT_RAISE: free_raise((PLpgSQL_stmt_raise *) stmt); break; + case PLPGSQL_STMT_ASSERT: + free_assert((PLpgSQL_stmt_assert *) stmt); + break; case PLPGSQL_STMT_EXECSQL: free_execsql((PLpgSQL_stmt_execsql *) stmt); break; @@ -611,6 +617,13 @@ free_raise(PLpgSQL_stmt_raise *stmt) } static void +free_assert(PLpgSQL_stmt_assert *stmt) +{ + free_expr(stmt->cond); + free_expr(stmt->hint); +} + +static void free_execsql(PLpgSQL_stmt_execsql *stmt) { free_expr(stmt->sqlstmt); @@ -732,6 +745,7 @@ static void dump_return(PLpgSQL_stmt_return *stmt); static void dump_return_next(PLpgSQL_stmt_return_next *stmt); static void dump_return_query(PLpgSQL_stmt_return_query *stmt); static void dump_raise(PLpgSQL_stmt_raise *stmt); +static void dump_assert(PLpgSQL_stmt_assert *stmt); static void dump_execsql(PLpgSQL_stmt_execsql *stmt); static void dump_dynexecute(PLpgSQL_stmt_dynexecute *stmt); static void dump_dynfors(PLpgSQL_stmt_dynfors *stmt); @@ -804,6 +818,9 @@ dump_stmt(PLpgSQL_stmt *stmt) case PLPGSQL_STMT_RAISE: dump_raise((PLpgSQL_stmt_raise *) stmt); break; + case PLPGSQL_STMT_ASSERT: + dump_assert((PLpgSQL_stmt_assert *) stmt); + break; case PLPGSQL_STMT_EXECSQL: dump_execsql((PLpgSQL_stmt_execsql *) stmt); break; @@ -1354,6 +1371,24 @@ dump_raise(PLpgSQL_stmt_raise *stmt) } static void +dump_assert(PLpgSQL_stmt_assert *stmt) +{ + dump_ind(); + printf("ASSERT "); + dump_expr(stmt->cond); + printf("\n"); + + dump_indent += 2; + if (stmt->hint != NULL) + { + dump_ind(); + printf(" HINT = "); + dump_expr(stmt->hint); + } + dump_indent -= 2; +} + +static void dump_execsql(PLpgSQL_stmt_execsql *stmt) { dump_ind(); diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y index 46217fd..57dcd50 100644 --- a/src/pl/plpgsql/src/pl_gram.y +++ b/src/pl/plpgsql/src/pl_gram.y @@ -192,7 +192,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt); %type <loop_body> loop_body %type <stmt> proc_stmt pl_block %type <stmt> stmt_assign stmt_if stmt_loop stmt_while stmt_exit -%type <stmt> stmt_return stmt_raise stmt_execsql +%type <stmt> stmt_return stmt_raise stmt_assert stmt_execsql %type <stmt> stmt_dynexecute stmt_for stmt_perform stmt_getdiag %type <stmt> stmt_open stmt_fetch stmt_move stmt_close stmt_null %type <stmt> stmt_case stmt_foreach_a @@ -247,6 +247,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt); %token <keyword> K_ALIAS %token <keyword> K_ALL %token <keyword> K_ARRAY +%token <keyword> K_ASSERT %token <keyword> K_BACKWARD %token <keyword> K_BEGIN %token <keyword> K_BY @@ -871,6 +872,8 @@ proc_stmt : pl_block ';' { $$ = $1; } | stmt_raise { $$ = $1; } + | stmt_assert + { $$ = $1; } | stmt_execsql { $$ = $1; } | stmt_dynexecute @@ -1847,6 +1850,37 @@ stmt_raise : K_RAISE } ; +stmt_assert: K_ASSERT + { + PLpgSQL_stmt_assert *new; + int endtoken; + + new = palloc(sizeof(PLpgSQL_stmt_assert)); + + new->cmd_type = PLPGSQL_STMT_ASSERT; + new->lineno = plpgsql_location_to_lineno(@1); + + new->cond = read_sql_construct(',', ';', 0, + ", or ;", + "SELECT ", + true, true, true, + NULL, &endtoken); + + if (endtoken == ',') + { + new->hint = read_sql_construct(';', 0, 0, + ";", + "SELECT ", + true, true, true, + NULL, NULL); + } + else + new->hint = NULL; + + $$ = (PLpgSQL_stmt *) new; + } + ; + loop_body : proc_sect K_END K_LOOP opt_label ';' { $$.stmts = $1; diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c index f932377..1350ec1 100644 --- a/src/pl/plpgsql/src/pl_scanner.c +++ b/src/pl/plpgsql/src/pl_scanner.c @@ -98,6 +98,7 @@ static const ScanKeyword unreserved_keywords[] = { PG_KEYWORD("absolute", K_ABSOLUTE, UNRESERVED_KEYWORD) PG_KEYWORD("alias", K_ALIAS, UNRESERVED_KEYWORD) PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD) + PG_KEYWORD("assert", K_ASSERT, UNRESERVED_KEYWORD) PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD) PG_KEYWORD("close", K_CLOSE, UNRESERVED_KEYWORD) PG_KEYWORD("collate", K_COLLATE, UNRESERVED_KEYWORD) diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 66d4da6..e1bb619 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -80,6 +80,7 @@ enum enum PLpgSQL_stmt_types { PLPGSQL_STMT_BLOCK, + PLPGSQL_STMT_ASSERT, PLPGSQL_STMT_ASSIGN, PLPGSQL_STMT_IF, PLPGSQL_STMT_CASE, @@ -632,6 +633,14 @@ typedef struct typedef struct +{ /* ASSERT statement */ + int cmd_type; + int lineno; + PLpgSQL_expr *cond; + PLpgSQL_expr *hint; +} PLpgSQL_stmt_assert; + +typedef struct { /* Generic SQL statement to execute */ int cmd_type; int lineno; diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index 2c0b2e5..530fde8 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -5377,3 +5377,63 @@ NOTICE: outer_func() done drop function outer_outer_func(int); drop function outer_func(int); drop function inner_func(int); +-- ensure enabled user assertions +set enable_user_asserts = on; +-- should be ok +do $$ +begin + assert 1=1; +end; +$$; +-- should fail +do $$ +begin + assert 1=0; +end; +$$; +ERROR: Assertion failure +DETAIL: "1=0" is false +CONTEXT: PL/pgSQL function inline_code_block line 3 at ASSERT +-- should fail +do $$ +begin + assert NULL; +end; +$$; +ERROR: Assertion failure +DETAIL: "NULL" is null +CONTEXT: PL/pgSQL function inline_code_block line 3 at ASSERT +-- should fail +-- test of warning, when message related to a assert is null +do $$ +begin + assert 1=0, NULL; +end; +$$; +ERROR: Assertion failure +DETAIL: "1=0" is false +HINT: Message attached to failed assertion is null +CONTEXT: PL/pgSQL function inline_code_block line 3 at ASSERT +-- should fail +do $$ +declare var text := 'some value'; +begin + assert 1=0, format('content of var: "%s"', var); +end; +$$; +ERROR: Assertion failure +DETAIL: "1=0" is false +HINT: content of var: "some value" +CONTEXT: PL/pgSQL function inline_code_block line 4 at ASSERT +-- ensure asserts are not trapped by 'others' +do $$ +begin + assert 1=0, 'unhandled assert'; +exception when others then + null; -- do nothing +end; +$$ language plpgsql; +ERROR: Assertion failure +DETAIL: "1=0" is false +HINT: unhandled assert +CONTEXT: PL/pgSQL function inline_code_block line 3 at ASSERT diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index 6dabe50..a78afc1 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -12,7 +12,8 @@ SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%'; enable_seqscan | on enable_sort | on enable_tidscan | on -(11 rows) + enable_user_asserts | on +(12 rows) CREATE TABLE foo2(fooid int, f2 int); INSERT INTO foo2 VALUES(1, 11); diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index 001138e..a5f8acf 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -4217,3 +4217,52 @@ select outer_outer_func(20); drop function outer_outer_func(int); drop function outer_func(int); drop function inner_func(int); + +-- ensure enabled user assertions +set enable_user_asserts = on; + +-- should be ok +do $$ +begin + assert 1=1; +end; +$$; + +-- should fail +do $$ +begin + assert 1=0; +end; +$$; + +-- should fail +do $$ +begin + assert NULL; +end; +$$; + +-- should fail +-- test of warning, when message related to a assert is null +do $$ +begin + assert 1=0, NULL; +end; +$$; + +-- should fail +do $$ +declare var text := 'some value'; +begin + assert 1=0, format('content of var: "%s"', var); +end; +$$; + +-- ensure asserts are not trapped by 'others' +do $$ +begin + assert 1=0, 'unhandled assert'; +exception when others then + null; -- do nothing +end; +$$ language plpgsql;
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers