postgres-notify-options-20190310_01.patch

text/x-patch

Filename: postgres-notify-options-20190310_01.patch
Type: text/x-patch
Part: 0
Message: Re: Re: proposal: make NOTIFY list de-duplication optional

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
File+
contrib/tcn/tcn.c 1 1
doc/src/sgml/ref/notify.sgml 49 15
src/backend/commands/async.c 79 14
src/backend/nodes/copyfuncs.c 4 3
src/backend/nodes/equalfuncs.c 4 3
src/backend/nodes/outfuncs.c 2 1
src/backend/nodes/readfuncs.c 2 1
src/backend/parser/gram.y 66 12
src/backend/tcop/utility.c 4 4
src/backend/utils/adt/ruleutils.c 1 1
src/include/catalog/pg_proc.dat 4 0
src/include/commands/async.h 3 1
src/include/nodes/parsenodes.h 7 5
src/test/regress/expected/async.out 21 0
src/test/regress/sql/async.sql 10 0
 contrib/tcn/tcn.c                   |  2 +-
 doc/src/sgml/ref/notify.sgml        | 64 +++++++++++++++++++------
 src/backend/commands/async.c        | 93 +++++++++++++++++++++++++++++++------
 src/backend/nodes/copyfuncs.c       |  7 +--
 src/backend/nodes/equalfuncs.c      |  7 +--
 src/backend/nodes/outfuncs.c        |  3 +-
 src/backend/nodes/readfuncs.c       |  3 +-
 src/backend/parser/gram.y           | 78 ++++++++++++++++++++++++++-----
 src/backend/tcop/utility.c          |  8 ++--
 src/backend/utils/adt/ruleutils.c   |  2 +-
 src/include/catalog/pg_proc.dat     |  4 ++
 src/include/commands/async.h        |  4 +-
 src/include/nodes/parsenodes.h      | 12 +++--
 src/test/regress/expected/async.out | 21 +++++++++
 src/test/regress/sql/async.sql      | 10 ++++
 15 files changed, 257 insertions(+), 61 deletions(-)

diff --git a/contrib/tcn/tcn.c b/contrib/tcn/tcn.c
index 5355a64c5e..b80337a5ce 100644
--- a/contrib/tcn/tcn.c
+++ b/contrib/tcn/tcn.c
@@ -161,7 +161,7 @@ triggered_change_notification(PG_FUNCTION_ARGS)
 					strcpy_quoted(payload, SPI_getvalue(trigtuple, tupdesc, colno), '\'');
 				}
 
-				Async_Notify(channel, payload->data);
+				Async_Notify(channel, payload->data, true);
 			}
 			ReleaseSysCache(indexTuple);
 			break;
diff --git a/doc/src/sgml/ref/notify.sgml b/doc/src/sgml/ref/notify.sgml
index e0e125a2a2..e06b00f1f3 100644
--- a/doc/src/sgml/ref/notify.sgml
+++ b/doc/src/sgml/ref/notify.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-NOTIFY <replaceable class="parameter">channel</replaceable> [ , <replaceable class="parameter">payload</replaceable> ]
+NOTIFY [ ( option [, ...] ) ] <replaceable class="parameter">channel</replaceable> [ , <replaceable class="parameter">payload</replaceable> ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
+
+COLLAPSE [ boolean ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -47,10 +51,10 @@ NOTIFY <replaceable class="parameter">channel</replaceable> [ , <replaceable cla
   </para>
 
   <para>
-   The information passed to the client for a notification event includes the
-   notification channel
-   name, the notifying session's server process <acronym>PID</acronym>, and the
-   payload string, which is an empty string if it has not been specified.
+   The information passed to the client for a notification event includes
+   the notification channel name, the notifying session's server process
+   <acronym>PID</acronym>, and the payload string, which is an empty string
+   if it has not been specified.
   </para>
 
   <para>
@@ -92,21 +96,13 @@ NOTIFY <replaceable class="parameter">channel</replaceable> [ , <replaceable cla
    is that applications using <command>NOTIFY</command> for real-time signaling
    should try to keep their transactions short.
   </para>
-
   <para>
-   If the same channel name is signaled multiple times from the same
-   transaction with identical payload strings, the
-   database server can decide to deliver a single notification only.
-   On the other hand, notifications with distinct payload strings will
-   always be delivered as distinct notifications. Similarly, notifications from
-   different transactions will never get folded into one notification.
    Except for dropping later instances of duplicate notifications,
    <command>NOTIFY</command> guarantees that notifications from the same
    transaction get delivered in the order they were sent.  It is also
-   guaranteed that messages from different transactions are delivered in
-   the order in which the transactions committed.
+   guaranteed that messages from different transactions are delivered
+   in the order in which the transactions committed.
   </para>
-
   <para>
    It is common for a client that executes <command>NOTIFY</command>
    to be listening on the same notification channel itself.  In that case
@@ -147,6 +143,21 @@ NOTIFY <replaceable class="parameter">channel</replaceable> [ , <replaceable cla
      </para>
     </listitem>
    </varlistentry>
+   <varlistentry>
+    <term><replaceable class="parameter">COLLAPSE</replaceable></term>
+    <listitem>
+     <para>
+      Controls collapsing of repeated messages.
+
+      When set to <literal>on</literal>, notification will be skipped if
+      the same channel was already signaled with identical payload in the same
+      transaction block (finished with <literal>COMMIT</literal>,
+      <literal>END</literal> or <literal>SAVEPOINT</literal>).
+      
+      When set to <literal>off</literal>, duplicates will not be collapsed.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
@@ -190,6 +201,13 @@ NOTIFY <replaceable class="parameter">channel</replaceable> [ , <replaceable cla
     to use than the <command>NOTIFY</command> command if you need to work with
     non-constant channel names and payloads.
    </para>
+   <para>
+    There is a three-argument version,
+    <literal><function>pg_notify</function>(<type>text</type>,
+    <type>text</type>, <type>boolean</type>)</literal>. The third argument
+    corresponds to the <replaceable class="parameter">COLLAPSE</replaceable>
+    option of the <literal>NOTIFY</literal> command. 
+   </para>
   </refsect2>
  </refsect1>
 
@@ -210,6 +228,22 @@ Asynchronous notification "virtual" with payload "This is the payload" received
 LISTEN foo;
 SELECT pg_notify('fo' || 'o', 'pay' || 'load');
 Asynchronous notification "foo" with payload "payload" received from server process with PID 14728.
+
+/* Identical messages from same (sub-) transaction will be eliminated,
+   unless you use COLLAPSE off */
+LISTEN bar;
+BEGIN;
+NOTIFY bar, 'Coffee please';
+NOTIFY bar, 'Coffee please';
+NOTIFY bar, 'Milk please';
+NOTIFY (COLLAPSE off) bar, 'Milk please';
+SAVEPOINT s;
+NOTIFY bar, 'Coffee please';
+COMMIT;
+Asynchronous notification "bar" with payload "Coffee please" received from server process with PID 31517.
+Asynchronous notification "bar" with payload "Milk please" received from server process with PID 31517.
+Asynchronous notification "bar" with payload "Milk please" received from server process with PID 31517.
+Asynchronous notification "bar" with payload "Coffee please" received from server process with PID 31517.
 </programlisting></para>
  </refsect1>
 
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 5a7ee0de4c..0f1280b3f5 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -49,12 +49,14 @@
  * 4. The NOTIFY statement (routine Async_Notify) stores the notification in
  *	  a backend-local list which will not be processed until transaction end.
  *
- *	  Duplicate notifications from the same transaction are sent out as one
- *	  notification only. This is done to save work when for example a trigger
- *	  on a 2 million row table fires a notification for each row that has been
- *	  changed. If the application needs to receive every single notification
- *	  that has been sent, it can easily add some unique string into the extra
- *	  payload parameter.
+ *	  By default (COLLAPSE on), duplicate notifications from the
+ *	  same transaction are sent out as one notification only. This is done to
+ *	  save work when for example a trigger on a 2 million row table fires a
+ *	  notification for each row that has been changed. If the application needs
+ *	  to receive every single notification that has been sent, it can easily
+ *	  add some unique string into the extra payload parameter.
+ *
+ *	  If COLLAPSE off, de-duplication is not performed.
  *
  *	  When the transaction is ready to commit, PreCommit_Notify() adds the
  *	  pending notifications to the head of the queue. The head pointer of the
@@ -123,6 +125,7 @@
 #include "access/xact.h"
 #include "catalog/pg_database.h"
 #include "commands/async.h"
+#include "commands/defrem.h"
 #include "funcapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
@@ -523,7 +526,42 @@ pg_notify(PG_FUNCTION_ARGS)
 	/* For NOTIFY as a statement, this is checked in ProcessUtility */
 	PreventCommandDuringRecovery("NOTIFY");
 
-	Async_Notify(channel, payload);
+	Async_Notify(channel, payload, true);
+
+	PG_RETURN_VOID();
+}
+
+
+/*
+ * pg_notify_3args
+ *    SQL function to send a notification event, 3-argument version
+ */
+Datum
+pg_notify_3args(PG_FUNCTION_ARGS)
+{
+	const char		*channel;
+	const char		*payload;
+	bool			 collapse;
+
+	if (PG_ARGISNULL(0))
+		channel = "";
+	else
+		channel = text_to_cstring(PG_GETARG_TEXT_PP(0));
+
+	if (PG_ARGISNULL(1))
+		payload = "";
+	else
+		payload = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+	if (PG_ARGISNULL(2))
+		collapse = true;
+	else
+		collapse = PG_GETARG_BOOL(2);
+
+	/* For NOTIFY as a statement, this is checked in ProcessUtility */
+	PreventCommandDuringRecovery("NOTIFY");
+
+	Async_Notify(channel, payload, collapse);
 
 	PG_RETURN_VOID();
 }
@@ -539,10 +577,10 @@ pg_notify(PG_FUNCTION_ARGS)
  *		^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  */
 void
-Async_Notify(const char *channel, const char *payload)
+Async_Notify(const char *channel, const char *payload, bool collapse)
 {
-	Notification *n;
-	MemoryContext oldcontext;
+	Notification	*n;
+	MemoryContext	 oldcontext;
 
 	if (IsParallelWorker())
 		elog(ERROR, "cannot send notifications from a parallel worker");
@@ -550,7 +588,7 @@ Async_Notify(const char *channel, const char *payload)
 	if (Trace_notify)
 		elog(DEBUG1, "Async_Notify(%s)", channel);
 
-	/* a channel name must be specified */
+	/* channel name must be specified */
 	if (!channel || !strlen(channel))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -569,9 +607,10 @@ Async_Notify(const char *channel, const char *payload)
 					 errmsg("payload string too long")));
 	}
 
-	/* no point in making duplicate entries in the list ... */
-	if (AsyncExistsPendingNotify(channel, payload))
-		return;
+	if (collapse)
+		/* remove duplicate entries in the list */
+		if (AsyncExistsPendingNotify(channel, payload))
+			return;
 
 	/*
 	 * The notification list needs to live until end of transaction, so store
@@ -595,6 +634,32 @@ Async_Notify(const char *channel, const char *payload)
 	MemoryContextSwitchTo(oldcontext);
 }
 
+void
+Async_Notify_WithOptions(const char *channel,
+		const char *payload, List *options)
+{
+	ListCell		*lc;
+
+	bool			 collapse;
+
+	collapse = true;
+
+	/* Parse options list. */
+	foreach(lc, options)
+	{
+		DefElem *opt = (DefElem *) lfirst(lc);
+
+		if (strcmp(opt->defname, "collapse") == 0)
+			collapse = defGetBoolean(opt);
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("unrecognized NOTIFY option \"%s\"",
+							opt->defname)));
+	}
+	Async_Notify(channel, payload, collapse);
+}
+
 /*
  * queue_listen
  *		Common code for listen, unlisten, unlisten all commands.
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a8a735c247..df91526429 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3623,8 +3623,9 @@ _copyNotifyStmt(const NotifyStmt *from)
 {
 	NotifyStmt *newnode = makeNode(NotifyStmt);
 
-	COPY_STRING_FIELD(conditionname);
+	COPY_STRING_FIELD(channel);
 	COPY_STRING_FIELD(payload);
+	COPY_NODE_FIELD(options);
 
 	return newnode;
 }
@@ -3634,7 +3635,7 @@ _copyListenStmt(const ListenStmt *from)
 {
 	ListenStmt *newnode = makeNode(ListenStmt);
 
-	COPY_STRING_FIELD(conditionname);
+	COPY_STRING_FIELD(channel);
 
 	return newnode;
 }
@@ -3644,7 +3645,7 @@ _copyUnlistenStmt(const UnlistenStmt *from)
 {
 	UnlistenStmt *newnode = makeNode(UnlistenStmt);
 
-	COPY_STRING_FIELD(conditionname);
+	COPY_STRING_FIELD(channel);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3cab90e9f8..3db593e63c 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1480,8 +1480,9 @@ _equalRuleStmt(const RuleStmt *a, const RuleStmt *b)
 static bool
 _equalNotifyStmt(const NotifyStmt *a, const NotifyStmt *b)
 {
-	COMPARE_STRING_FIELD(conditionname);
+	COMPARE_STRING_FIELD(channel);
 	COMPARE_STRING_FIELD(payload);
+	COMPARE_NODE_FIELD(options);
 
 	return true;
 }
@@ -1489,7 +1490,7 @@ _equalNotifyStmt(const NotifyStmt *a, const NotifyStmt *b)
 static bool
 _equalListenStmt(const ListenStmt *a, const ListenStmt *b)
 {
-	COMPARE_STRING_FIELD(conditionname);
+	COMPARE_STRING_FIELD(channel);
 
 	return true;
 }
@@ -1497,7 +1498,7 @@ _equalListenStmt(const ListenStmt *a, const ListenStmt *b)
 static bool
 _equalUnlistenStmt(const UnlistenStmt *a, const UnlistenStmt *b)
 {
-	COMPARE_STRING_FIELD(conditionname);
+	COMPARE_STRING_FIELD(channel);
 
 	return true;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 69179a07c3..bebdd0a9e6 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2661,8 +2661,9 @@ _outNotifyStmt(StringInfo str, const NotifyStmt *node)
 {
 	WRITE_NODE_TYPE("NOTIFY");
 
-	WRITE_STRING_FIELD(conditionname);
+	WRITE_STRING_FIELD(channel);
 	WRITE_STRING_FIELD(payload);
+	WRITE_NODE_FIELD(options);
 }
 
 static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 4b845b1bb7..dfe64b5fec 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -296,8 +296,9 @@ _readNotifyStmt(void)
 {
 	READ_LOCALS(NotifyStmt);
 
-	READ_STRING_FIELD(conditionname);
+	READ_STRING_FIELD(channel);
 	READ_STRING_FIELD(payload);
+	READ_NODE_FIELD(options);
 
 	READ_DONE();
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e23e68fdb3..582c2ef692 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -524,7 +524,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean> opt_varying opt_timezone opt_no_inherit
 
 %type <ival>	Iconst SignedIconst
-%type <str>		Sconst comment_text notify_payload
+%type <str>		Sconst comment_text
+%type <str>		notify_option_name
+%type <node>	notify_option_arg
+%type <defelt>	notify_option_elem
+%type <list>	notify_option_list
+
 %type <str>		RoleId opt_boolean_or_string
 %type <list>	var_list
 %type <str>		ColId ColLabel var_name type_function_name param_name
@@ -9741,24 +9746,73 @@ opt_instead:
  *
  *****************************************************************************/
 
-NotifyStmt: NOTIFY ColId notify_payload
+NotifyStmt:
+		NOTIFY ColId
+			{
+				NotifyStmt *n = makeNode(NotifyStmt);
+				n->channel = $2;
+				n->payload = NULL;
+				n->options = NIL;
+				$$ = (Node *)n;
+			}
+		| NOTIFY ColId ',' Sconst
+			{
+				NotifyStmt *n = makeNode(NotifyStmt);
+				n->channel = $2;
+				n->payload = $4;
+				n->options = NIL;
+				$$ = (Node *)n;
+			}
+		| NOTIFY '(' notify_option_list ')' ColId
+			{
+				NotifyStmt *n = makeNode(NotifyStmt);
+				n->options = $3;
+				n->channel = $5;
+				n->payload = NULL;
+				$$ = (Node *)n;
+			}
+		| NOTIFY '(' notify_option_list ')' ColId ',' Sconst
+			{
+				NotifyStmt *n = makeNode(NotifyStmt);
+				n->options = $3;
+				n->channel = $5;
+				n->payload = $7;
+				$$ = (Node *)n;
+			}
+		;
+
+notify_option_list:
+			notify_option_elem
 				{
-					NotifyStmt *n = makeNode(NotifyStmt);
-					n->conditionname = $2;
-					n->payload = $3;
-					$$ = (Node *)n;
+					$$ = list_make1($1);
+				}
+			| notify_option_list ',' notify_option_elem
+				{
+					$$ = lappend($1, $3);
 				}
 		;
 
-notify_payload:
-			',' Sconst							{ $$ = $2; }
-			| /*EMPTY*/							{ $$ = NULL; }
+notify_option_elem:
+			notify_option_name notify_option_arg
+				{
+					$$ = makeDefElem($1, $2, @1);
+				}
+		;
+
+notify_option_name:
+			NonReservedWord			{ $$ = $1; }
+		;
+
+notify_option_arg:
+			opt_boolean_or_string		{ $$ = (Node *) makeString($1); }
+			| NumericOnly				{ $$ = (Node *) $1; }
+			| /* EMPTY */				{ $$ = NULL; }
 		;
 
 ListenStmt: LISTEN ColId
 				{
 					ListenStmt *n = makeNode(ListenStmt);
-					n->conditionname = $2;
+					n->channel = $2;
 					$$ = (Node *)n;
 				}
 		;
@@ -9767,13 +9821,13 @@ UnlistenStmt:
 			UNLISTEN ColId
 				{
 					UnlistenStmt *n = makeNode(UnlistenStmt);
-					n->conditionname = $2;
+					n->channel = $2;
 					$$ = (Node *)n;
 				}
 			| UNLISTEN '*'
 				{
 					UnlistenStmt *n = makeNode(UnlistenStmt);
-					n->conditionname = NULL;
+					n->channel = NULL;
 					$$ = (Node *)n;
 				}
 		;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6ec795f1b4..d6f8d09174 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -611,7 +611,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				NotifyStmt *stmt = (NotifyStmt *) parsetree;
 
 				PreventCommandDuringRecovery("NOTIFY");
-				Async_Notify(stmt->conditionname, stmt->payload);
+				Async_Notify_WithOptions(stmt->channel, stmt->payload, stmt->options);
 			}
 			break;
 
@@ -621,7 +621,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 
 				PreventCommandDuringRecovery("LISTEN");
 				CheckRestrictedOperation("LISTEN");
-				Async_Listen(stmt->conditionname);
+				Async_Listen(stmt->channel);
 			}
 			break;
 
@@ -631,8 +631,8 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 
 				/* we allow UNLISTEN during recovery, as it's a noop */
 				CheckRestrictedOperation("UNLISTEN");
-				if (stmt->conditionname)
-					Async_Unlisten(stmt->conditionname);
+				if (stmt->channel)
+					Async_Unlisten(stmt->channel);
 				else
 					Async_UnlistenAll();
 			}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 85055bbb95..1300347387 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -6581,7 +6581,7 @@ get_utility_query_def(Query *query, deparse_context *context)
 		appendContextKeyword(context, "",
 							 0, PRETTYINDENT_STD, 1);
 		appendStringInfo(buf, "NOTIFY %s",
-						 quote_identifier(stmt->conditionname));
+						 quote_identifier(stmt->channel));
 		if (stmt->payload)
 		{
 			appendStringInfoString(buf, ", ");
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 562c5408f8..94dbe5c70a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7554,6 +7554,10 @@
   proname => 'pg_notify', proisstrict => 'f', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'text text',
   prosrc => 'pg_notify' },
+{ oid => '3998', descr => 'send a notification event',
+  proname => 'pg_notify', proisstrict => 'f', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'text text bool',
+  prosrc => 'pg_notify_3args' },
 { oid => '3296',
   descr => 'get the fraction of the asynchronous notification queue currently in use',
   proname => 'pg_notification_queue_usage', provolatile => 'v',
diff --git a/src/include/commands/async.h b/src/include/commands/async.h
index cfea78e039..36b39ff8bd 100644
--- a/src/include/commands/async.h
+++ b/src/include/commands/async.h
@@ -16,6 +16,7 @@
 #include <signal.h>
 
 #include "fmgr.h"
+#include "nodes/parsenodes.h"
 
 /*
  * The number of SLRU page buffers we use for the notification queue.
@@ -33,7 +34,8 @@ extern void NotifyMyFrontEnd(const char *channel,
 				 int32 srcPid);
 
 /* notify-related SQL statements */
-extern void Async_Notify(const char *channel, const char *payload);
+extern void Async_Notify(const char *channel, const char *payload, bool collapse);
+extern void Async_Notify_WithOptions(const char *channel, const char *payload, List *options);
 extern void Async_Listen(const char *channel);
 extern void Async_Unlisten(const char *channel);
 extern void Async_UnlistenAll(void);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index fe35783359..94f24568e9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -27,6 +27,7 @@
 #include "nodes/primnodes.h"
 #include "nodes/value.h"
 #include "partitioning/partdefs.h"
+#include "commands/async.h"
 
 
 typedef enum OverridingKind
@@ -2947,9 +2948,10 @@ typedef struct RuleStmt
  */
 typedef struct NotifyStmt
 {
-	NodeTag		type;
-	char	   *conditionname;	/* condition name to notify */
-	char	   *payload;		/* the payload string, or NULL if none */
+	NodeTag	 type;
+	char	*channel;	/* channel name to notify */
+	char	*payload;	/* the payload string, or NULL if none */
+	List	*options;	/* List of DefElem nodes */
 } NotifyStmt;
 
 /* ----------------------
@@ -2959,7 +2961,7 @@ typedef struct NotifyStmt
 typedef struct ListenStmt
 {
 	NodeTag		type;
-	char	   *conditionname;	/* condition name to listen on */
+	char	   *channel;		/* channel name to listen on */
 } ListenStmt;
 
 /* ----------------------
@@ -2969,7 +2971,7 @@ typedef struct ListenStmt
 typedef struct UnlistenStmt
 {
 	NodeTag		type;
-	char	   *conditionname;	/* name to unlisten on, or NULL for all */
+	char	   *channel;		/* channel name to unlisten, or NULL for all */
 } UnlistenStmt;
 
 /* ----------------------
diff --git a/src/test/regress/expected/async.out b/src/test/regress/expected/async.out
index 19cbe38e63..6d5726127c 100644
--- a/src/test/regress/expected/async.out
+++ b/src/test/regress/expected/async.out
@@ -8,6 +8,18 @@ SELECT pg_notify('notify_async1','sample message1');
  
 (1 row)
 
+SELECT pg_notify('notify_async1','sample_message1',true);
+ pg_notify 
+-----------
+ 
+(1 row)
+
+SELECT pg_notify('notify_async1','sample_message1',false);
+ pg_notify 
+-----------
+ 
+(1 row)
+
 SELECT pg_notify('notify_async1','');
  pg_notify 
 -----------
@@ -29,9 +41,18 @@ SELECT pg_notify('notify_async_channel_name_too_long____________________________
 ERROR:  channel name too long
 --Should work. Valid NOTIFY/LISTEN/UNLISTEN commands
 NOTIFY notify_async2;
+NOTIFY (collapse off) notify_async2;
+NOTIFY notify_async2, '';
+NOTIFY (collapse on) notify_async2, '';
+NOTIFY (collapse off) notify_async2, '';
 LISTEN notify_async2;
 UNLISTEN notify_async2;
 UNLISTEN *;
+-- Should fail. Invalid option syntax
+NOTIFY (collapse maybe) notify_async2;
+ERROR:  collapse requires a Boolean value
+NOTIFY (wrong opt) notify_async2, 'test';
+ERROR:  unrecognized NOTIFY option "wrong"
 -- Should return zero while there are no pending notifications.
 -- src/test/isolation/specs/async-notify.spec tests for actual usage.
 SELECT pg_notification_queue_usage();
diff --git a/src/test/regress/sql/async.sql b/src/test/regress/sql/async.sql
index 40f6e01538..29f9681d2a 100644
--- a/src/test/regress/sql/async.sql
+++ b/src/test/regress/sql/async.sql
@@ -4,6 +4,8 @@
 
 --Should work. Send a valid message via a valid channel name
 SELECT pg_notify('notify_async1','sample message1');
+SELECT pg_notify('notify_async1','sample_message1',true);
+SELECT pg_notify('notify_async1','sample_message1',false);
 SELECT pg_notify('notify_async1','');
 SELECT pg_notify('notify_async1',NULL);
 
@@ -14,10 +16,18 @@ SELECT pg_notify('notify_async_channel_name_too_long____________________________
 
 --Should work. Valid NOTIFY/LISTEN/UNLISTEN commands
 NOTIFY notify_async2;
+NOTIFY (collapse off) notify_async2;
+NOTIFY notify_async2, '';
+NOTIFY (collapse on) notify_async2, '';
+NOTIFY (collapse off) notify_async2, '';
 LISTEN notify_async2;
 UNLISTEN notify_async2;
 UNLISTEN *;
 
+-- Should fail. Invalid option syntax
+NOTIFY (collapse maybe) notify_async2;
+NOTIFY (wrong opt) notify_async2, 'test';
+
 -- Should return zero while there are no pending notifications.
 -- src/test/isolation/specs/async-notify.spec tests for actual usage.
 SELECT pg_notification_queue_usage();