libpq
的事件系统被设计为向已注册的事件处理器通知有趣的
libpq
事件,
例如
PGconn 和
PGresult
对象的创建或销毁。
一个主要的用例是:
它允许应用程序把自己的数据与一个
PGconn 或
PGresult
关联起来,
并确保这些数据在适当的时刻被释放。
每个已注册的事件处理器都与两块数据关联,
libpq
只把它们当作不透明的
void *
指针看待。
一种是应用程序在向一个
PGconn
注册事件处理器时提供的
直通指针。
在
PGconn
的生命周期以及由它产生的所有
PGresult
中,
直通指针从不改变;
因此如果使用它,
它必须指向长寿命的数据。
此外,
还有一个
实例数据指针,
它在每个
PGconn 和
PGresult
中都初始为
NULL。
这个指针可以用
PQinstanceData、
PQsetInstanceData、
PQresultInstanceData 和
PQresultSetInstanceData
函数操纵。
注意,与直通指针不同,
一个
PGconn
的实例数据不会被由它创建的
PGresult
自动继承。
libpq
不知道直通指针和实例数据指针指向什么
(如果有的话),
并且永远不会尝试释放它们
— 这是事件处理器的责任。
枚举 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
没有任何方式指示失败。
此外,
事件过程的失败也不应当中止清理不需要的内存的过程。
PGEventProc #
PGEventProc
是一个指向事件过程的指针的 typedef,
也就是接收来自 libpq
事件的用户回调函数。
事件过程的签名必须是
int eventproc(PGEventId evtId, void *evtInfo, void *passThrough)
evtId
参数指示发生了哪个
PGEVT 事件。
evtInfo
指针必须被转换为相应的结构类型,
以获得关于该事件的更多信息。
passThrough
参数是在事件过程被注册时提供给
PQregisterEventProc
的指针。
如果成功,
该函数应当返回一个非零值,
失败则返回零。
一个特定的事件过程在任何一条
PGconn
中只能被注册一次。
这是因为该过程的地址被用作查找键来标识相关联的实例数据。
在 Windows 上,
函数可以有两个不同的地址:
一个从 DLL 外部可见,
另一个从 DLL 内部可见。
应当小心确保
libpq
的事件过程函数只使用这些地址中的一个,
否则会产生混淆。
编写能够正常工作的代码的最简单规则是:
确保事件过程被声明为
static。
如果该过程的地址必须在其自身源文件之外可用,
则暴露一个单独的函数来返回该地址。
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);
下面是一个管理 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 */
}