implementing NOTIFY with message parameter

Started by Andras Kadingerover 20 years ago6 messages
#1Andras Kadinger
bandit@surfnonstop.com
1 attachment(s)

Greetings everyone,

Beginner in PostgreSQL internals, seeking the insight of more experienced.

" (At present, the extra field is unused and will always point to an empty
string.)" - http://www.postgresql.org/docs/8.0/static/libpq-notify.html
regarding PGnotify returned by PQnotifies

I would like to implement the functionality that is the idea behind that
extra message field: I would like to be able to give NOTIFY an extra
string parameter, and I would like that string to end up in that extra
field of that PGnotify structure above.

I have been using PostgreSQL for different projects in the last 5-6 years,
but have never looked under the hood until now. Please guide me.

Pointers to previous relevant discussions would also be kindly welcomed.

----------------

Command/syntax:

Being unfamiliar with flex/yacc/bison, I chose the simplest route, and
added an optional Sconst:

opt_notifymessage:
Sconst { $$ = $1; }
| /*EMPTY*/ { $$ = ""; }

----------------

Storage:

In this scenario, we need to store the message with each notification, so
notifications would no longer be conceptually unique(relname), but
unique(relname,message) instead. As a result, they will need a separate
table. I therefore invented pg_notify:

pg_notify:
NameData relname;
int4 senderpid;
int4 recipientpid;
NameData message; //FIXME: what about type text instead?

and as senderpid now has a place in pg_notify, I removed it from
pg_listener:

pg_listener:
NameData relname;
int4 listenerpid;

----------------

backend/commands/async.c:

Async_Notify - now takes NotifyStmt * as input

AtCommit_Notify - scans pg_listener, and for each tuple that has pending
notifies for its relname, inserts a tuple into pg_notify

ProcessIncomingNotify - now scans pg_notify instead of pg_listener,
delivers notifies sent to that BE PID,
and deletes their tuples

NotifyMyFrontend - now has got a new parameter, char *message, that it
sends to the frontend

----------------

Issues:

I don't know much about string internals in PostgreSQL, so I just
duplicated code for the string type already used in pg_listener: NameData;
but NameData is limited to NAMEDATALEN bytes (64 currently) and for
certain applications this might be a limiting factor. It would be nice to
have something longer - say NOTIFYMESSAGELEN = 256 bytes? I guess it would
be easy to come up with another, size-limited string type, but then again
256 - nevertheless sufficient for my current application, where 64
wouldn't do - is just another arbitrary number, and as such, hard to
objectively reason for or against.

Is text/bpchar/varlena (with its TOASTedness) viable here and for this
purpose? Its capacity would certainly waive that concern.

I am also not very proficient in char* and NameData semantics, storage
issues and accessors - please scrutinize the code in this respect as well.

My modifications leave behavior of LISTEN/NOTIFY mostly unchanged,
but they change the structure and behavior of pg_listener. Should we worry
about this and try to add code to emulate the old behavior?

-----------------

I attached a - never compiled, never tested - patch, as a vehicle to
demonstrate my ideas, solicit further discussion and ask for guidance.

Thank you in advance.

Best Regards,
Andras

Attachments:

notify_with_parameter_01.difftext/plain; charset=US-ASCII; name=notify_with_parameter_01.diffDownload
diff -Nru postgresql-8.0.2-vanilla/src/backend/catalog/Makefile postgresql-8.0.2/src/backend/catalog/Makefile
--- postgresql-8.0.2-vanilla/src/backend/catalog/Makefile	2004-07-21 22:34:45.000000000 +0200
+++ postgresql-8.0.2/src/backend/catalog/Makefile	2005-05-07 05:51:05.840517947 +0200
@@ -30,7 +30,7 @@
 	pg_attrdef.h pg_constraint.h pg_inherits.h pg_index.h \
 	pg_operator.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \
 	pg_language.h pg_largeobject.h pg_aggregate.h pg_statistic.h \
-	pg_rewrite.h pg_trigger.h pg_listener.h pg_description.h pg_cast.h \
+	pg_rewrite.h pg_trigger.h pg_listener.h pg_notify.h pg_description.h pg_cast.h \
 	pg_namespace.h pg_conversion.h pg_database.h pg_shadow.h pg_group.h \
 	pg_tablespace.h pg_depend.h indexing.h \
     )
diff -Nru postgresql-8.0.2-vanilla/src/backend/commands/async.c postgresql-8.0.2/src/backend/commands/async.c
--- postgresql-8.0.2-vanilla/src/backend/commands/async.c	2004-12-31 22:59:41.000000000 +0100
+++ postgresql-8.0.2/src/backend/commands/async.c	2005-05-12 07:49:37.240803594 +0200
@@ -81,6 +81,7 @@
 #include "access/heapam.h"
 #include "catalog/catname.h"
 #include "catalog/pg_listener.h"
+#include "catalog/pg_notify.h"
 #include "commands/async.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
@@ -131,7 +132,7 @@
 static void Async_UnlistenAll(void);
 static void Async_UnlistenOnExit(int code, Datum arg);
 static void ProcessIncomingNotify(void);
-static void NotifyMyFrontEnd(char *relname, int32 listenerPID);
+static void NotifyMyFrontEnd(char *relname, int32 listenerPID, char *message);
 static bool AsyncExistsPendingNotify(const char *relname);
 static void ClearPendingNotifies(void);
 
@@ -152,26 +153,25 @@
  *--------------------------------------------------------------
  */
 void
-Async_Notify(char *relname)
+Async_Notify(NotifyStmt *stmt)
 {
 	if (Trace_notify)
-		elog(DEBUG1, "Async_Notify(%s)", relname);
+		if (stmt->message[0] != '\0')
+			elog(DEBUG1, "Async_Notify(%s,%s)", stmt->relation->relname, stmt->message);
+		else
+			elog(DEBUG1, "Async_Notify(%s)", stmt->relation->relname);
 
-	/* no point in making duplicate entries in the list ... */
-	if (!AsyncExistsPendingNotify(relname))
-	{
-		/*
-		 * The name list needs to live until end of transaction, so store
-		 * it in the transaction context.
-		 */
-		MemoryContext oldcontext;
+	/*
+	 * The notify list needs to live until end of transaction, so store
+	 * it in the transaction context.
+	 */
+	MemoryContext oldcontext;
 
-		oldcontext = MemoryContextSwitchTo(CurTransactionContext);
+	oldcontext = MemoryContextSwitchTo(CurTransactionContext);
 
-		pendingNotifies = lcons(pstrdup(relname), pendingNotifies);
+	pendingNotifies = lcons(stmt, pendingNotifies); //FIXME: shouldn't we use a copy of the statement here?
 
-		MemoryContextSwitchTo(oldcontext);
-	}
+	MemoryContextSwitchTo(oldcontext);
 }
 
 /*
@@ -242,7 +242,7 @@
 	i = 0;
 	values[i++] = (Datum) relname;
 	values[i++] = (Datum) pid;
-	values[i++] = (Datum) 0;	/* no notifies pending */
+	//values[i++] = (Datum) 0;	/* no notifies pending */
 
 	tuple = heap_formtuple(RelationGetDescr(lRel), values, nulls);
 	simple_heap_insert(lRel, tuple);
@@ -433,14 +433,14 @@
 void
 AtCommit_Notify(void)
 {
-	Relation	lRel;
+	Relation	lRel,
+			nRel;
 	TupleDesc	tdesc;
 	HeapScanDesc scan;
 	HeapTuple	lTuple,
-				rTuple;
-	Datum		value[Natts_pg_listener];
-	char		repl[Natts_pg_listener],
-				nulls[Natts_pg_listener];
+				nTuple;
+	Datum		value[Natts_pg_notify];
+	char		nulls[Natts_pg_notify];
 
 	if (pendingNotifies == NIL)
 		return;					/* no NOTIFY statements in this
@@ -459,14 +459,16 @@
 	if (Trace_notify)
 		elog(DEBUG1, "AtCommit_Notify");
 
-	/* preset data to update notify column to MyProcPid */
-	nulls[0] = nulls[1] = nulls[2] = ' ';
-	repl[0] = repl[1] = repl[2] = ' ';
-	repl[Anum_pg_listener_notify - 1] = 'r';
-	value[0] = value[1] = value[2] = (Datum) 0;
-	value[Anum_pg_listener_notify - 1] = Int32GetDatum(MyProcPid);
+	for (i = 0; i < Natts_pg_listener; i++)
+	{
+		nulls[i] = ' ';
+		values[i] = PointerGetDatum(NULL);
+	}
+
+	value[Anum_pg_notify_senderpid - 1] = Int32GetDatum(MyProcPid);
 
 	lRel = heap_openr(ListenerRelationName, ExclusiveLock);
+	nRel = heap_openr(NotifyRelationName, ExclusiveLock);
 	tdesc = RelationGetDescr(lRel);
 	scan = heap_beginscan(lRel, SnapshotNow, 0, NULL);
 
@@ -476,103 +478,61 @@
 		char	   *relname = NameStr(listener->relname);
 		int32		listenerPID = listener->listenerpid;
 
-		if (!AsyncExistsPendingNotify(relname))
-			continue;
-
-		if (listenerPID == MyProcPid)
-		{
-			/*
-			 * Self-notify: no need to bother with table update. Indeed,
-			 * we *must not* clear the notification field in this path, or
-			 * we could lose an outside notify, which'd be bad for
-			 * applications that ignore self-notify messages.
-			 */
+		ListCell *cell;
+		foreach(cell, pendingNotifies) {
+			NotifyStmt *stmt = (NotifyStmt *) lfirst(cell);
 
-			if (Trace_notify)
-				elog(DEBUG1, "AtCommit_Notify: notifying self");
+			if (strncmp(stmt->relation->relname, relname, NAMEDATALEN))
+				continue;
 
-			NotifyMyFrontEnd(relname, listenerPID);
-		}
-		else
-		{
-			if (Trace_notify)
-				elog(DEBUG1, "AtCommit_Notify: notifying pid %d",
-					 listenerPID);
-
-			/*
-			 * If someone has already notified this listener, we don't
-			 * bother modifying the table, but we do still send a SIGUSR2
-			 * signal, just in case that backend missed the earlier signal
-			 * for some reason.  It's OK to send the signal first, because
-			 * the other guy can't read pg_listener until we unlock it.
-			 */
-			if (kill(listenerPID, SIGUSR2) < 0)
+			if (listenerPID == MyProcPid)
 			{
 				/*
-				 * Get rid of pg_listener entry if it refers to a PID that
-				 * no longer exists.  Presumably, that backend crashed
-				 * without deleting its pg_listener entries. This code
-				 * used to only delete the entry if errno==ESRCH, but as
-				 * far as I can see we should just do it for any failure
-				 * (certainly at least for EPERM too...)
+				 * Self-notify: no need to bother with table update. Indeed,
+				 * we *must not* clear the notification field in this path, or
+				 * we could lose an outside notify, which'd be bad for
+				 * applications that ignore self-notify messages.
 				 */
-				simple_heap_delete(lRel, &lTuple->t_self);
+
+				if (Trace_notify)
+					elog(DEBUG1, "AtCommit_Notify: notifying self");
+
+				NotifyMyFrontEnd(relname, listenerPID, message);
 			}
-			else if (listener->notification == 0)
+			else
 			{
-				ItemPointerData ctid;
-				int			result;
-
-				rTuple = heap_modifytuple(lTuple, lRel,
-										  value, nulls, repl);
+				if (Trace_notify)
+					elog(DEBUG1, "AtCommit_Notify: notifying pid %d",
+						 listenerPID);
 
 				/*
-				 * We cannot use simple_heap_update here because the tuple
-				 * could have been modified by an uncommitted transaction;
-				 * specifically, since UNLISTEN releases exclusive lock on
-				 * the table before commit, the other guy could already
-				 * have tried to unlisten.	There are no other cases where
-				 * we should be able to see an uncommitted update or
-				 * delete. Therefore, our response to a
-				 * HeapTupleBeingUpdated result is just to ignore it.  We
-				 * do *not* wait for the other guy to commit --- that
-				 * would risk deadlock, and we don't want to block while
-				 * holding the table lock anyway for performance reasons.
-				 * We also ignore HeapTupleUpdated, which could occur if
-				 * the other guy commits between our heap_getnext and
-				 * heap_update calls.
+				 * If someone has already notified this listener, we don't
+				 * bother modifying the table, but we do still send a SIGUSR2
+				 * signal, just in case that backend missed the earlier signal
+				 * for some reason.  It's OK to send the signal first, because
+				 * the other guy can't read pg_listener until we unlock it.
 				 */
-				result = heap_update(lRel, &lTuple->t_self, rTuple,
-									 &ctid,
-									 GetCurrentCommandId(), InvalidSnapshot,
-									 false /* no wait for commit */ );
-				switch (result)
+				if (kill(listenerPID, SIGUSR2) < 0)
 				{
-					case HeapTupleSelfUpdated:
-						/* Tuple was already updated in current command? */
-						elog(ERROR, "tuple already updated by self");
-						break;
-
-					case HeapTupleMayBeUpdated:
-						/* done successfully */
-
-#ifdef NOT_USED					/* currently there are no indexes */
-						CatalogUpdateIndexes(lRel, rTuple);
-#endif
-						break;
+					/*
+					 * Get rid of pg_listener entry if it refers to a PID that
+					 * no longer exists.  Presumably, that backend crashed
+					 * without deleting its pg_listener entries. This code
+					 * used to only delete the entry if errno==ESRCH, but as
+					 * far as I can see we should just do it for any failure
+					 * (certainly at least for EPERM too...)
+					 */
+					simple_heap_delete(lRel, &lTuple->t_self);
+					//FIXME: we ought to delete pending notifications from pg_notify as well
+				}
+				else // if (listener->notification == 0) // we want to insert each notification
+				{
+					value[Anum_pg_notify_relname - 1] = (Datum) relname;
+					value[Anum_pg_notify_recipientpid - 1] = Int32GetDatum(listenerPID);
+					value[Anum_pg_notify_message - 1] = (Datum) stmt->message;
 
-					case HeapTupleBeingUpdated:
-						/* ignore uncommitted tuples */
-						break;
-
-					case HeapTupleUpdated:
-						/* ignore just-committed tuples */
-						break;
-
-					default:
-						elog(ERROR, "unrecognized heap_update status: %u",
-							 result);
-						break;
+					nTuple = heap_formtuple(RelationGetDescr(nRel), values, nulls);
+					simple_heap_insert(nRel, nTuple);
 				}
 			}
 		}
@@ -587,6 +547,7 @@
 	 * Else they might disregard the signal, which would make the
 	 * application programmer very unhappy.
 	 */
+	heap_close(nRel, NoLock);
 	heap_close(lRel, NoLock);
 
 	ClearPendingNotifies();
@@ -870,8 +831,7 @@
  *		Deal with arriving NOTIFYs from other backends.
  *		This is called either directly from the SIGUSR2 signal handler,
  *		or the next time control reaches the outer idle loop.
- *		Scan pg_listener for arriving notifies, report them to my front end,
- *		and clear the notification field in pg_listener until next time.
+ *		Scan pg_notify for arriving notifies and report them to my front end.
  *
  *		NOTE: since we are outside any transaction, we must create our own.
  * --------------------------------------------------------------
@@ -883,11 +843,7 @@
 	TupleDesc	tdesc;
 	ScanKeyData key[1];
 	HeapScanDesc scan;
-	HeapTuple	lTuple,
-				rTuple;
-	Datum		value[Natts_pg_listener];
-	char		repl[Natts_pg_listener],
-				nulls[Natts_pg_listener];
+	HeapTuple	lTuple;
 	bool		catchup_enabled;
 
 	/* Must prevent SIGUSR1 interrupt while I am running */
@@ -902,53 +858,43 @@
 
 	StartTransactionCommand();
 
-	lRel = heap_openr(ListenerRelationName, ExclusiveLock);
+	lRel = heap_openr(NotifyRelationName, ExclusiveLock);
 	tdesc = RelationGetDescr(lRel);
 
-	/* Scan only entries with my listenerPID */
+	/* Scan only entries with my recipientPID */
 	ScanKeyInit(&key[0],
-				Anum_pg_listener_pid,
+				Anum_pg_notify_recipientpid,
 				BTEqualStrategyNumber, F_INT4EQ,
 				Int32GetDatum(MyProcPid));
 	scan = heap_beginscan(lRel, SnapshotNow, 1, key);
 
-	/* Prepare data for rewriting 0 into notification field */
-	nulls[0] = nulls[1] = nulls[2] = ' ';
-	repl[0] = repl[1] = repl[2] = ' ';
-	repl[Anum_pg_listener_notify - 1] = 'r';
-	value[0] = value[1] = value[2] = (Datum) 0;
-	value[Anum_pg_listener_notify - 1] = Int32GetDatum(0);
-
 	while ((lTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
 	{
-		Form_pg_listener listener = (Form_pg_listener) GETSTRUCT(lTuple);
-		char	   *relname = NameStr(listener->relname);
-		int32		sourcePID = listener->notification;
+		Form_pg_notify notify = (Form_pg_notify) GETSTRUCT(lTuple);
+		char	   *relname = NameStr(notify->relname);
+		int32		sourcePID = notify->senderpid;
+		char	   *message = NameStr(notify->message);
+
+		/* Notify the frontend */
+
+		if (Trace_notify)
+			elog(DEBUG1, "ProcessIncomingNotify: received %s(%s) from %d",
+				 relname, message, (int) sourcePID);
 
-		if (sourcePID != 0)
-		{
-			/* Notify the frontend */
+		NotifyMyFrontEnd(relname, sourcePID, message);
 
-			if (Trace_notify)
-				elog(DEBUG1, "ProcessIncomingNotify: received %s from %d",
-					 relname, (int) sourcePID);
-
-			NotifyMyFrontEnd(relname, sourcePID);
-
-			/*
-			 * Rewrite the tuple with 0 in notification column.
-			 *
-			 * simple_heap_update is safe here because no one else would have
-			 * tried to UNLISTEN us, so there can be no uncommitted
-			 * changes.
-			 */
-			rTuple = heap_modifytuple(lTuple, lRel, value, nulls, repl);
-			simple_heap_update(lRel, &lTuple->t_self, rTuple);
+		/*
+		 * Delete the tuple.
+		 *
+		 * simple_heap_delete is safe here because no one else would have
+		 * tried to process notifies with our PID, so there can be no uncommitted
+		 * changes.
+		 */
+		simple_heap_delete(lRel, &lTuple->t_self);
 
 #ifdef NOT_USED					/* currently there are no indexes */
-			CatalogUpdateIndexes(lRel, rTuple);
+		CatalogUpdateIndexes(lRel, rTuple);
 #endif
-		}
 	}
 	heap_endscan(scan);
 
@@ -982,7 +928,7 @@
  * Send NOTIFY message to my front end.
  */
 static void
-NotifyMyFrontEnd(char *relname, int32 listenerPID)
+NotifyMyFrontEnd(char *relname, int32 listenerPID, char *message)
 {
 	if (whereToSendOutput == Remote)
 	{
@@ -993,8 +939,7 @@
 		pq_sendstring(&buf, relname);
 		if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
 		{
-			/* XXX Add parameter string here later */
-			pq_sendstring(&buf, "");
+			pq_sendstring(&buf, message);
 		}
 		pq_endmessage(&buf);
 
@@ -1006,23 +951,17 @@
 		 */
 	}
 	else
-		elog(INFO, "NOTIFY for %s", relname);
+		if (message[0] != '\0')
+			elog(INFO, "NOTIFY for %s: %s", relname, message);
+		else
+			elog(INFO, "NOTIFY for %s", relname);
 }
 
-/* Does pendingNotifies include the given relname? */
+/* Does pendingNotifies already have a statement identical to the given one? */
 static bool
-AsyncExistsPendingNotify(const char *relname)
+AsyncExistsPendingNotify(NotifyStmt *stmt)
 {
-	ListCell   *p;
-
-	foreach(p, pendingNotifies)
-	{
-		/* Use NAMEDATALEN for relname comparison.	  DZ - 26-08-1996 */
-		if (strncmp((const char *) lfirst(p), relname, NAMEDATALEN) == 0)
-			return true;
-	}
-
-	return false;
+	return list_member(pendingNotifies, stmt);
 }
 
 /* Clear the pendingNotifies list. */
diff -Nru postgresql-8.0.2-vanilla/src/backend/nodes/copyfuncs.c postgresql-8.0.2/src/backend/nodes/copyfuncs.c
--- postgresql-8.0.2-vanilla/src/backend/nodes/copyfuncs.c	2004-12-31 22:59:55.000000000 +0100
+++ postgresql-8.0.2/src/backend/nodes/copyfuncs.c	2005-05-07 05:59:04.948033379 +0200
@@ -1991,6 +1991,7 @@
 	NotifyStmt *newnode = makeNode(NotifyStmt);
 
 	COPY_NODE_FIELD(relation);
+	COPY_STRING_FIELD(message);
 
 	return newnode;
 }
diff -Nru postgresql-8.0.2-vanilla/src/backend/nodes/equalfuncs.c postgresql-8.0.2/src/backend/nodes/equalfuncs.c
--- postgresql-8.0.2-vanilla/src/backend/nodes/equalfuncs.c	2004-12-31 22:59:55.000000000 +0100
+++ postgresql-8.0.2/src/backend/nodes/equalfuncs.c	2005-05-12 07:54:25.612788790 +0200
@@ -1041,6 +1041,7 @@
 _equalNotifyStmt(NotifyStmt *a, NotifyStmt *b)
 {
 	COMPARE_NODE_FIELD(relation);
+	COMPARE_STRING_FIELD(message);
 
 	return true;
 }
diff -Nru postgresql-8.0.2-vanilla/src/backend/nodes/outfuncs.c postgresql-8.0.2/src/backend/nodes/outfuncs.c
--- postgresql-8.0.2-vanilla/src/backend/nodes/outfuncs.c	2004-12-31 22:59:55.000000000 +0100
+++ postgresql-8.0.2/src/backend/nodes/outfuncs.c	2005-05-12 07:09:35.167695542 +0200
@@ -1172,6 +1172,7 @@
 	WRITE_NODE_TYPE("NOTIFY");
 
 	WRITE_NODE_FIELD(relation);
+	WRITE_STRING_FIELD(message);
 }
 
 static void
diff -Nru postgresql-8.0.2-vanilla/src/backend/nodes/readfuncs.c postgresql-8.0.2/src/backend/nodes/readfuncs.c
--- postgresql-8.0.2-vanilla/src/backend/nodes/readfuncs.c	2004-12-31 22:59:55.000000000 +0100
+++ postgresql-8.0.2/src/backend/nodes/readfuncs.c	2005-05-12 07:09:50.317705869 +0200
@@ -169,6 +169,7 @@
 	READ_LOCALS(NotifyStmt);
 
 	READ_NODE_FIELD(relation);
+	READ_STRING_FIELD(message);
 
 	READ_DONE();
 }
diff -Nru postgresql-8.0.2-vanilla/src/backend/parser/gram.y postgresql-8.0.2/src/backend/parser/gram.y
--- postgresql-8.0.2-vanilla/src/backend/parser/gram.y	2004-12-31 23:00:27.000000000 +0100
+++ postgresql-8.0.2/src/backend/parser/gram.y	2005-05-07 06:15:48.957070922 +0200
@@ -3911,14 +3911,20 @@
  *
  *****************************************************************************/
 
-NotifyStmt: NOTIFY qualified_name
+NotifyStmt: NOTIFY qualified_name opt_notifymessage
 				{
 					NotifyStmt *n = makeNode(NotifyStmt);
 					n->relation = $2;
+					n->message = $3;
 					$$ = (Node *)n;
 				}
 		;
 
+opt_notifymessage:
+		 	Sconst	{ $$ = $1; }
+			| /*EMPTY*/	{ $$ = ""; }
+		;
+
 ListenStmt: LISTEN qualified_name
 				{
 					ListenStmt *n = makeNode(ListenStmt);
diff -Nru postgresql-8.0.2-vanilla/src/backend/tcop/utility.c postgresql-8.0.2/src/backend/tcop/utility.c
--- postgresql-8.0.2-vanilla/src/backend/tcop/utility.c	2005-01-24 18:46:29.000000000 +0100
+++ postgresql-8.0.2/src/backend/tcop/utility.c	2005-05-07 05:10:28.696829622 +0200
@@ -779,7 +779,7 @@
 			{
 				NotifyStmt *stmt = (NotifyStmt *) parsetree;
 
-				Async_Notify(stmt->relation->relname);
+				Async_Notify(stmt);
 			}
 			break;
 
diff -Nru postgresql-8.0.2-vanilla/src/include/catalog/catname.h postgresql-8.0.2/src/include/catalog/catname.h
--- postgresql-8.0.2-vanilla/src/include/catalog/catname.h	2004-12-31 23:03:24.000000000 +0100
+++ postgresql-8.0.2/src/include/catalog/catname.h	2005-05-07 05:26:05.552087319 +0200
@@ -32,6 +32,7 @@
 #define  LanguageRelationName "pg_language"
 #define  LargeObjectRelationName "pg_largeobject"
 #define  ListenerRelationName "pg_listener"
+#define  NotifyRelationName "pg_notify"
 #define  NamespaceRelationName "pg_namespace"
 #define  OperatorClassRelationName "pg_opclass"
 #define  OperatorRelationName "pg_operator"
diff -Nru postgresql-8.0.2-vanilla/src/include/catalog/pg_listener.h postgresql-8.0.2/src/include/catalog/pg_listener.h
--- postgresql-8.0.2-vanilla/src/include/catalog/pg_listener.h	2004-12-31 23:03:24.000000000 +0100
+++ postgresql-8.0.2/src/include/catalog/pg_listener.h	2005-05-07 05:46:37.722805287 +0200
@@ -36,7 +36,6 @@
 {
 	NameData	relname;
 	int4		listenerpid;
-	int4		notification;
 } FormData_pg_listener;
 
 /* ----------------
@@ -50,10 +49,9 @@
  *		compiler constants for pg_listener
  * ----------------
  */
-#define Natts_pg_listener						3
+#define Natts_pg_listener						2
 #define Anum_pg_listener_relname				1
 #define Anum_pg_listener_pid					2
-#define Anum_pg_listener_notify					3
 
 /* ----------------
  *		initial contents of pg_listener are NOTHING.
diff -Nru postgresql-8.0.2-vanilla/src/include/catalog/pg_notify.h postgresql-8.0.2/src/include/catalog/pg_notify.h
--- postgresql-8.0.2-vanilla/src/include/catalog/pg_notify.h	1970-01-01 01:00:00.000000000 +0100
+++ postgresql-8.0.2/src/include/catalog/pg_notify.h	2005-05-07 08:00:53.316391409 +0200
@@ -0,0 +1,65 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_notify.h
+ *	  Asynchronous notification
+ *
+ *
+ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_notify.h,v 1.18 2004/12/31 22:03:24 pgsql Exp $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NOTIFY_H
+#define PG_NOTIFY_H
+
+/* ----------------
+ *		postgres.h contains the system type definitions and the
+ *		CATALOG(), BOOTSTRAP and DATA() sugar words so this file
+ *		can be read by both genbki.sh and the C compiler.
+ * ----------------
+ */
+
+/* ----------------------------------------------------------------
+ *		pg_notify definition.
+ *
+ *		cpp turns this into typedef struct FormData_pg_notify
+ * ----------------------------------------------------------------
+ */
+
+CATALOG(pg_notify) BKI_WITHOUT_OIDS
+{
+	NameData	relname;
+	int4		senderpid;
+	int4		recipientpid;
+	NameData	message; //FIXME: what about type text instead?
+} FormData_pg_notify;
+
+/* ----------------
+ *		Form_pg_notify corresponds to a pointer to a tuple with
+ *		the format of pg_notify relation.
+ * ----------------
+ */
+typedef FormData_pg_notify *Form_pg_notify;
+
+/* ----------------
+ *		compiler constants for pg_notify
+ * ----------------
+ */
+#define Natts_pg_notify						4
+#define Anum_pg_notify_relname				1
+#define Anum_pg_notify_senderpid				2
+#define Anum_pg_notify_recipientpid				3
+#define Anum_pg_notify_message					4
+
+/* ----------------
+ *		initial contents of pg_notify are NOTHING.
+ * ----------------
+ */
+
+#endif   /* PG_NOTIFY_H */
diff -Nru postgresql-8.0.2-vanilla/src/include/commands/async.h postgresql-8.0.2/src/include/commands/async.h
--- postgresql-8.0.2-vanilla/src/include/commands/async.h	2004-12-31 23:03:28.000000000 +0100
+++ postgresql-8.0.2/src/include/commands/async.h	2005-05-12 08:46:02.056984528 +0200
@@ -16,7 +16,7 @@
 extern bool Trace_notify;
 
 /* notify-related SQL statements */
-extern void Async_Notify(char *relname);
+extern void Async_Notify(NotifyStmt *stmt);
 extern void Async_Listen(char *relname, int pid);
 extern void Async_Unlisten(char *relname, int pid);
 
diff -Nru postgresql-8.0.2-vanilla/src/include/nodes/parsenodes.h postgresql-8.0.2/src/include/nodes/parsenodes.h
--- postgresql-8.0.2-vanilla/src/include/nodes/parsenodes.h	2004-12-31 23:03:34.000000000 +0100
+++ postgresql-8.0.2/src/include/nodes/parsenodes.h	2005-05-12 07:10:42.282451376 +0200
@@ -1500,6 +1500,7 @@
 {
 	NodeTag		type;
 	RangeVar   *relation;		/* qualified name to notify */
+	char        *message;		/* message to send with notify */
 } NotifyStmt;
 
 /* ----------------------
diff -Nru postgresql-8.0.2-vanilla/src/tools/pgindent/pgindent postgresql-8.0.2/src/tools/pgindent/pgindent
--- postgresql-8.0.2-vanilla/src/tools/pgindent/pgindent	2004-10-07 16:15:50.000000000 +0200
+++ postgresql-8.0.2/src/tools/pgindent/pgindent	2005-05-07 05:49:01.211821875 +0200
@@ -497,6 +497,7 @@
 -TFormData_pg_language \
 -TFormData_pg_largeobject \
 -TFormData_pg_listener \
+-TFormData_pg_notify \
 -TFormData_pg_namespace \
 -TFormData_pg_opclass \
 -TFormData_pg_operator \
@@ -527,6 +528,7 @@
 -TForm_pg_language \
 -TForm_pg_largeobject \
 -TForm_pg_listener \
+-TForm_pg_notify \
 -TForm_pg_namespace \
 -TForm_pg_opclass \
 -TForm_pg_operator \
#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andras Kadinger (#1)
Re: implementing NOTIFY with message parameter

Andras Kadinger <bandit@surfnonstop.com> writes:

... I therefore invented pg_notify:

Please review the archived discussions about reimplementing NOTIFY using
only shared memory, no tables. The pg_listener table causes a number of
performance problems, and I think that getting rid of it should be a top
priority if any work is going to get done in that area. Your proposed
patch would make the performance issues substantially worse by
increasing the volume of dead tuples :-(

Also, any time you are planning new user-visible behavior, it's a good
idea to write the documentation *first*. In particular I'd like to see
a spec for how this looks to a program using libpq: what is the API for
receiving notify messages with arguments? Can an empty-string argument
be distinguished from no argument at all? (Possibly the protocol
prevents that, I'm not sure at the moment.)

regards, tom lane

#3Alvaro Herrera
alvherre@surnet.cl
In reply to: Tom Lane (#2)
Re: implementing NOTIFY with message parameter

On Thu, May 12, 2005 at 10:09:23AM -0400, Tom Lane wrote:

Andras Kadinger <bandit@surfnonstop.com> writes:

... I therefore invented pg_notify:

Please review the archived discussions about reimplementing NOTIFY using
only shared memory, no tables.

Maybe this can be done using two SLRU areas like we did for multixact.
Notifiers create entries, listeners destroy them. Unsolved questions
are how would the listeners know that messages were for them, when would
messages be destroyed if they are multi-recipient, and what performance
issues there are with this approach.

This hammer of mine ... nails, more nails!

--
Alvaro Herrera (<alvherre[a]surnet.cl>)
"Oh, great altar of passive entertainment, bestow upon me thy discordant images
at such speed as to render linear thought impossible" (Calvin a la TV)

#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#3)
Re: implementing NOTIFY with message parameter

Alvaro Herrera <alvherre@surnet.cl> writes:

On Thu, May 12, 2005 at 10:09:23AM -0400, Tom Lane wrote:

Please review the archived discussions about reimplementing NOTIFY using
only shared memory, no tables.

Maybe this can be done using two SLRU areas like we did for multixact.

If you are looking for an existing tool to adapt, I think the sinval
messaging mechanism is a FAR better prototype.

regards, tom lane

#5Alvaro Herrera
alvherre@surnet.cl
In reply to: Tom Lane (#4)
Re: implementing NOTIFY with message parameter

On Thu, May 12, 2005 at 10:48:50AM -0400, Tom Lane wrote:

Alvaro Herrera <alvherre@surnet.cl> writes:

On Thu, May 12, 2005 at 10:09:23AM -0400, Tom Lane wrote:

Please review the archived discussions about reimplementing NOTIFY using
only shared memory, no tables.

Maybe this can be done using two SLRU areas like we did for multixact.

If you are looking for an existing tool to adapt, I think the sinval
messaging mechanism is a FAR better prototype.

Hmm. The problem is that it's easy to do something if the sinval queue
fills up -- just reset everybody's cache. But you can't just drop all
pending notifies if the queue fills up.

A possible approach to that problem would be spilling to disk the
entries of the idle backends.

--
Alvaro Herrera (<alvherre[a]surnet.cl>)
"[PostgreSQL] is a great group; in my opinion it is THE best open source
development communities in existence anywhere." (Lamar Owen)

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#5)
Re: implementing NOTIFY with message parameter

Alvaro Herrera <alvherre@surnet.cl> writes:

On Thu, May 12, 2005 at 10:48:50AM -0400, Tom Lane wrote:

If you are looking for an existing tool to adapt, I think the sinval
messaging mechanism is a FAR better prototype.

Hmm. The problem is that it's easy to do something if the sinval queue
fills up -- just reset everybody's cache. But you can't just drop all
pending notifies if the queue fills up.

I think you can. See previous discussions.

regards, tom lane