2015-01-26 22:34 GMT+01:00 Jim Nasby <[email protected]>:
> 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 ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers