Postgresql 中文操作指南

34.14. Event System #

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

每个已注册的事件处理程序都关联有两条数据,而 libpq 仅以不透明 void * 指针对其知晓。有一个 pass-through 指针,该指针是由事件处理程序在使用 PGconn 注册时提供的。直通指针不会在 PGconn 的生命周期内发生改变,以及所有 PGresult_s generated from it; so if used, it must point to long-lived data. In addition there is an _instance data 指针,它在每个 PGconnPGresult 中以 NULL 起始。可以使用 PQinstanceDataPQsetInstanceDataPQresultInstanceDataPQresultSetInstanceData 函数来处理此指针。请注意,与直通指针不同, PGconn 的实例数据不会由从其中创建的 _PGresult_s 自动继承。libpq 不知道直通和实例数据指针指向什么(如果指向某个东西),并且永远不尝试释放它们——这是事件处理程序的责任。

34.14.1. Event Types #

枚举 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 中指示失败。此外,事件过程失败不应中止清理不需要的内存的过程。

34.14.2. Event Callback Procedure #

  • 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.14.3. Event Support Functions #

  • PQregisterEventProc #

    • 使用 libpq 注册事件回调过程。

int PQregisterEventProc(PGconn *conn, PGEventProc proc,
                        const char *name, void *passThrough);
  • 需要在要接收事件的所有 PGconn 上注册一次事件过程。除了内存之外,没有限制能与连接注册的事件过程数量。此函数在成功时应返回非零值,而失败时应返回零。

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

    • PQsetInstanceData #

  • 设置连接 conninstanceData,以将 procdata 设置为 data。这将返回非零值,表示成功,而返回零值,表示失败。(只有当 proc 尚未在 conn 中注册时才会失败。)

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

    • 返回连接的 conn,该 connproc 关联,或者在没有这样的连接时返回 NULL

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

    • proc 的结果 instanceData 设置为 data 。这将返回非零表示成功,返回零表示失败。(仅当未在结果中正确注册 proc 时才可能失败。)

int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data);
  • 请注意,除非使用 PQresultAlloc 分配 data 表示的任何存储,否则 PQresultMemorySize 将无法将其计算在内。(这样做是值得推荐的,因为它消除了显式释放此类存储的需求,当结果被破坏时。)

    • PQresultInstanceData #

  • 返回与 proc 关联的结果的 instanceData ,或如果不存在,则返回 NULL

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

34.14.4. 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) */
            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 */
}