34.14. 事件系统#
libpq的事件系统旨在向已注册的事件处理程序发出通知,告知其有关有趣的libpq事件,例如PGconn
和PGresult
对象的创建或销毁。一个主要用例是,这允许应用程序将自己的数据与PGconn
或PGresult
关联,并确保在适当的时间释放该数据。
每个已注册的事件处理程序都与两条数据关联,libpq仅将其视为不透明的void *
指针。有一个传递指针,该指针由应用程序在使用PGconn
注册事件处理程序时提供。传递指针在PGconn
的生命周期内和从其生成的所有PGresult
中均保持不变;因此,如果使用,它必须指向长期数据。此外,还有一个实例数据指针,它在每个PGconn
和PGresult
中都以NULL
开头。可以使用PQinstanceData
、PQsetInstanceData
、PQresultInstanceData
和PQresultSetInstanceData
函数来操作此指针。请注意,与传递指针不同,PGconn
的实例数据不会自动继承自从中创建的PGresult
。libpq不知道传递和实例数据指针指向什么(如果有的话),并且永远不会尝试释放它们 — 这是事件处理程序的责任。
34.14.1. 事件类型#
枚举PGEventId
命名事件系统处理的事件类型。其所有值都以PGEVT
开头的名称命名。对于每个事件类型,都有一个相应的事件信息结构,其中包含传递给事件处理程序的参数。事件类型为
PGEVT_REGISTER
#当调用
PQregisterEventProc
时,会发生注册事件。这是初始化事件过程可能需要的任何instanceData
的理想时机。每个连接的每个事件处理程序只会触发一次注册事件。如果事件过程失败(返回零),则注册将被取消。typedef struct { PGconn *conn; } PGEventRegister;
当收到
PGEVT_REGISTER
事件时,应将evtInfo
指针强制转换为PGEventRegister *
。此结构包含一个PGconn
,它应处于CONNECTION_OK
状态;如果在获得良好的PGconn
后立即调用PQregisterEventProc
,则可以保证这一点。返回失败代码时,必须执行所有清理,因为不会发送PGEVT_CONNDESTROY
事件。PGEVT_CONNRESET
#在完成
PQreset
或PQresetPoll
后,会触发连接重置事件。在这两种情况下,只有在重置成功时才会触发事件。在 PostgreSQL v15 及更高版本中,忽略事件过程的返回值。但是,在早期版本中,返回成功(非零)非常重要,否则连接将被中止。typedef struct { PGconn *conn; } PGEventConnReset;
当收到
PGEVT_CONNRESET
事件时,应将evtInfo
指针强制转换为PGEventConnReset *
。尽管包含的PGconn
刚刚重置,但所有事件数据保持不变。此事件应用于重置/重新加载/重新查询任何关联的instanceData
。请注意,即使事件过程无法处理PGEVT_CONNRESET
,当连接关闭时,它仍会收到PGEVT_CONNDESTROY
事件。PGEVT_CONNDESTROY
#响应
PQfinish
,会触发连接销毁事件。事件过程负责正确清理其事件数据,因为 libpq 无法管理此内存。未能清理将导致内存泄漏。typedef struct { PGconn *conn; } PGEventConnDestroy;
当收到
PGEVT_CONNDESTROY
事件时,应将evtInfo
指针强制转换为PGEventConnDestroy *
。此事件在PQfinish
执行任何其他清理之前触发。忽略事件过程的返回值,因为没有办法从PQfinish
指示失败。此外,事件过程失败不应中止清理不需要的内存的过程。PGEVT_RESULTCREATE
#结果创建事件在响应任何生成结果的查询执行函数时触发,包括
PQgetResult
。此事件仅在成功创建结果后触发。typedef struct { PGconn *conn; PGresult *result; } PGEventResultCreate;
收到
PGEVT_RESULTCREATE
事件时,应将evtInfo
指针强制转换为PGEventResultCreate *
。conn
是用于生成结果的连接。这是初始化任何需要与结果关联的instanceData
的理想位置。如果事件过程失败(返回零),则在结果的剩余生命周期内将忽略该事件过程;也就是说,它不会收到此结果或从中复制的结果的PGEVT_RESULTCOPY
或PGEVT_RESULTDESTROY
事件。PGEVT_RESULTCOPY
#结果复制事件在响应
PQcopyResult
时触发。此事件仅在复制完成后触发。只有成功处理源结果的PGEVT_RESULTCREATE
或PGEVT_RESULTCOPY
事件的事件过程才会收到PGEVT_RESULTCOPY
事件。typedef struct { const PGresult *src; PGresult *dest; } PGEventResultCopy;
收到
PGEVT_RESULTCOPY
事件时,应将evtInfo
指针强制转换为PGEventResultCopy *
。src
结果是已复制的内容,而dest
结果是复制目标。此事件可用于提供instanceData
的深层副本,因为PQcopyResult
无法执行此操作。如果事件过程失败(返回零),则在结果的剩余生命周期内将忽略该事件过程;也就是说,它不会收到该结果或从中复制的结果的PGEVT_RESULTCOPY
或PGEVT_RESULTDESTROY
事件。PGEVT_RESULTDESTROY
#结果销毁事件在响应
PQclear
时触发。事件过程负责正确清理其事件数据,因为 libpq 无法管理此内存。未能清理将导致内存泄漏。typedef struct { PGresult *result; } PGEventResultDestroy;
收到
PGEVT_RESULTDESTROY
事件时,应将evtInfo
指针强制转换为PGEventResultDestroy *
。此事件在PQclear
执行任何其他清理之前触发。事件过程的返回值将被忽略,因为无法从PQclear
指示失败。此外,事件过程失败不应中止清理不需要的内存的过程。
34.14.2. 事件回调过程#
PGEventProc
#PGEventProc
是一个指向事件过程的指针的类型定义,即从 libpq 接收事件的用户回调函数。事件过程的签名必须是int eventproc(PGEventId evtId, void *evtInfo, void *passThrough)
evtId
参数指示发生了哪个PGEVT
事件。evtInfo
指针必须强制转换为适当的结构类型以获取有关该事件的更多信息。passThrough
参数是注册事件过程时提供给PQregisterEventProc
的指针。如果函数成功,则应返回一个非零值;如果失败,则返回零。一个特定的事件过程只能在任何
PGconn
中注册一次。这是因为过程的地址用作查找键以标识关联的实例数据。注意
在 Windows 上,函数可以有两个不同的地址:一个从 DLL 外部可见,另一个从 DLL 内部可见。应该注意,只能将其中一个地址与 libpq 的事件过程函数一起使用,否则会导致混淆。编写有效代码的最简单规则是确保事件过程声明为
static
。如果过程的地址必须在其自己的源文件外部可用,则公开一个单独的函数来返回该地址。
34.14.3. 事件支持函数#
PQregisterEventProc
#使用 libpq 注册事件回调过程。
int PQregisterEventProc(PGconn *conn, PGEventProc proc, const char *name, void *passThrough);
必须在要接收事件的每个
PGconn
上注册一次事件过程。除了内存之外,没有限制可以向连接注册的事件过程的数量。如果函数成功,则返回一个非零值;如果失败,则返回零。proc
参数将在触发 libpq 事件时被调用。其内存地址还用于查找instanceData
。name
参数用于在错误消息中引用事件过程。此值不能为NULL
或零长度字符串。名称字符串被复制到PGconn
中,因此传递的内容不必是长期存在的。passThrough
指针在发生事件时传递给proc
。此参数可以是NULL
。PQsetInstanceData
#将连接
conn
的instanceData
设置为过程proc
的data
。这会返回非零表示成功,零表示失败。(只有当proc
未在conn
中正确注册时才会失败。)int PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data);
PQinstanceData
#返回连接
conn
与过程proc
关联的instanceData
,如果不存在,则返回NULL
。void *PQinstanceData(const PGconn *conn, PGEventProc proc);
PQresultSetInstanceData
#将结果的
instanceData
设置为proc
的data
。成功时返回非零值,失败时返回零值。(只有当proc
未在结果中正确注册时才会失败。)int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data);
请注意,由
data
表示的任何存储都不会被PQresultMemorySize
考虑在内,除非使用PQresultAlloc
分配。(这样做是推荐的,因为它消除了在销毁结果时显式释放此类存储的需要。)PQresultInstanceData
#返回结果的
instanceData
,与proc
关联,如果不存在,则返回NULL
。void *PQresultInstanceData(const PGresult *res, PGEventProc proc);
34.14.4. 事件示例#
以下是管理与 libpq 连接和结果关联的私有数据的框架示例。
/* required header for libpq events (note: includes libpq-fe.h) */
#include <libpq-events.h>
/* The instanceData */
typedef struct
{
int n;
char *str;
} mydata;
/* PGEventProc */
static int myEventProc(PGEventId evtId, void *evtInfo, void *passThrough);
int
main(void)
{
mydata *data;
PGresult *res;
PGconn *conn =
PQconnectdb("dbname=postgres options=-csearch_path=");
if (PQstatus(conn) != CONNECTION_OK)
{
/* PQerrorMessage's result includes a trailing newline */
fprintf(stderr, "%s", PQerrorMessage(conn));
PQfinish(conn);
return 1;
}
/* called once on any connection that should receive events.
* Sends a PGEVT_REGISTER to myEventProc.
*/
if (!PQregisterEventProc(conn, myEventProc, "mydata_proc", NULL))
{
fprintf(stderr, "Cannot register PGEventProc\n");
PQfinish(conn);
return 1;
}
/* conn instanceData is available */
data = PQinstanceData(conn, myEventProc);
/* Sends a PGEVT_RESULTCREATE to myEventProc */
res = PQexec(conn, "SELECT 1 + 1");
/* result instanceData is available */
data = PQresultInstanceData(res, myEventProc);
/* If PG_COPYRES_EVENTS is used, sends a PGEVT_RESULTCOPY to myEventProc */
res_copy = PQcopyResult(res, PG_COPYRES_TUPLES | PG_COPYRES_EVENTS);
/* result instanceData is available if PG_COPYRES_EVENTS was
* used during the PQcopyResult call.
*/
data = PQresultInstanceData(res_copy, myEventProc);
/* Both clears send a PGEVT_RESULTDESTROY to myEventProc */
PQclear(res);
PQclear(res_copy);
/* Sends a PGEVT_CONNDESTROY to myEventProc */
PQfinish(conn);
return 0;
}
static int
myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
{
switch (evtId)
{
case PGEVT_REGISTER:
{
PGEventRegister *e = (PGEventRegister *)evtInfo;
mydata *data = get_mydata(e->conn);
/* associate app specific data with connection */
PQsetInstanceData(e->conn, myEventProc, data);
break;
}
case PGEVT_CONNRESET:
{
PGEventConnReset *e = (PGEventConnReset *)evtInfo;
mydata *data = PQinstanceData(e->conn, myEventProc);
if (data)
memset(data, 0, sizeof(mydata));
break;
}
case PGEVT_CONNDESTROY:
{
PGEventConnDestroy *e = (PGEventConnDestroy *)evtInfo;
mydata *data = PQinstanceData(e->conn, myEventProc);
/* free instance data because the conn is being destroyed */
if (data)
free_mydata(data);
break;
}
case PGEVT_RESULTCREATE:
{
PGEventResultCreate *e = (PGEventResultCreate *)evtInfo;
mydata *conn_data = PQinstanceData(e->conn, myEventProc);
mydata *res_data = dup_mydata(conn_data);
/* associate app specific data with result (copy it from conn) */
PQresultSetInstanceData(e->result, myEventProc, res_data);
break;
}
case PGEVT_RESULTCOPY:
{
PGEventResultCopy *e = (PGEventResultCopy *)evtInfo;
mydata *src_data = PQresultInstanceData(e->src, myEventProc);
mydata *dest_data = dup_mydata(src_data);
/* associate app specific data with result (copy it from a result) */
PQresultSetInstanceData(e->dest, myEventProc, dest_data);
break;
}
case PGEVT_RESULTDESTROY:
{
PGEventResultDestroy *e = (PGEventResultDestroy *)evtInfo;
mydata *data = PQresultInstanceData(e->result, myEventProc);
/* free instance data because the result is being destroyed */
if (data)
free_mydata(data);
break;
}
/* unknown event ID, just return true. */
default:
break;
}
return true; /* event processing succeeded */
}