事件系统

PostgreSQL 9.4.4 中文手册
上一页 | 上一级 | 章 31. libpq - C 库 | 下一页

# 31.13. 事件系统

libpq事件系统用于通知对libpq 事件感兴趣的注册事件处理过程，如创建或删除`PGconn`和 `PGresult`对象。一个主要的使用原因是，它允许应用程序通过一个 `PGconn`或`PGresult`关联它们自己的数据， 并且确保数据在适当的时候释放。

每个注册的事件处理程序与两片数据相关联，已知的libpq 只作为不透明的`void *`指针。当事件处理程序注册带有`PGconn`时， 应用程序会提供一个passthrough指针。传递指针在由它产生的`PGconn` 和所有的`PGresult`的生命周期中永远不会改变，它指向生命周期长的数据。 除此之外，还有一个instance data指针，它从每个`PGconn`和 `PGresult`中的`NULL`开始。这个指针可以与`PQinstanceData`， `PQsetInstanceData`，`PQresultInstanceData`和 `PQsetResultInstanceData`函数一起使用。需要注意的是不同于传递指针， 一个`PGconn`的实例数据不会被由它产生的`PGresult`自动继承。 libpq不知道传递和实例数据指针指向的是什么，并且不会尝试去释放它们； 这对事件处理程序是一种保证。

## 31.13.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`函数完成时，触发连接复位事件。 在这两种情况下，只有重置成功时才会触发事件。如果事件过程失败，整个连接复位都会失败； `PGconn`被置为`CONNECTION_BAD`状态并且 `PQresetPoll`将返回`PGRES_POLLING_FAILED`。

      typedef struct
      {
          PGconn *conn;
      } PGEventConnReset;

  当接收到一个`PGEVT_CONNRESET`事件时，`evtInfo` 指针应该被转换为一个`PGEventConnReset *`。尽管包含的 `PGconn`被重置了，但所有事件数据不会改变。 这个事件应该用于reset/reload/requery任何关联的`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`。 如果事件过程失败，结果会被清理并且传播该失败。事件过程不应该尝试自己`PQclear`结果对象。 当返回一个错误代码时，必须执行所有的清理，因为没有`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`做不到。 如果事件过程失败，整个拷贝过程都将失败，并且`dest`结果也会被清理。 当返回一个错误代码时，必须执行所有的清理，因为没有`PGEVT_RESULTDESTROY`事件会被发送。

`PGEVT_RESULTDESTROY`

  在回应`PQclear`时会触发结果破坏事件。这是事件过程的责任； 合理清理它的事件数据，因为libpq没有能力管理这块内存。清理失败会导致内存溢出。

      typedef struct
      {
          PGresult *result;
      } PGEventResultDestroy;

  当接收到一个`PGEVT_RESULTDESTROY`事件时，`evtInfo` 指针应该被转换为一个`PGEventResultDestroy *`。 这个事件会在`PQclear`执行清理之前被触发。事件过程的返回结果会被忽略， 因为没有一个方式能够从`PQclear`指出失败。同样， 一个事件过程失败不应该中止对不需要内存的清理。

## 31.13.2. 事件回调过程

`PGEventProc`

  `PGEventProc`是一个事件过程中指针的typedef，也就是， 从libpq接收事件的用户回调函数。事件过程的用法必须如下：

      int eventproc(PGEventId evtId, void *evtInfo, void *passThrough)

  `evtId`参数指出要发生哪个`PGEVT`事件。 `evtInfo`指针必须被转换为合适的结构类型以获取有关该事件的进一步信息。 `passThrough`是当事件过程被注册时，提供给 `PQregisterEventProc`的指针。这个函数应该返回一个非0的值，如果成功的话，反之，返回0。

  在任意`PGconn`中，一个特殊的事件过程只能注册一次。 这是因为过程地址被用于作为查询关键字，以识别相关的实例数据。

  小心
  在Windows上，函数可以有两个不同的地址：一个是内部DLL可见的，另一个是外部DLL可见的。 需要注意的是，libpq事件过程函数只会使用其中一个地址，否则会造成混乱。 有效的编写代码的简单规则是为了保证事件过程声明为`static`。 如果过程地址在它的源文件之外是可用的，公开一个单独的函数以返回地址。

## 31.13.3. 事件支持函数

`PQregisterEventProc`

  用libpq注册一个事件回调过程。

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

  在每个`PGconn`中必须注册一次事件过程，用于希望接受到的事件。除了内存之外， 对于一次连接注册的事件过程个数没有限制。如果成功，则返回一个非0的值，否则返回0。

  当一个libpq事件被触发时，会调用一个`proc`参数。 内存地址同样会被用于查找`instanceData`。`name` 用于指出在错误消息中的事件过程。这个值不能为`NULL`或一个长度为0的字符串。 名字字符串被拷贝到`PGconn`中，因此被传递的不需要拥有很长的生命周期。 `passThrough`指针被传递到`proc`， 不管何时触发事件。这个参数可以是`NULL`。

`PQsetInstanceData`

  为`proc`到`data`的过程设置连接`conn` 的`instanceData`。成功则返回一个非0值，否则返回0。 只有`proc`没有成功在`conn`注册时才会发生失败。

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

`PQinstanceData`

  返回与`proc`过程，或`NULL`（如果存在空） 相关的`conn`的`instanceData`。

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

`PQresultSetInstanceData`

  为`proc`到`data`的过程设置结果的`instanceData`。 成功则返回一个非0值，否则返回0。只有`proc`没有成功在结果注册时才会发生失败。

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

`PQresultInstanceData`

  返回与`proc`过程，或`NULL`（如果存在空）相关的结果的`instanceData`。

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

## 31.13.4. 事件例子

一个管理与libpq连接和结果相关的私有数据的例子：

    /* 需要libpq事件的头文件 (注意：包括 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");

        if (PQstatus(conn) != CONNECTION_OK)
        {
            fprintf(stderr, "Connection to database failed: %s",
                    PQerrorMessage(conn));
            PQfinish(conn);
            return 1;
        }

        /* 在任何应该接收事件的连接上调用一次。
         * 发送一个 PGEVT_REGISTER 到 myEventProc。
         */
        if (!PQregisterEventProc(conn, myEventProc, "mydata_proc", NULL))
        {
            fprintf(stderr, "Cannot register PGEventProc\n");
            PQfinish(conn);
            return 1;
        }

        /* conn instanceData 是可用的 */
        data = PQinstanceData(conn, myEventProc);

        /* 发送一个 PGEVT_RESULTCREATE 到 myEventProc */
        res = PQexec(conn, "SELECT 1 + 1");

        /* 结果 instanceData 是可用的 */
        data = PQresultInstanceData(res, myEventProc);

        /* 如果使用了 PG_COPYRES_EVENTS，发送一个 PGEVT_RESULTCOPY到 myEventProc */
        res_copy = PQcopyResult(res, PG_COPYRES_TUPLES | PG_COPYRES_EVENTS);

        /* 结果 instanceData 是可用的，如果 PG_COPYRES_EVENTS
         * 在PQcopyResult调用期间使用了的话。
         */
        data = PQresultInstanceData(res_copy, myEventProc);

        /* 两个clears都发送 PGEVT_RESULTDESTROY 到 myEventProc */
        PQclear(res);
        PQclear(res_copy);

        /* 发送一个 PGEVT_CONNDESTROY 到 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);

                /* 将应用程序特定的数据与连接相关联 */
                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);

                /* 释放实例数据，因为conn被破坏了 */
                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);

                /* 将应用程序特定的数据与结果相关联 (从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);

                /* 将应用程序特定的数据与结果相关联 (从结果中拷贝) */
                PQsetResultInstanceData(e->dest, myEventProc, dest_data);
                break;
            }

            case PGEVT_RESULTDESTROY:
            {
                PGEventResultDestroy *e = (PGEventResultDestroy *)evtInfo;
                mydata *data = PQresultInstanceData(e->result, myEventProc);

                /* 释放实例数据，因为结果被破坏了 */
                if (data)
                  free_mydata(data);
                break;
            }

            /* 未知事件ID，只是返回TRUE。*/
            default:
                break;
        }

        return TRUE; /* 事件处理成功 */
    }

上一页 | 起始页 | 下一页
通知处理 | 上一级 | 环境变量