34.13. 事件系统

libpq 的事件系统旨在向注册事件处理程序通知有关有趣的 libpq 事件,例如PGconnPGresult对象的创建或销毁。一个主要的用例是,它允许应用程序将自己的数据与PGconnPGresult关联,并确保在适当的时间释放数据。

每个注册的事件处理程序都与两个数据相关联,libpq 仅将其称为不透明的void *指针。当事件处理程序向PGconn注册时,应用程序会提供* passthrough 指针。直通指针在PGconn以及从其生成的所有PGresult的生命周期内永远不会改变;因此,如果使用它,它必须指向长期存在的数据。此外,还有一个 instance data *指针,它在PGconnPGresult的每一个中以NULL开头。可以使用PQinstanceDataPQsetInstanceDataPQresultInstanceDataPQsetResultInstanceData函数来操纵该指针。请注意,与直通指针不同,PGconn的实例数据不会被从其创建的PGresult自动继承。 libpq 不知道传递和实例数据指针指向什么(如果有的话),并且永远不会尝试释放它们-这是事件处理程序的责任。

34 .13.1. 活动类型

枚举PGEventId命名事件系统处理的事件的类型。其所有值的名称都以PGEVT开头。对于每种事件类型,都有一个相应的事件信息结构,该结构包含传递给事件处理程序的参数。事件类型为:

  • PGEVT_REGISTER
    • 调用PQregisterEventProc时发生注册事件。现在是初始化事件过程可能需要的任何instanceData的理想时间。每个连接的每个事件处理程序将仅触发一个注册事件。如果事件过程失败,注册将中止。
typedef struct
{
    PGconn *conn;
} PGEventRegister;

收到PGEVT_REGISTER事件时,* evtInfo *指针应强制转换为PGEventRegister *。此结构包含PGconn,该状态应为CONNECTION_OK。保证如果一个人获得好的PGconn之后马上打电话PQregisterEventProc。返回失败代码时,必须执行所有清除操作,因为不会发送PGEVT_CONNDESTROY事件。

  • PGEVT_CONNRESET
    • 完成PQresetPQresetPoll会触发连接重置事件。在这两种情况下,仅在重置成功后才触发该事件。如果事件过程失败,则整个连接重置将失败; 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 无法 Management 此内存。清理失败将导致内存泄漏。
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_RESULTCREATEPGEVT_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触发结果 destroy 事件。正确清理事件数据是事件过程的责任,因为 libpq 无法 Management 此内存。清理失败将导致内存泄漏。
typedef struct
{
    PGresult *result;
} PGEventResultDestroy;

收到PGEVT_RESULTDESTROY事件时,* evtInfo *指针应强制转换为PGEventResultDestroy *PQclear执行任何其他清除之前会触发此事件。由于无法从PQclear指示失败,因此将忽略事件过程的返回值。同样,事件过程失败不应使清理不必要的内存的过程中止。

34 .13.2. 事件回呼程序

  • PGEventProc
    • PGEventProc是指向事件过程的指针的 typedef,即,用于从 libpq 接收事件的用户回调函数。事件过程的签名必须是
int eventproc(PGEventId evtId, void *evtInfo, void *passThrough)
  • evtId *参数指示发生了哪个PGEVT事件。 * evtInfo *指针必须强制转换为适当的结构类型,以获取有关事件的更多信息。 * passThrough *参数是注册事件过程时提供给PQregisterEventProc的指针。如果成功,该函数应返回非零值;如果失败,则应返回零。

特定事件过程只能在PGconn中注册一次。这是因为该过程的地址用作查找键,以标识关联的实例数据。

Caution

在 Windows 上,函数可以具有两个不同的地址:一个从 DLL 外部可见,而另一个从 DLL 内部可见。应该注意的是,只有这些地址之一与 libpq 的事件过程函数一起使用,否则会引起混淆。编写将起作用的代码的最简单规则是确保将事件过程声明为static。如果该过程的地址必须在其自身的源文件之外可用,则公开一个单独的函数以返回该地址。

34 .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
    • 将过程* proc 的连接 conn instanceData设置为 data 。对于成功,返回非零;对于失败,返回零。 (仅当 proc 未在 conn *中正确注册时,才有可能失败.)
int PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data);
  • PQinstanceData
    • 返回与过程* proc 关联的连接 conn *的instanceData,如果不存在则返回NULL
void *PQinstanceData(const PGconn *conn, PGEventProc proc);
  • PQresultSetInstanceData
    • 将* proc 的结果instanceData设置为 data 。对于成功,返回非零;对于失败,返回零。 (仅当 proc *未在结果中正确注册时才可能失败.)
int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data);
  • PQresultInstanceData
    • 返回与* proc *关联的结果instanceData,如果没有则返回NULL
void *PQresultInstanceData(const PGresult *res, PGEventProc proc);

34 .13.4. 活动范例

这是 Management 与 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)
    {
        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.
     */
    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 */
}