33.13. 事件系统
libpq的事件系统被设计为通知已注册的事件处理器它感兴趣的libpq事件,例如PGconn
以及PGresult
对象的创建和毁灭。一种主要的使用情况是这允许应用将自己的数据与一个PGconn
或者PGresult
关联在一起,并且确保那些数据在适当的时候被释放。
每一个已注册的事件处理器与两部分数据相关,对于libpq它们只是透明的void *
指针。当事件处理器被注册到一个PGconn
时,会有一个应用提供的转移指针。该转移指针在PGconn
及其产生的所有PGresult
的生命期内都不会改变。因此,如果使用它,它必须指向长期存在的数据。此外,还有一个instance data指针,它在每一个PGconn
和PGresult
中都开始于NULL
。这个指针可以使用 PQinstanceData
、 PQsetInstanceData
、 PQresultInstanceData
和 PQsetResultInstanceData
函数操纵。注意和转移指针不同,一个PGconn
的实例数据不会被从它创建的PGresult
自动继承。libpq不知道转移和实例数据指针指向的是什么(如果有),并且将不会尝试释放它们 — 那是事件处理器的责任。
33.13.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
完成时被触发。在两种情况中,只有重置成功才会触发该事件。如果事件过程失败,整个连接重置将失败,PGconn
会被置为CONNECTION_BAD
状态并且PQresetPoll
将返回PGRES_POLLING_FAILED
。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
的理想位置。如果该事件过程失败,结果将被清除并且失败将会被传播。该事件过程不能尝试自己PQclear
结果对象。当返回一个失败代码时,所有清理必须被执行而不会发送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
没法这样做。如果该事件过程失败,整个复制操作将失败并且dest
结果将被清除。当返回一个失败代码时,所有清理必须被执行而不会为目标结果发送PGEVT_RESULTDESTROY
事件。 -
PGEVT_RESULTDESTROY
-
为了响应
PQclear
,结果销毁事件会被触发。由于 libpq 没有能力管理事件数据,事件过程有责任正确地清理它的事件数据。清理失败将会导致内存泄露。typedef struct { PGresult *result; } PGEventResultDestroy;
当接收到一个
PGEVT_RESULTDESTROY
事件时,evtInfo
指针应该被造型为PGEventResultDestroy *
。这个事件在PQclear
执行任何其他清理之前被触发。该事件过程的返回值被忽略,因为没有办法指示来自PQclear
的失败。还有,一个事件过程失败不该中断不需要的内存的清理过程。
33.13.2. 事件回调函数
-
PGEventProc
-
PGEventProc
是到一个事件过程的指针的 typedef,也就是从 libpq 接收事件的用户回调函数。一个事件过程的原型必须是int eventproc(PGEventId evtId, void *evtInfo, void *passThrough)
evtId
指示发生了哪一个PGEVT
事件。evtInfo
指针必须被造型为合适的结构类型才能获得关于事件的进一步信息。当事件过程已被注册时,passThrough
参数是提供给PQregisterEventProc
的指针。如果成功,该函数应该返回非零值,失败则返回零。在任何一个
PGconn
中,一个特定事件过程只能被注册一次。这是因为该过程的地址被用作查找键来标识相关的实例数据。小心
在 Windows 上,函数能够有两个不同的地址:一个对 DLL 之外可见而另一个对 DLL 之内可见。我们应当小心只有其中之一会被用于libpq的事件过程函数,否则将会产生混淆。编写代码的最简单规则是将所有的事件过程声明为
static
。如果过程的地址必须对它自己的源代码文件之外可见,提供一个单独的函数来返回该地址。
33.13.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
没有被正确地注册在conn
中,才可能会失败)。int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data);
-
PQresultInstanceData
-
返回结果的与过程
proc
相关的instanceData
,如果没有则返回NULL
。void *PQresultInstanceData(const PGresult *res, PGEventProc proc);
33.13.4. 事件实例
这里是一个管理与 libpq 连接和结果相关的私有数据的例子的框架。
/* 要求 libpq 事件的头文件(注意:包括 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"); if (PQstatus(conn) != CONNECTION_OK) { fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn)); PQfinish(conn); return 1; } /* 在任何应该接收事件的连接上调用一次。 * 发送一个 PGEVT_REGISTER 给 myEventProc。 */ if (!PQregisterEventProc(conn, myEventProc, "mydata_proc", NULL)) { fprintf(stderr, "Cannot register PGEventProc\n"); PQfinish(conn); return 1; } /* conn 的 instanceData 可用 */ data = PQinstanceData(conn, myEventProc); /* 发送一个 PGEVT_RESULTCREATE 给 myEventProc */ res = PQexec(conn, "SELECT 1 + 1"); /* 结果的 instanceData 可用 */ data = PQresultInstanceData(res, myEventProc); /* 如果使用了 PG_COPYRES_EVENTS,发送一个 PGEVT_RESULTCOPY 给 myEventProc */ res_copy = PQcopyResult(res, PG_COPYRES_TUPLES | PG_COPYRES_EVENTS); /* 如果在 PQcopyResult 调用时使用了 PG_COPYRES_EVENTS,结果的 instanceData 可用。*/ data = PQresultInstanceData(res_copy, myEventProc); /* 两个清除都发送一个 PGEVT_RESULTDESTROY 给 myEventProc */ PQclear(res); PQclear(res_copy); /* 发送一个 PGEVT_CONNDESTROY 给 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); /* 将应用相关的数据与连接关联起来 */ 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 复制过来)关联起来 */ PQsetResultInstanceData(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); /* 把应用相关的数据与结果(从一个结果复制过来)关联起来 */ PQsetResultInstanceData(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; /* 事件处理成功 */ }