31.13. 事件系统
libpq事件系统用于通知对libpq 事件感兴趣的注册事件处理过程,如创建或删除PGconn
和 PGresult
对象。一个主要的使用原因是,它允许应用程序通过一个 PGconn
或PGresult
关联它们自己的数据, 并且确保数据在适当的时候释放。
每个注册的事件处理程序与两片数据相关联,已知的libpq 只作为不透明的void *
指针。当事件处理程序注册带有PGconn
时, 应用程序会提供一个passthrough指针。传递指针在由它产生的PGconn
和所有的PGresult
的生命周期中永远不会改变,它指向生命周期长的数据。 除此之外,还有一个instance data指针,它从每个PGconn
和 PGresult
中的NULL
开始。这个指针可以与PQinstanceData
, PQsetInstanceData
,PQresultInstanceData
和 PQsetResultInstanceData
函数一起使用。需要注意的是不同于传递指针, 一个PGconn
的实例数据不会被由它产生的PGresult
自动继承。 libpq不知道传递和实例数据指针指向的是什么,并且不会尝试去释放它们; 这对事件处理程序是一种保证。
31.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
被重置了,但所有事件数据不会改变。 这个事件应该用于reset/reload/requery任何关联的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
指出失败。同样, 一个事件过程失败不应该中止对不需要内存的清理。
31.13.2. 事件回调过程
PGEventProc
PGEventProc
是一个事件过程中指针的typedef,也就是, 从libpq接收事件的用户回调函数。事件过程的用法必须如下:
int eventproc(PGEventId evtId, void *evtInfo, void *passThrough)
evtId
参数指出要发生哪个PGEVT
事件。 evtInfo
指针必须被转换为合适的结构类型以获取有关该事件的进一步信息。 passThrough
是当事件过程被注册时,提供给 PQregisterEventProc
的指针。这个函数应该返回一个非0的值,如果成功的话,反之,返回0。
在任意PGconn
中,一个特殊的事件过程只能注册一次。 这是因为过程地址被用于作为查询关键字,以识别相关的实例数据。
Caution |
---|
在Windows上,函数可以有两个不同的地址:一个是内部DLL可见的,另一个是外部DLL可见的。 需要注意的是,libpq事件过程函数只会使用其中一个地址,否则会造成混乱。 有效的编写代码的简单规则是为了保证事件过程声明为static 。 如果过程地址在它的源文件之外是可用的,公开一个单独的函数以返回地址。 |
31.13.3. 事件支持函数
PQregisterEventProc
用libpq注册一个事件回调过程。
int PQregisterEventProc(PGconn *conn, PGEventProc proc,
const char *name, void *passThrough);
在每个PGconn
中必须注册一次事件过程,用于希望接受到的事件。除了内存之外, 对于一次连接注册的事件过程个数没有限制。如果成功,则返回一个非0的值,否则返回0。
当一个libpq事件被触发时,会调用一个proc
参数。 内存地址同样会被用于查找instanceData
。name
用于指出在错误信息中的事件过程。这个值不能为NULL
或一个长度为0的字符串。 名字字符串被拷贝到PGconn
中,因此被传递的不需要拥有很长的生命周期。 passThrough
指针被传递到proc
, 不管何时触发事件。这个参数可以是NULL
。
PQsetInstanceData
为proc
到data
的过程设置连接conn
的instanceData
。成功则返回一个非0值,否则返回0。 只有proc
没有成功在conn
注册时才会发生失败。
int PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data);
PQinstanceData
返回与proc
过程,或NULL
(如果存在空) 相关的conn
的instanceData
。
void *PQinstanceData(const PGconn *conn, PGEventProc proc);
PQresultSetInstanceData
为proc
到data
的过程设置结果的instanceData
。 成功则返回一个非0值,否则返回0。只有proc
没有成功在结果注册时才会发生失败。
int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data);
PQresultInstanceData
返回与proc
过程,或NULL
(如果存在空)相关的结果的instanceData
。
void *PQresultInstanceData(const PGresult *res, PGEventProc proc);
31.13.4. 事件例子
一个管理与libpq连接和结果相关的私有数据的例子:
/* <!-- required header for libpq events (note: includes libpq-fe.h) -->需要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;
}
<!--
/* called once on any connection that should receive events.
* Sends a PGEVT_REGISTER to myEventProc.
*/
-->
/* 在任何应该接收事件的连接上调用一次。
* 发送一个 PGEVT_REGISTER 到 myEventProc。
*/
if (!PQregisterEventProc(conn, myEventProc, "mydata_proc", NULL))
{
fprintf(stderr, "Cannot register PGEventProc\n");
PQfinish(conn);
return 1;
}
<!-- /* conn instanceData is available */ -->
/* conn instanceData 是可用的 */
data = PQinstanceData(conn, myEventProc);
<!-- /* Sends a PGEVT_RESULTCREATE to myEventProc */ -->
/* 发送一个 PGEVT_RESULTCREATE 到 myEventProc */
res = PQexec(conn, "SELECT 1 + 1");
<!-- /* result instanceData is available */ -->
/* 结果 instanceData 是可用的 */
data = PQresultInstanceData(res, myEventProc);
<!-- /* If PG_COPYRES_EVENTS is used, sends a PGEVT_RESULTCOPY to myEventProc */ -->
/* 如果使用了 PG_COPYRES_EVENTS,发送一个 PGEVT_RESULTCOPY到 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.
*/
-->
/* 结果 instanceData 是可用的,如果 PG_COPYRES_EVENTS
* 在PQcopyResult调用期间使用了的话。
*/
data = PQresultInstanceData(res_copy, myEventProc);
<!-- /* Both clears send a PGEVT_RESULTDESTROY to myEventProc */ -->
/* 两个clears都发送 PGEVT_RESULTDESTROY 到 myEventProc */
PQclear(res);
PQclear(res_copy);
<!-- /* Sends a PGEVT_CONNDESTROY to myEventProc */ -->
/* 发送一个 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);
<!-- /* 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 */ -->
/* 释放实例数据,因为conn被破坏了 */
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) */ -->
/* 将应用程序特定的数据与结果相关联 (从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);
<!-- /* associate app specific data with result (copy it from a result) */ -->
/*将应用程序特定的数据与结果相关联 (从结果中拷贝)*/
PQsetResultInstanceData(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. */ -->
/* 未知事件ID,只是返回TRUE。*/
default:
break;
}
return TRUE; /* <!-- event processing succeeded -->事件处理成功 */
}