30.13. Система событий
Система событий libpq разработана для уведомления функций-обработчиков об интересных событиях libpq, например, о создании и уничтожении объектов PGconn и PGresult. Основное их предназначение в том, чтобы позволить приложениям связать собственные данные с объектами PGconn и PGresult и обеспечить их освобождение в нужное время.
Каждый зарегистрированный обработчик событий связывается с двумя элементами данных, которые известны libpq только как скрытые указатели void *. Первый сквозной указатель передаётся приложением, когда обработчик событий регистрируется в PGconn. Этот указатель никогда не меняется на протяжении жизни PGconn и все объекты PGresult создаются с ним; поэтому, если он используется, он должен указывать на долгоживущие данные. В дополнение к нему имеется указатель данных экземпляра, который изначально равен NULL во всех объектах PGconn и PGresult. Этим указателем можно управлять с помощью функций PQinstanceData, PQsetInstanceData, PQresultInstanceData и PQsetResultInstanceData. Заметьте, что в отличие от сквозного указателя, данные экземпляра PGconn автоматически не наследуются объектами PGresult, создаваемыми из него. Библиотека libpq не знает, на что указывают сквозной указатель и указатель данных экземпляра (если они ненулевые) и никогда не будет пытаться освобождать их — за это отвечает обработчик событий.
30.13.1. Типы событий
Перечисление PGEventId описывает типы событий, обрабатываемых системой событий. Имена всех их значений начинаются с PGEVT. Для каждого типа событий имеется соответствующая структура информации о событии, содержащая параметры, передаваемые обработчикам событий. Определены следующие типы событий:
- PGEVT_REGISTER
- Событие регистрации происходит, когда вызывается - PQregisterEventProc. Это идеальное время для инициализации данных экземпляра (- instanceData), которые могут понадобиться процедуре событий. Для каждого обработчика событий в рамках соединения будет выдаваться только одно событие регистрации. Если обработка события завершается ошибкой, регистрация прерывается.- typedef struct { PGconn *conn; } PGEventRegister;- При поступлении события - PGEVT_REGISTERуказатель- evtInfoследует привести к- PGEventRegister *. Эта структура содержит объект- PGconn, который должен быть в состоянии- CONNECTION_OK; это гарантируется, если- PQregisterEventProcвызывается сразу после получения рабочего объекта- PGconn. В случае выдачи кода ошибки всю очистку необходимо провести самостоятельно, так как событие- PGEVT_CONNDESTROYне поступит.
- PGEVT_CONNRESET
- Событие сброса соединения происходит при завершении - PQresetили- PQresetPoll. В обоих случаях это событие вызывается, только если сброс был успешным. Если обработка события завершается ошибкой, происходит сбой всей операции сброса соединения; объект- PGconnпереходит в состояние- CONNECTION_BADи- PQresetPollвозвращает- PGRES_POLLING_FAILED.- 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, которые нужно связать с результатом. В случае сбоя обработчика объект результата очищается и ошибка распространяется дальше. Обработчик события не должен пытаться выполнять- 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никак нельзя сообщить об ошибке. Кроме того, ошибка в обработчике событий не должна прерывать процесс очистки ставшей ненужной памяти.
30.13.2. Процедура обработки событий
-  PGEventProc
- PGEventProc— это определение типа для указателя на обработчик событий, то есть функцию обратного вызова, получающую события от libpq. Обработчик событий должен иметь такую сигнатуру:- int eventproc(PGEventId evtId, void *evtInfo, void *passThrough) - Параметр - evtIdговорит, какое событие- PGEVTпроизошло. Указатель- evtInfoдолжен приводиться к типу определённой структуры для получения дополнительной информации о событии. В параметре- passThroughпередаётся сквозной указатель, поступивший в- PQregisterEventProcпри регистрации обработчика события. Эта функция должна вернуть ненулевое значение в случае успеха или ноль в противном случае.- Обработчик определённого события может быть зарегистрирован в любом - PGconnтолько раз. Это связано с тем, что адрес обработчика используется как ключ для выбора связанных данных экземпляра.- Внимание- В Windows функции могут иметь два разных адреса: один, видимый снаружи DLL, и второй, видимый внутри DLL. Учитывая это, надо позаботиться о том, чтобы только один из адресов использовался с функциями обработки событий libpq, иначе возникнет путаница. Самый простой способ написать код, который будет работать — всегда помечать обработчик событий как - static. Если адрес обработчика нужно получить вне его исходного файла, экспортируйте отдельную функцию, которая будет возвращать этот адрес.
30.13.3. Функции поддержки событий
-  PQregisterEventProc
- Регистрирует обработчик событий в libpq. - int PQregisterEventProc(PGconn *conn, PGEventProc proc, const char *name, void *passThrough);- Обработчик событий должен быть зарегистрирован один раз для каждого соединения - PGconn, события которого представляют интерес. Число обработчиков событий, которые можно зарегистрировать для соединения, не ограничивается ничем, кроме объёма памяти. Эта функция возвращает ненулевое значение в случае успеха или ноль в противном случае.- Процедура, переданная в аргументе - proc, будет вызываться, когда произойдёт событие libpq. Её адрес в памяти также применяется для поиска данных- instanceData. Аргумент- nameиспользуется при упоминании обработчика событий в сообщениях об ошибках. Это значение не может быть равно- NULLили указывать на строку нулевой длины. Эта строка имени копируется в- PGconn, так что переданная строка может быть временной. Сквозной указатель (- passThrough) будет передаваться обработчику- procпри каждом вызове события. Этот аргумент может равняться- NULL.
-  PQsetInstanceData
- Устанавливает для подключения - connуказатель- instanceDataдля обработчика- procравным- data. Эта функция возвращает ненулевое значение в случае успеха или ноль в противном случае. (Ошибка возможна, только если обработчик- procне был корректно зарегистрирован для соединения- conn.)- int PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data); 
-  PQinstanceData
- Возвращает для соединения - connуказатель на- instanceData, связанный с обработчиком- proc, либо- NULL, если такого обработчика нет.- void *PQinstanceData(const PGconn *conn, PGEventProc proc); 
-  PQresultSetInstanceData
- Устанавливает для объекта результата ( - res) указатель- instanceDataдля обработчика- procравным- data. Эта функция возвращает ненулевое значение в случае успеха или ноль в противном случае. (Ошибка возможна, только если обработчик- procне был корректно зарегистрирован для объекта результата.)- int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data); 
-  PQresultInstanceData
- Возвращает для объекта результата ( - res) указатель на- instanceData, связанный с обработчиком- proc, либо- NULL, если такого обработчика нет.- void *PQresultInstanceData(const PGresult *res, PGEventProc proc); 
30.13.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)
    {
        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 */
}