Reputation: 343
I try to create client -> database notification / update system. The client is written in Java / Hibernate. To notify the client i use the trigger:
CREATE OR REPLACE FUNCTION notifyUsers() RETURNS TRIGGER AS $$
DECLARE
data integer;
notification json;
BEGIN
IF (TG_OP = 'DELETE') THEN
data = OLD.id;
ELSE
data = NEW.id;
END IF;
-- Contruct the notification
notification = json_build_object(
'table',TG_TABLE_NAME,
'action', TG_OP,
'id', data);
-- Execute pg_notify(channel, notification)
PERFORM pg_notify('events',notification::text);
-- Result is ignored since this is an AFTER trigger
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER notifyUsersAccountData AFTER INSERT OR UPDATE OR DELETE ON document FOR EACH ROW EXECUTE PROCEDURE notifyUsers();
but when the notification is received by client I cant determine who trigger it. Can I send any additional parameter that would determine who triggers it (session_id? In my application there are custom users but I would like to avoid sending User.id as logging on the same user by 2 PC would break my notification/update system)
After notification is receiver the client updates the value, the "session id" is needed to determine whether "THIS client" made the change (of course in this case the client should not be updated) or someone else (in this case the update should be applied)
EDIT - - -
I would like to refresh this topic as it is not resolved. The solution with calling pg_backend_pid sometimes breaks resulting that "The PID of caller that performs SQL query" is not the same as PGNotification.getPid. The code:
public int getSessionPid() {
int lResult = -1;
try
{
connect();
Session session = _sessionFactory.getCurrentSession();
Transaction lTransaction = session.beginTransaction();
SQLQuery lQuery = session.createSQLQuery("SELECT pg_backend_pid()");
List lResultQuery = lQuery.list();
lResult = Integer.parseInt(lResultQuery.get(0).toString());
lTransaction.commit();
}catch(org.hibernate.SessionException e)
{
e.printStackTrace();
}
return lResult;
}
and the code where comparison appears:
int lPid = Facade.databaseConnector.warehouse.getSessionPid();
for(int i = 0; i < notifications.length; ++i)
{
if(lPid == notifications[i].getPID())
...
Additionally I see that in the table where current backend connections are visible select * from pg_stat_activity
there are 3 connections per one instance of my program.
SELECT 1
- just to be updated with notifications as without "touching" database, notifications wont be triggeredAll the backend connections have different ports and PIDs in the pg_stat_activity.
Any ideas?
Upvotes: 0
Views: 1487
Reputation: 343
It took me a while to polish the solution but I have found really cool one. I wanted to be independent from amount of opened session across one instance of my software and be sure that the instance of the software properly determine whether trigger comes from this instance or not (even if one instance may open more than one session, and on the same PC more than one instances can be opened). And here it is:
SELECT * from pg_stat_activity
EXECUTE 'SELECT application_name from pg_stat_activity where pid IN (SELECT pg_backend_pid())' INTO session_app_name;
notification = json_build_object(
'session', session_app_name, ...);
and when the PC software instance receives the trigger it compares whether local Application_name is the same as that sent in json ('session') and this way I am sure that the trigger was created by action that came from this instance
Upvotes: 0
Reputation: 5950
PostgreSQL documentation for pg_notify seems to give solution to this problem.
It is common for a client that executes NOTIFY to be listening on the same notification channel itself. In that case it will get back a notification event, just like all the other listening sessions. Depending on the application logic, this could result in useless work, for example, reading a database table to find the same updates that that session just wrote out. It is possible to avoid such extra work by noticing whether the notifying session's server process PID (supplied in the notification event message) is the same as one's own session's PID (available from libpq). When they are the same, the notification event is one's own work bouncing back, and can be ignored.
So your client gets payload like this:
SELECT pg_notify('foo', 'payload');
Asynchronous notification of 'foo' received from backend pid 13976
Data: payload
You can just parse that pid out of it. It is kinda like session_id I guess.
You can get your current session pid like this:
SELECT pg_backend_pid();
13976
Upvotes: 1