💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 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 -->事件处理成功 */ } ```