While googling and looking for examples for quite a while, I could not find any indication as to whether or not it is possible to get a notification "callback" in client applications, when a database table changes.
The feature is supported (somewhat at least) in the postgresql C library, so the database itself seems to support this use case.
CREATE TABLE IF NOT EXISTS disco (
instance TEXT NOT NULL,
service TEXT NOT NULL,
attributes TEXT[] DEFAULT '{}',
endpoints TEXT[],
CONSTRAINT service_instance PRIMARY KEY(instance,service));
Based on the table given above, I created a small Lisp package, which allows advertising some services (it is a toy service discovery, backed by a database), using the postmodern postgresql package.
Of course, aside from advertising, applications will also want to wait for / search for active services. And since polling is clearly not the way to go, some form of notification is needed. On C API level, applications can use select() to wait for the socket, connected to the database and PQNotify() or something like that.
My question is, if postmodern also supports this use case and I am looking for an example.
Here the LISP package as I did it so far.
(ql:quickload :postmodern)
(defpackage :disco
(:use :common-lisp :postmodern)
(:export
:connect-toplevel
:ensure-disco-table
:advertise
:stop-advertise
:advertised)
(:shadow :connect-toplevel))
(in-package :disco)
(defun ensure-disco-table ()
(postmodern:query "CREATE TABLE IF NOT EXISTS disco (
instance TEXT NOT NULL,
service TEXT NOT NULL,
attributes TEXT[] DEFAULT '{}',
endpoints TEXT[],
CONSTRAINT service_instance PRIMARY KEY(instance,service));"))
(defun connect-toplevel ()
(progn
(postmodern:connect-toplevel "postgres" "postgres" "password" "localhost")
(ensure-disco-table)))
(defun advertise (instance service endpoints &optional attributes)
(let ((attribs (if attributes attributes #())))
(postmodern:query (:insert-into 'disco :set 'instance instance
'service service
'attributes attribs
'endpoints endpoints))))
(defun stop-advertise (instance service)
(postmodern:query (:delete-from 'disco
:where (:and
(:= 'instance instance)
(:= 'service service)))))
(defun advertised (instance service endpoints handler &optional attributes)
(if (advertise instance service endpoints attributes)
(progn
(funcall handler)
(stop-advertise instance service)
T)
nil))
For the searching part, I fancy a function similar to:
(defun with-services (filters continuation)
; ...
)
Where filters allows to look for e.g. service names and attributes (does not matter for the question, really) and once all required services are available, the continuation function is called with a list of rows of the relevant services.
The with-services function can either block until every service needed is there or it could do it asynchronously.
Ideas, if this is possible without extending the postmodern package? How would it look like?
Update
Since I could not find documentation I steeped to source code level and in file protocol.lisp I found how notifications are being handled:
(defun get-notification (socket)
"Read an asynchronous notification message from the socket and
signal a condition for it."
(let ((pid (read-int4 socket))
(channel (read-str socket))
(payload (read-str socket)))
(warn 'postgresql-notification
:pid pid
:channel channel
:payload payload
:format-control "Asynchronous notification ~S~@[ (payload: ~S)~]
received from ~ server process with PID ~D."
:format-arguments (list channel payload pid))))
So it appears to my not yet well trained LISP eyes, that notifications are somehow mapped to some sort of exceptions. In other languages, this is usually a code smell. Does this mean, that notification handling is a hidden non-feature or is this an idiomatic way to do things in Common Lisp?