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

Reply via email to