Bit*_*ler 3 postgresql sbcl common-lisp
在谷歌搜索和寻找示例很长一段时间后,我找不到任何关于在数据库表更改时是否可以在客户端应用程序中获得通知“回调”的任何指示。
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));
Run Code Online (Sandbox Code Playgroud)
基于上面给出的表,我创建了一个小的 Lisp 包,它允许使用postmodernpostgresql 包为一些服务做广告(它是一个由数据库支持的玩具服务发现)。
当然,除了广告之外,应用程序还需要等待/搜索活动服务。由于轮询显然不是可行的方法,因此需要某种形式的通知。在 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))
Run Code Online (Sandbox Code Playgroud)
对于搜索部分,我喜欢一个类似于:
(defun with-services (filters continuation)
; ...
)
Run Code Online (Sandbox Code Playgroud)
哪里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))))
Run Code Online (Sandbox Code Playgroud)
因此,在我尚未受过良好训练的 LISP 眼中,通知以某种方式映射到某种异常。在其他语言中,这通常是代码异味。这是否意味着通知处理是一个隐藏的非功能,或者这是在 Common Lisp 中做事的惯用方式?
常见的 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
Run Code Online (Sandbox Code Playgroud)
此代码建立将处理通知条件的处理程序。它有三个地方需要填写。
第一个应该返回 true 如果条件应该由这个处理程序处理。第二个 - 在将控制权返回给警告站点之前应该执行哪些步骤来处理这种情况。第三个应该包含应用程序逻辑,其中包含一些处理与 Postgres 通信的服务器代码。
所有这一切都无需展开堆栈即可发生(除非某些其他条件处理程序在其他地方显式执行非本地退出)。WARN如果通知被处理,执行将从站点继续,如果没有处理,它将继续向标准输出打印警告。