2024年9月26日: PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不受支持的版本:11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4

32.14. 事件系统 #

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

每个注册的事件处理程序都与两块数据相关联,libpq 仅将其识别为不透明的 void * 指针。有一个由应用程序在事件处理程序向 PGconn 注册时提供的 传递指针。在 PGconn 的整个生命周期及其生成的所有 PGresult 中,传递指针永远不会改变;因此,如果使用,它必须指向长期存在的数据。此外,还有一个 实例数据 指针,它在每个 PGconnPGresult 中最初都是 NULL。可以使用 PQinstanceDataPQsetInstanceDataPQresultInstanceDataPQresultSetInstanceData 函数来操作此指针。请注意,与传递指针不同,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 #

连接重置事件在 PQresetPQresetPoll 完成后触发。在这两种情况下,仅当重置成功时才会触发事件。在 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_RESULTCOPYPGEVT_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 无法做到这一点。如果事件过程失败(返回零),则该事件过程将在此新结果的剩余生命周期中被忽略;也就是说,它不会接收该结果或从中复制的结果的 PGEVT_RESULTCOPYPGEVT_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 中,只能注册一次特定的事件过程。这是因为过程的地址用作查找键来识别关联的实例数据。

注意

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

32.14.3. 事件支持函数 #

PQregisterEventProc #

将事件回调过程注册到 libpq。

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

每个您想要接收事件的 PGconn 都必须注册一次事件过程。在一个连接上可以注册的事件过程数量没有限制,除了内存限制。如果成功,该函数返回非零值;如果失败,则返回零。

当 libpq 事件触发时,将调用 proc 参数。其内存地址也用于查找 instanceDataname 参数用于在错误消息中引用事件过程。此值不能为 NULL 或零长度字符串。名称字符串被复制到 PGconn 中,因此传递的值不需要长期存在。每当发生事件时, passThrough 指针都会传递给 proc。此参数可以为 NULL

PQsetInstanceData #

将连接 conninstanceData 为过程 proc 设置为 data。成功返回非零值,失败返回零。(只有当 proc 未在 conn 中正确注册时,才会发生失败。)

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

返回连接 conn 与过程 proc 关联的 instanceData,如果没有则返回 NULL

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

将结果的 instanceDataproc 设置为 data。成功返回非零值,失败返回零。(只有当 proc 未在结果中正确注册时,才会发生失败。)

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

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

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 */
}

提交更正

如果您在文档中看到任何不正确的内容,与您对特定功能的体验不符,或者需要进一步澄清,请使用 此表单 报告文档问题。