2015-01-26 22:34 GMT+01:00 Jim Nasby <jim.na...@bluetreble.com>: > On 1/22/15 2:01 PM, Pavel Stehule wrote: > >> >> * I would to simplify a behave of evaluating of message >> expression - probably I disallow NULL there. >> >> >> Well, the only thing I could see you doing there is throwing a >> different error if the hint is null. I don't see that as an improvement. >> I'd just leave it as-is. >> >> >> I enabled a NULL - but enforced a WARNING before. >> > > 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 Regards Pavel > > * GUC enable_asserts will be supported >> >> >> That would be good. Would that allow for enabling/disabling on a >> per-function basis too? >> >> >> sure - there is only question if we develop a #option >> enable|disable_asserts. I have no string idea. >> > > The option would be nice, but I don't think it's strictly necessary. The > big thing is being able to control this on a per-function basis (which I > think you can do with ALTER FUNCTION SET?) > > -- > Jim Nasby, Data Architect, Blue Treble Consulting > Data in Trouble? Get it in Treble! http://BlueTreble.com >
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml new file mode 100644 index 6bcb106..5663983 *** a/doc/src/sgml/config.sgml --- b/doc/src/sgml/config.sgml *************** dynamic_library_path = 'C:\tools\postgre *** 6912,6917 **** --- 6912,6931 ---- <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 new file mode 100644 index 69a0885..26f7eba *** a/doc/src/sgml/plpgsql.sgml --- b/doc/src/sgml/plpgsql.sgml *************** END LOOP <optional> <replaceable>label</ *** 3372,3377 **** --- 3372,3380 ---- <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> *************** RAISE unique_violation USING MESSAGE = ' *** 3564,3570 **** --- 3567,3599 ---- 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 so expected + 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 by the + <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 new file mode 100644 index 28c8c40..da12428 *** a/src/backend/utils/errcodes.txt --- b/src/backend/utils/errcodes.txt *************** P0000 E ERRCODE_PLPGSQL_ERROR *** 454,459 **** --- 454,460 ---- 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 new file mode 100644 index c35867b..cd55e94 *** a/src/backend/utils/init/globals.c --- b/src/backend/utils/init/globals.c *************** bool IsBinaryUpgrade = false; *** 99,104 **** --- 99,105 ---- 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 new file mode 100644 index f6df077..b3a2660 *** a/src/backend/utils/misc/guc.c --- b/src/backend/utils/misc/guc.c *************** static struct config_bool ConfigureNames *** 980,985 **** --- 980,994 ---- }, { + {"enable_user_asserts", PGC_USERSET, ERROR_HANDLING_OPTIONS, + gettext_noop("Enable user asserts 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 new file mode 100644 index 6e33a17..fab3e8a *** a/src/include/miscadmin.h --- b/src/include/miscadmin.h *************** extern bool IsBackgroundWorker; *** 137,142 **** --- 137,143 ---- 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 new file mode 100644 index ae5421f..54e60ab *** a/src/pl/plpgsql/src/pl_exec.c --- b/src/pl/plpgsql/src/pl_exec.c *************** static int exec_stmt_return_query(PLpgSQ *** 136,141 **** --- 136,143 ---- 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, *************** exception_matches_conditions(ErrorData * *** 1013,1024 **** int sqlerrstate = cond->sqlerrstate; /* ! * OTHERS matches everything *except* query-canceled; if you're ! * foolish enough, you can match that explicitly. */ if (sqlerrstate == 0) { ! if (edata->sqlerrcode != ERRCODE_QUERY_CANCELED) return true; } /* Exact match? */ --- 1015,1028 ---- int sqlerrstate = cond->sqlerrstate; /* ! * OTHERS matches everything *except* query-canceled and ! * assert-exception. if you're foolish enough, you can ! * match that explicitly. */ if (sqlerrstate == 0) { ! if (edata->sqlerrcode != ERRCODE_QUERY_CANCELED && ! edata->sqlerrcode != ERRCODE_ASSERT_EXCEPTION) return true; } /* Exact match? */ *************** exec_stmt(PLpgSQL_execstate *estate, PLp *** 1465,1470 **** --- 1469,1478 ---- 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; *************** exec_stmt_raise(PLpgSQL_execstate *estat *** 3091,3096 **** --- 3099,3160 ---- 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; + bool expr_isnull; + Datum expr_val; + + expr_val = exec_eval_expr(estate, stmt->hint, + &expr_isnull, + &expr_typeid); + + 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 new file mode 100644 index 1dcea73..2b8d3b7 *** a/src/pl/plpgsql/src/pl_funcs.c --- b/src/pl/plpgsql/src/pl_funcs.c *************** plpgsql_stmt_typename(PLpgSQL_stmt *stmt *** 244,249 **** --- 244,251 ---- 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: *************** static void free_return(PLpgSQL_stmt_ret *** 330,335 **** --- 332,338 ---- 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); *************** free_stmt(PLpgSQL_stmt *stmt) *** 391,396 **** --- 394,402 ---- 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; *************** free_raise(PLpgSQL_stmt_raise *stmt) *** 611,616 **** --- 617,629 ---- } 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); *************** static void dump_return(PLpgSQL_stmt_ret *** 732,737 **** --- 745,751 ---- 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); *************** dump_stmt(PLpgSQL_stmt *stmt) *** 804,809 **** --- 818,826 ---- 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; *************** dump_raise(PLpgSQL_stmt_raise *stmt) *** 1352,1357 **** --- 1369,1392 ---- } dump_indent -= 2; } + + 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) diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y new file mode 100644 index 590aac5..8d87255 *** a/src/pl/plpgsql/src/pl_gram.y --- b/src/pl/plpgsql/src/pl_gram.y *************** static void check_raise_parameters(PLp *** 192,198 **** %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_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 --- 192,198 ---- %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_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 *************** static void check_raise_parameters(PLp *** 246,251 **** --- 246,252 ---- %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 *************** proc_stmt : pl_block ';' *** 870,875 **** --- 871,878 ---- { $$ = $1; } | stmt_raise { $$ = $1; } + | stmt_assert + { $$ = $1; } | stmt_execsql { $$ = $1; } | stmt_dynexecute *************** stmt_raise : K_RAISE *** 1846,1851 **** --- 1849,1885 ---- } ; + 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 new file mode 100644 index ec08b02..8af7d41 *** a/src/pl/plpgsql/src/pl_scanner.c --- b/src/pl/plpgsql/src/pl_scanner.c *************** static const ScanKeyword unreserved_keyw *** 98,103 **** --- 98,104 ---- 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 new file mode 100644 index 00f2f77..b909865 *** a/src/pl/plpgsql/src/plpgsql.h --- b/src/pl/plpgsql/src/plpgsql.h *************** enum *** 79,84 **** --- 79,85 ---- enum PLpgSQL_stmt_types { PLPGSQL_STMT_BLOCK, + PLPGSQL_STMT_ASSERT, PLPGSQL_STMT_ASSIGN, PLPGSQL_STMT_IF, PLPGSQL_STMT_CASE, *************** typedef struct *** 631,636 **** --- 632,645 ---- 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 new file mode 100644 index daf3447..04e2b74 *** a/src/test/regress/expected/plpgsql.out --- b/src/test/regress/expected/plpgsql.out *************** NOTICE: outer_func() done *** 5365,5367 **** --- 5365,5427 ---- 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 fails + 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 fails + do $$ + begin + assert NULL; + end; + $$; + ERROR: Assertion failure + DETAIL: "NULL" is null + CONTEXT: PL/pgSQL function inline_code_block line 3 at ASSERT + -- should fails + -- 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 fails + 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 + -- assertions is unhandled + 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 new file mode 100644 index 7991e99..80e0b4b *** a/src/test/regress/expected/rangefuncs.out --- b/src/test/regress/expected/rangefuncs.out *************** SELECT name, setting FROM pg_settings WH *** 12,18 **** enable_seqscan | on enable_sort | on enable_tidscan | on ! (11 rows) CREATE TABLE foo2(fooid int, f2 int); INSERT INTO foo2 VALUES(1, 11); --- 12,19 ---- enable_seqscan | on enable_sort | on enable_tidscan | on ! 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 new file mode 100644 index a0840c9..aece91b *** a/src/test/regress/sql/plpgsql.sql --- b/src/test/regress/sql/plpgsql.sql *************** select outer_outer_func(20); *** 4209,4211 **** --- 4209,4260 ---- 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 fails + do $$ + begin + assert 1=0; + end; + $$; + + -- should fails + do $$ + begin + assert NULL; + end; + $$; + + -- should fails + -- test of warning, when message related to a assert is null + do $$ + begin + assert 1=0, NULL; + end; + $$; + + -- should fails + do $$ + declare var text := 'some value'; + begin + assert 1=0, format('content of var: "%s"', var); + end; + $$; + + -- assertions is unhandled + 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