Hi here is updated patch
2015-01-21 23:28 GMT+01:00 Jim Nasby <jim.na...@bluetreble.com>: > On 1/21/15 3:10 PM, Pavel Stehule wrote: > >> >> is there some agreement on this behave of ASSERT statement? >> >> I would to assign last patch to next commitfest. >> >> Possible changes: >> >> * 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. > > * 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. > > * a assert exception should not be handled by PLpgSQL handler -- like >> CANCEL exception >> > > +1 > -- > Jim Nasby, Data Architect, Blue Treble Consulting > Data in Trouble? Get it in Treble! http://BlueTreble.com >
commit 60f85cb5f284bf812ee73d321850d25313d19d65 Author: Pavel Stehule <pavel.steh...@gooddata.com> Date: Thu Jan 22 20:56:31 2015 +0100 initial diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 6bcb106..5663983 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -6912,6 +6912,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 69a0885..26f7eba 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -3372,6 +3372,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> @@ -3564,7 +3567,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 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 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 c35867b..cd55e94 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 f6df077..b3a2660 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -980,6 +980,15 @@ static struct config_bool ConfigureNamesBool[] = }, { + {"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 index 6e33a17..fab3e8a 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -137,6 +137,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 ae5421f..237f453 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -136,6 +136,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, @@ -1013,12 +1015,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 that 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? */ @@ -1465,6 +1469,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; @@ -3091,6 +3099,62 @@ 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; + 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 + elog(WARNING, "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 1dcea73..2b8d3b7 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 590aac5..8d87255 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 @@ -246,6 +246,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 @@ -870,6 +871,8 @@ proc_stmt : pl_block ';' { $$ = $1; } | stmt_raise { $$ = $1; } + | stmt_assert + { $$ = $1; } | stmt_execsql { $$ = $1; } | stmt_dynexecute @@ -1846,6 +1849,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 ec08b02..8af7d41 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 00f2f77..b909865 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -79,6 +79,7 @@ enum enum PLpgSQL_stmt_types { PLPGSQL_STMT_BLOCK, + PLPGSQL_STMT_ASSERT, PLPGSQL_STMT_ASSIGN, PLPGSQL_STMT_IF, PLPGSQL_STMT_CASE, @@ -631,6 +632,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 daf3447..292dceb 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -5365,3 +5365,64 @@ 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 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; +$$; +WARNING: Message attached to failed assertion is null +CONTEXT: PL/pgSQL function inline_code_block line 3 at ASSERT +ERROR: Assertion failure +DETAIL: "1=0" is false +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 index 7991e99..80e0b4b 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 a0840c9..aece91b 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -4209,3 +4209,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 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