关于 table Common Lisp 与后现代包的更改的客户端通知

client side notification on table change in Common Lisp with Postmodern package

虽然在谷歌上搜索并寻找了很长一段时间的示例,但我找不到任何关于当数据库 table 更改时是否可以在客户端应用程序中获得通知“回调”的指示.

该功能在 postgresql C 库中受支持(至少在某种程度上),因此数据库本身似乎支持此用例。

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));

基于上面给出的 table,我创建了一个小的 Lisp 包,它允许发布一些服务(这是一个玩具服务发现,由数据库支持),使用 postmodern postgresql包。

当然,除了广告,应用也会想要等待/搜索主动服务。由于轮询显然不是可行的方法,因此需要某种形式的通知。在 C API 级别,应用程序可以使用 select() 等待套接字,连接到数据库和 PQNotify() 或类似的东西。

我的问题是,如果 postmodern 也支持这个用例,我正在寻找一个例子。

这是我目前所做的 LISP 包。

(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))

对于搜索部分,我喜欢类似这样的函数:

(defun with-services (filters continuation)
  ; ...
)

其中 filters 允许查找例如服务名称和属性(对于问题来说并不重要,真的),一旦所有必需的服务都可用,就会调用 continuation 函数,其中包含相关服务的行列表。

with-services 函数可以阻塞直到所需的每个服务都存在,或者它可以异步执行。

想法,是否可以在不扩展后现代包的情况下实现?它会是什么样子?

更新

由于找不到文档,我深入研究了源代码级别并在文件 protocol.lisp 中找到了如何处理通知:

(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))))

所以在我尚未受过良好训练的 LISP 眼睛看来,通知以某种方式映射到某种异常。在其他语言中,这通常是一种代码味道。这是否意味着通知处理是一个隐藏的非功能,或者这是在 Common Lisp 中做事的惯用方式?

Common Lisp 条件也不例外,因为向它们发出信号并不会展开堆栈。这是 Common Lisp 条件系统的自然使用,是 C++ 的流行的仅抛出异常系统无法实现的/Java/Python。

函数 WARN 发出条件信号,允许匹配条件类型 POSTGRESQL-NOTIFICATION 的条件处理程序触发,然后调用 MUFFLE-WARNING 重新启动以继续执行。 WARN 用于在警告未被 MUFFLE-WARNING 处理的情况下向标准输出打印警告消息。

让我们考虑以下代码:

(labels ((handle-notification-p (condition)
           (let ((pid (postgresql-notification-pid condition))
                 (channel (postgresql-notification-channel condition))
                 (payload (postgresql-notification-payload condition)))
             ...)) ;; return true if we want to handle this notification
         (handle-notification (condition)
           (when (handle-notification-p condition)
             ... ;; perform actions to handle the notifications
             (invoke-restart 'muffle-warning condition))))
  (handler-bind ((postgresql-notification #'handle-notification))
    ...)) ;; application logic goes here

此代码建立一个处理通知条件的处理程序。它有三个地方需要填写。

第一个应该 return true 当且仅当条件应该由这个处理程序处理。第二个 - 在 return 将控制权控制到警告站点之前,应该执行哪些步骤来处理这种情况。第三个应该包含应用程序逻辑,其中包含一些处理与 Postgres 通信的服务器代码。

所有这一切都是在不展开堆栈的情况下发生的(除非某些其他条件处理程序在其他地方明确执行非本地退出)。如果通知已处理,将从 WARN 站点继续执行,如果未处理,它将继续向标准输出输出警告。