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 *。这个结构体包含一个应该处于CONNECTION_OK状态的PGconn;如果在获得一个良好的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是通过 typedef 定义的事件过程指针类型,也就是接收 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,都必须注册一次事件过程。一个连接可注册的事件过程数量只受内存限制。函数成功时返回非零值,失败时返回零。当一个 libpq 事件被触发时,
proc参数将被调用。它的内存地址也被用来查找instanceData。name参数被用来在错误消息中引用该事件过程。这个值不能是NULL或一个零长度串。名字串被复制到PGconn中,因此传递进来的东西不需要长期存在。当一个事件发生时,passThrough指针被传递给proc。这个参数可以是NULL。PQsetInstanceData#设置连接
conn的用于过程proc的instanceData为data。它在成功时返回非零值,失败时返回零(只有proc没有被正确地注册在conn中,才可能会失败)。int PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data);
PQinstanceData#返回连接
conn的与过程proc相关的instanceData,如果没有则返回NULL。void *PQinstanceData(const PGconn *conn, PGEventProc proc);
PQresultSetInstanceData#将结果中针对
proc的instanceData设置为data。成功时返回非零值,失败时返回零。(只有当proc未在结果中正确注册时,才可能失败。)int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data);
注意,
data所指的存储不会计入PQresultMemorySize,除非使用PQresultAlloc分配它。(推荐这样做,因为结果销毁时便不必显式释放这部分存储。)PQresultInstanceData#返回结果的与过程
proc相关的instanceData,如果没有则返回NULL。void *PQresultInstanceData(const PGresult *res, PGEventProc proc);
34.14.4. 事件示例 #
下面给出一个示例框架,用于管理与 libpq 连接和结果关联的私有数据。
/* libpq 事件所需的头文件(注意:其中包含 libpq-fe.h) */
#include <libpq-events.h>
/* 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, *res_copy;
PGconn *conn =
PQconnectdb("dbname=postgres options=-csearch_path=");
if (PQstatus(conn) != CONNECTION_OK)
{
/* PQerrorMessage 的结果包含末尾的换行符 */
fprintf(stderr, "%s", PQerrorMessage(conn));
PQfinish(conn);
return 1;
}
/* 在每个需要接收事件的连接上调用一次。
* 向 myEventProc 发送 PGEVT_REGISTER 事件。
*/
if (!PQregisterEventProc(conn, myEventProc, "mydata_proc", NULL))
{
fprintf(stderr, "Cannot register PGEventProc\n");
PQfinish(conn);
return 1;
}
/* 可以取得 conn 的 instanceData */
data = PQinstanceData(conn, myEventProc);
/* 向 myEventProc 发送 PGEVT_RESULTCREATE 事件 */
res = PQexec(conn, "SELECT 1 + 1");
/* 可以取得结果的 instanceData */
data = PQresultInstanceData(res, myEventProc);
/* 使用 PG_COPYRES_EVENTS 时,向 myEventProc 发送 PGEVT_RESULTCOPY 事件 */
res_copy = PQcopyResult(res, PG_COPYRES_TUPLES | PG_COPYRES_EVENTS);
/* 如果调用 PQcopyResult 时使用了 PG_COPYRES_EVENTS,
* 就可以取得结果的 instanceData。
*/
data = PQresultInstanceData(res_copy, myEventProc);
/* 两次清除操作都会向 myEventProc 发送 PGEVT_RESULTDESTROY 事件 */
PQclear(res);
PQclear(res_copy);
/* 向 myEventProc 发送 PGEVT_CONNDESTROY 事件 */
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);
/* 将应用程序特有的数据与连接关联 */
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);
/* 连接正在销毁,因此释放实例数据 */
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);
/* 将应用程序特有的数据与结果关联(从 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);
/* 将应用程序特有的数据与结果关联(从另一个结果复制) */
PQresultSetInstanceData(e->dest, myEventProc, dest_data);
break;
}
case PGEVT_RESULTDESTROY:
{
PGEventResultDestroy *e = (PGEventResultDestroy *)evtInfo;
mydata *data = PQresultInstanceData(e->result, myEventProc);
/* 结果正在销毁,因此释放实例数据 */
if (data)
free_mydata(data);
break;
}
/* 未知的事件 ID,直接返回 true。 */
default:
break;
}
return true; /* 事件处理成功 */
}
报告文档问题
阅读 上游文档. 通过 PostgreSQL 文档反馈表单.