31.12. 事件系统

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

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

31.12.1. 事件类型

枚举PGEventId命名事件系统处理的事件的类型。 所有的命名值都是从PGEVT开始。对每个事件类型来说, 有一个相关的事件信息结构,用于传送传递给事件处理程序的参数。事件类型如下:

PGEVT_REGISTER

当调用PQregisterEventProc时,会发生注册的事件。 这是一个理想化的时间,用于初始化任意instanceData,可能需要一个事件过程。 每次连接中的每个事件处理程序只会触发一个注册了的事件。 如果事件过程失败,会终止注册。

typedef struct
{
    PGconn *conn;
} PGEventRegister;

当接收到PGEVT_REGISTER时,evtInfo指针 应该被转换为一个PGEventRegister *。这个结构包含了一个 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备重置了,但所有事件数据不会改变。 这个事件应该用于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_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时会触发结果破坏事件。 这是事件过程的责任;合理清理它的事件数据,因为libpq没有能力管理这块内存。 清理失败会导致内存溢出。

typedef struct
{
    PGresult *result;
} PGEventResultDestroy;

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

31.12.2. 事件回调过程

PGEventProc

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

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

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

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

Caution

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

31.12.3. 事件支持函数

PQregisterEventProc

libpq的注册事件回调过程。

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

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

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

PQsetInstanceData

procdata的过程设置conn'sinstanceData连接。 成功则返回一个非0值,否则返回0。 只有proc没有成功在conn注册时才会发生失败。

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

PQinstanceData

返回与proc过程,或NULL(如果存在空)相关的conninstanceData

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

PQresultSetInstanceData

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

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

PQresultInstanceData

返回与proc过程,或NULL(如果存在空)相关的结果的instanceData

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

31.12.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");

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