libpq 的事件系统旨在通知注册的事件处理程序有关有趣的 libpq 事件,例如 PGconn 和 PGresult 对象的创建或销毁。一个主要用例是,这允许应用程序将自己的数据与 PGconn 或 PGresult 相关联,并确保在适当的时间释放数据。libpq’s event system is designed to notify registered event handlers about interesting libpq events, such as the creation or destruction of PGconn and PGresult objects. A principal use case is that this allows applications to associate their own data with a PGconn or PGresult and ensure that that data is freed at an appropriate time.

每个注册的事件处理程序都与两条数据相关联,libpq 只知道它们是不透明的 void * 指针。当事件处理程序向 PGconn 注册时,应用程序会提供一个传递指针。在 PGconn 及其生成的所有 PGresult 的生命周期内,传递指针永远不会改变;所以如果使用,它必须指向长期存在的数据。此外还有一个实例数据指针,它在每个 PGconn 和 PGresult 中以 NULL 开头。可以使用 PQinstanceData、PQsetInstanceData、PQresultInstanceData 和 PQsetResultInstanceData 函数来操作该指针。请注意,与传递指针不同,PGconn 的实例数据不会被从它创建的 PGresults 自动继承。 libpq 不知道传递和实例数据指针指向什么(如果有的话)并且永远不会尝试释放它们——这是事件处理程序的责任。
Each registered event handler is associated with two pieces of data, known to libpq only as opaque void * pointers. There is a pass-through pointer that is provided by the application when the event handler is registered with a PGconn. The pass-through pointer never changes for the life of the PGconn and all PGresults generated from it; so if used, it must point to long-lived data. In addition there is an instance data pointer, which starts out NULL in every PGconn and PGresult. This pointer can be manipulated using the PQinstanceData, PQsetInstanceData, PQresultInstanceData and PQsetResultInstanceData functions. Note that unlike the pass-through pointer, instance data of a PGconn is not automatically inherited by PGresults created from it. libpq does not know what pass-through and instance data pointers point to (if anything) and will never attempt to free them — that is the responsibility of the event handler.

Event Types

枚举 PGEventId 命名事件系统处理的事件类型。 它的所有值的名称都以 PGEVT 开头。 对于每种事件类型,都有一个相应的事件信息结构,该结构携带传递给事件处理程序的参数。 事件类型有:The enum PGEventId names the types of events handled by the event system. All its values have names beginning with PGEVT. For each event type, there is a corresponding event info structure that carries the parameters passed to the event handlers. The event types are:

PGEVT_REGISTER

注册事件在调用 PQregisterEventProc 时发生。 这是初始化事件过程可能需要的任何 instanceData 的理想时机。 每个连接的每个事件处理程序只会触发一个注册事件。 如果事件过程失败,则中止注册。The register event occurs when PQregisterEventProc is called. It is the ideal time to initialize any instanceData an event procedure may need. Only one register event will be fired per event handler per connection. If the event procedure fails, the registration is aborted.

typedef struct {
PGconn *conn;
} PGEventRegister;

当收到 PGEVT_REGISTER 事件时,应该将 evtInfo 指针转换为 PGEventRegister *。 该结构包含一个应该处于 CONNECTION_OK 状态的 PGconn; 保证在获得一个好的 PGconn 之后立即调用 PQregisterEventProc。 返回失败代码时,必须执行所有清理,因为不会发送 PGEVT_CONNDESTROY 事件。When a PGEVT_REGISTER event is received, the evtInfo pointer should be cast to a PGEventRegister *. This structure contains a PGconn that should be in the CONNECTION_OK status; guaranteed if one calls PQregisterEventProc right after obtaining a good PGconn. When returning a failure code, all cleanup must be performed as no PGEVT_CONNDESTROY event will be sent.

PGEVT_CONNRESET

连接重置事件在 PQreset 或 PQresetPoll 完成时触发。 在这两种情况下,只有在重置成功时才会触发事件。 如果事件过程失败,则整个连接重置失败; PGconn 进入 CONNECTION_BAD 状态,PQresetPoll 将返回 PGRES_POLLING_FAILED。The connection reset event is fired on completion of PQreset or PQresetPoll. In both cases, the event is only fired if the reset was successful. If the event procedure fails, the entire connection reset will fail; the PGconn is put into CONNECTION_BAD status and PQresetPoll will return PGRES_POLLING_FAILED.

typedef struct {
PGconn *conn;
} PGEventConnReset;

当收到 PGEVT_CONNRESET 事件时,应该将 evtInfo 指针转换为 PGEventConnReset *。 尽管包含的 PGconn 刚刚重置,但所有事件数据保持不变。 此事件应用于重置/重新加载/重新查询任何关联的 instanceData。 请注意,即使事件过程未能处理 PGEVT_CONNRESET,它仍然会在连接关闭时收到 PGEVT_CONNDESTROY 事件。When a PGEVT_CONNRESET event is received, the evtInfo pointer should be cast to a PGEventConnReset *. Although the contained PGconn was just reset, all event data remains unchanged. This event should be used to reset/reload/requery any associated instanceData. Note that even if the event procedure fails to process PGEVT_CONNRESET, it will still receive a PGEVT_CONNDESTROY event when the connection is closed.

PGEVT_CONNDESTROY

连接销毁事件被触发以响应 PQfinish。 正确清理其事件数据是事件过程的责任,因为 libpq 无法管理此内存。 清理失败会导致内存泄漏。The connection destroy event is fired in response to PQfinish. It is the event procedure’s responsibility to properly clean up its event data as libpq has no ability to manage this memory. Failure to clean up will lead to memory leaks.

typedef struct {
PGconn *conn;
} PGEventConnDestroy;

当收到 PGEVT_CONNDESTROY 事件时,应该将 evtInfo 指针转换为 PGEventConnDestroy *。 此事件在 PQfinish 执行任何其他清理之前触发。 事件过程的返回值被忽略,因为无法从 PQfinish 指示失败。 此外,事件过程失败不应中止清理不需要的内存的过程。When a PGEVT_CONNDESTROY event is received, the evtInfo pointer should be cast to a PGEventConnDestroy *. This event is fired prior to PQfinish performing any other cleanup. The return value of the event procedure is ignored since there is no way of indicating a failure from PQfinish. Also, an event procedure failure should not abort the process of cleaning up unwanted memory.

PGEVT_RESULTCREATE

结果创建事件被触发以响应任何生成结果的查询执行函数,包括 PQgetResult。 只有在成功创建结果后才会触发此事件。The result creation event is fired in response to any query execution function that generates a result, including PQgetResult. This event will only be fired after the result has been created successfully.

typedef struct {
PGconn *conn;
PGresult *result;
} PGEventResultCreate;

当收到 PGEVT_RESULTCREATE 事件时,应该将 evtInfo 指针转换为 PGEventResultCreate *。 conn 是用于生成结果的连接。 这是初始化需要与结果关联的任何 instanceData 的理想位置。 如果事件过程失败,结果将被清除并传播失败。 事件过程不能尝试为自己 PQclear 结果对象。 返回失败代码时,必须执行所有清理,因为不会发送 PGEVT_RESULTDESTROY 事件。
When a PGEVT_RESULTCREATE event is received, the evtInfo pointer should be cast to a PGEventResultCreate *. The conn is the connection used to generate the result. This is the ideal place to initialize any instanceData that needs to be associated with the result. If the event procedure fails, the result will be cleared and the failure will be propagated. The event procedure must not try to PQclear the result object for itself. When returning a failure code, all cleanup must be performed as no PGEVT_RESULTDESTROY event will be sent.

PGEVT_RESULTCOPY

结果复制事件被触发以响应 PQcopyResult。 只有在复制完成后才会触发此事件。 只有成功处理源结果的 PGEVT_RESULTCREATE 或 PGEVT_RESULTCOPY 事件的事件过程才会接收 PGEVT_RESULTCOPY 事件。The result copy event is fired in response to PQcopyResult. This event will only be fired after the copy is complete. Only event procedures that have successfully handled the PGEVT_RESULTCREATE or PGEVT_RESULTCOPY event for the source result will receive PGEVT_RESULTCOPY events.

typedef struct {
const PGresult *src;
PGresult *dest;
} PGEventResultCopy;

当收到 PGEVT_RESULTCOPY 事件时,应该将 evtInfo 指针转换为 PGEventResultCopy *。 src 结果是复制的内容,而 dest 结果是复制目标。 此事件可用于提供 instanceData 的深层副本,因为 PQcopyResult 无法做到这一点。 如果事件过程失败,则整个复制操作将失败,并且 dest 结果将被清除。 返回失败代码时,必须执行所有清理,因为不会为目标结果发送 PGEVT_RESULTDESTROY 事件。When a PGEVT_RESULTCOPY event is received, the evtInfo pointer should be cast to a PGEventResultCopy *. The src result is what was copied while the dest result is the copy destination. This event can be used to provide a deep copy of instanceData, since PQcopyResult cannot do that. If the event procedure fails, the entire copy operation will fail and the dest result will be cleared. When returning a failure code, all cleanup must be performed as no PGEVT_RESULTDESTROY event will be sent for the destination result.

PGEVT_RESULTDESTROY

结果销毁事件被触发以响应 PQclear。 正确清理其事件数据是事件过程的责任,因为 libpq 无法管理此内存。 清理失败会导致内存泄漏。The result destroy event is fired in response to a PQclear. It is the event procedure’s responsibility to properly clean up its event data as libpq has no ability to manage this memory. Failure to clean up will lead to memory leaks.

typedef struct {
PGresult *result;
} PGEventResultDestroy;

当收到 PGEVT_RESULTDESTROY 事件时,应该将 evtInfo 指针转换为 PGEventResultDestroy *。 此事件在 PQclear 执行任何其他清理之前触发。 事件过程的返回值被忽略,因为无法从 PQclear 指示失败。 此外,事件过程失败不应中止清理不需要的内存的过程。When a PGEVT_RESULTDESTROY event is received, the evtInfo pointer should be cast to a PGEventResultDestroy *. This event is fired prior to PQclear performing any other cleanup. The return value of the event procedure is ignored since there is no way of indicating a failure from PQclear. Also, an event procedure failure should not abort the process of cleaning up unwanted memory.

Event Callback Procedure

PGEventProc 是指向事件过程的指针的 typedef,即从 libpq 接收事件的用户回调函数。 事件过程的签名必须是​​int eventproc(PGEventId evtId, void *evtInfo, void *passThrough)​​​。
evtId 参数指示发生了哪个 PGEVT 事件。 必须将 evtInfo 指针转换为适当的结构类型才能获得有关事件的更多信息。 passThrough 参数是注册事件过程时提供给 PQregisterEventProc 的指针。 如果成功,函数应该返回一个非零值,如果失败则返回零。The evtId parameter indicates which PGEVT event occurred. The evtInfo pointer must be cast to the appropriate structure type to obtain further information about the event. The passThrough parameter is the pointer provided to PQregisterEventProc when the event procedure was registered. The function should return a non-zero value if it succeeds and zero if it fails.
一个特定的事件过程只能在任何 PGconn 中注册一次。 这是因为过程的地址被用作查找键来识别关联的实例数据。A particular event procedure can be registered only once in any PGconn. This is because the address of the procedure is used as a lookup key to identify the associated instance data.
在 Windows 上,函数可以有两个不同的地址:一个从 DLL 外部可见,另一个从 DLL 内部可见。 应该小心,这些地址中只有一个与 libpq 的事件过程函数一起使用,否则会导致混淆。 编写有效的代码的最简单规则是确保事件过程被声明为静态的。 如果过程的地址必须在其自己的源文件之外可用,则公开一个单独的函数来返回地址。On Windows, functions can have two different addresses: one visible from outside a DLL and another visible from inside the DLL. One should be careful that only one of these addresses is used with libpq’s event-procedure functions, else confusion will result. The simplest rule for writing code that will work is to ensure that event procedures are declared static. If the procedure’s address must be available outside its own source file, expose a separate function to return the address.

Event Support Functions

int PQregisterEventProc(PGconn *conn, PGEventProc proc, const char *name, void *passThrough)

PQregisterEventProc向 libpq 注册事件回调过程。必须在每个要接收事件的 PGconn 上注册一次事件过程。 除了内存之外,可以向连接注册的事件过程的数量没有限制。 如果成功,该函数返回一个非零值,如果失败则返回零。An event procedure must be registered once on each PGconn you want to receive events about. There is no limit, other than memory, on the number of event procedures that can be registered with a connection. The function returns a non-zero value if it succeeds and zero if it fails.

触发 libpq 事件时将调用 proc 参数。 它的内存地址也用于查找instanceData。 name 参数用于引用错误消息中的事件过程。 此值不能为 NULL 或零长度字符串。 名称字符串被复制到 PGconn 中,因此传递的内容不需要长期存在。 每当事件发生时,passThrough 指针就会传递给 proc。 此参数可以为 NULL。The proc argument will be called when a libpq event is fired. Its memory address is also used to lookup instanceData. The name argument is used to refer to the event procedure in error messages. This value cannot be NULL or a zero-length string. The name string is copied into the PGconn, so what is passed need not be long-lived. The passThrough pointer is passed to the proc whenever an event occurs. This argument can be NULL.

int PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data)

将过程 proc 的连接 conn 的 instanceData 设置为数据。 这将返回非零表示成功,返回零表示失败。 (只有在 proc 没有在 conn 中正确注册时,才可能失败。)Sets the connection conn’s instanceData for procedure proc to data. This returns non-zero for success and zero for failure. (Failure is only possible if proc has not been properly registered in conn.)

void *PQinstanceData(const PGconn *conn, PGEventProc proc);

返回与过程 proc 关联的连接 conn 的 instanceData,如果没有,则返回 NULL。Returns the connection conn’s instanceData associated with procedure proc, or NULL if there is none.

int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data)

将 proc 的结果的 instanceData 设置为数据。 这将返回非零表示成功,返回零表示失败。 (只有在结果中没有正确注册 proc 时才可能失败。)Sets the result’s instanceData for proc to data. This returns non-zero for success and zero for failure. (Failure is only possible if proc has not been properly registered in the result.)
请注意,由数据表示的任何存储都不会被 PQresultMemorySize 考虑,除非它是使用 PQresultAlloc 分配的。 (这样做是值得推荐的,因为它消除了在结果被销毁时显式释放此类存储的需要。)
Beware that any storage represented by data will not be accounted for by PQresultMemorySize, unless it is allocated using PQresultAlloc. (Doing so is recommendable because it eliminates the need to free such storage explicitly when the result is destroyed.)

void *PQresultInstanceData(const PGresult *res, PGEventProc proc)

返回与 proc 关联的结果的 instanceData,如果没有,则返回 NULL。Returns the result’s instanceData associated with proc, or NULL if there is none.

Event Example

这是管理与 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) */
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. */
default:
break;
}

return true; /* event processing succeeded */
}