PostgreSQL 中文参考文档
🌐 英文原版 官方 ↗

32.14. 事件系统 #

32.14.1. 事件类型
32.14.2. 事件回调过程
32.14.3. 事件支持函数
32.14.4. 事件系统示例

libpq 的事件系统被设计为向已注册的事件处理器通知有趣的 libpq 事件, 例如 PGconn 和 PGresult 对象的创建或销毁。 一个主要的用例是: 它允许应用程序把自己的数据与一个 PGconn 或 PGresult 关联起来, 并确保这些数据在适当的时刻被释放。

每个已注册的事件处理器都与两块数据关联, libpq 只把它们当作不透明的 void * 指针看待。 一种是应用程序在向一个 PGconn 注册事件处理器时提供的 直通指针。 在 PGconn 的生命周期以及由它产生的所有 PGresult 中, 直通指针从不改变; 因此如果使用它, 它必须指向长寿命的数据。 此外, 还有一个 实例数据指针, 它在每个 PGconn 和 PGresult 中都初始为 NULL。 这个指针可以用 PQinstanceData、 PQsetInstanceData、 PQresultInstanceData 和 PQresultSetInstanceData 函数操纵。 注意,与直通指针不同, 一个 PGconn 的实例数据不会被由它创建的 PGresult 自动继承。 libpq 不知道直通指针和实例数据指针指向什么 (如果有的话), 并且永远不会尝试释放它们 — 这是事件处理器的责任。

32.14.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 完成时被触发。 在这两种情况下, 只有当重置成功时该事件才会被触发。 在 PostgreSQL v15 及更高版本中, 事件过程的返回值会被忽略。 然而在更早的版本中, 返回成功(非零)很重要, 否则连接将被中止。

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 的理想位置。 如果一个事件过程失败 (返回零), 那么在该结果剩余的生命周期内, 该事件过程将被忽略; 也就是说, 对于这个结果以及从它复制的结果, 它将不会收到 PGEVT_RESULTCOPY 或 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 做不到这一点。 如果一个事件过程失败 (返回零), 那么在新结果剩余的生命周期内, 该事件过程将被忽略; 也就是说, 对于那个结果以及从它复制的结果, 它将不会收到 PGEVT_RESULTCOPY 或 PGEVT_RESULTDESTROY 事件。

PGEVT_RESULTDESTROY #

结果销毁事件作为对一个 PQclear 的响应被触发。 由于 libpq 没有能力管理这块内存, 正确地清理它的事件数据是事件过程的责任。 未能清理将导致内存泄漏。

typedef struct
{
    PGresult *result;
} PGEventResultDestroy;

当收到一个 PGEVT_RESULTDESTROY 事件时, evtInfo 指针应当被转换为 PGEventResultDestroy *。 这个事件会在 PQclear 执行任何其他清理之前被触发。 事件过程的返回值会被忽略, 因为 PQclear 没有任何方式指示失败。 此外, 事件过程的失败也不应当中止清理不需要的内存的过程。

32.14.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。 如果该过程的地址必须在其自身源文件之外可用, 则暴露一个单独的函数来返回该地址。

32.14.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 没有被正确地注册在该结果中时才可能失败。)

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

注意,由 data 表示的任何存储都不会被 PQresultMemorySize 计入, 除非它是用 PQresultAlloc 分配的。 (推荐这样做, 因为它消除了在结果被销毁时显式释放这类存储的需要。)

PQresultInstanceData #

返回结果中与 proc 关联的 instanceData, 如果没有则返回 NULL。

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

32.14.4. 事件系统示例 #

下面是一个管理 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) */
            PQresultSetInstanceData(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) */
            PQresultSetInstanceData(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 */
}