32.14. Система событий#

32.14. Система событий

32.14. Система событий

Система событий libpq предназначена для уведомления зарегистрированных обработчиков событий о интересующих их событиях libpq, таких как создание или удаление объектов PGconn и PGresult. Основное применение заключается в том, что это позволяет приложениям ассоциировать свои собственные данные с объектами PGconn или PGresult и гарантировать, что эти данные будут освобождены в подходящее время.

Каждый зарегистрированный обработчик событий связан с двумя данными, известными только libpq как непрозрачные указатели void *. Существует указатель прохождения, который предоставляется приложением при регистрации обработчика событий с PGconn. Указатель прохождения никогда не меняется на протяжении жизни PGconn и всех созданных из него PGresult; поэтому, если он используется, он должен указывать на долгоживущие данные. Кроме того, есть указатель данных экземпляра, который изначально равен NULL в каждом PGconn и PGresult. Этот указатель может быть изменен с помощью функций PQinstanceData, PQsetInstanceData, PQresultInstanceData и PQresultSetInstanceData. Обратите внимание, что в отличие от указателя прохождения, данные экземпляра PGconn не наследуются автоматически PGresult, созданными из него. libpq не знает, на что указывают указатели прохождения и данных экземпляра (если на что-то указывают) и никогда не попытается их освободить - это ответственность обработчика событий.

32.14.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. В обоих случаях событие срабатывает только в случае успешного сброса. Возвращаемое значение процедуры события игнорируется в Tantor SE версии 15 и выше. Однако, в более ранних версиях важно вернуть успешный результат (ненулевое значение), иначе соединение будет прервано.

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_RESULTCOPY или 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 не может этого сделать. Если процедура события завершается неудачно (возвращает ноль), эта процедура события будет проигнорирована на протяжении оставшегося срока службы нового результата; то есть она не будет получать события PGEVT_RESULTCOPY или PGEVT_RESULTDESTROY для этого результата или результатов, скопированных из него.

PGEVT_RESULTDESTROY

Событие destroy вызывается в ответ на 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, для которого вы хотите получать события. Нет ограничений, кроме памяти, на количество процедур событий, которые могут быть зарегистрированы с соединением. Функция возвращает ненулевое значение в случае успеха и ноль в случае неудачи.

Аргумент proc будет вызван, когда происходит событие libpq. Его адрес памяти также используется для поиска instanceData. Аргумент name используется для ссылки на процедуру события в сообщениях об ошибках. Это значение не может быть NULL или строкой нулевой длины. Строка имени копируется в PGconn, поэтому передаваемое значение не обязательно должно быть долговечным. Указатель passThrough передается в proc при возникновении события. Этот аргумент может быть NULL.

PQsetInstanceData

Устанавливает значение instanceData для процедуры proc в соединении conn равным data. Возвращает ненулевое значение в случае успеха и ноль в случае неудачи. (Неудача возможна только если процедура proc не была правильно зарегистрирована в соединении conn).

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

PQinstanceData

Возвращает instanceData соединения conn, связанное с процедурой proc, или NULL, если такового нет.

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

PQresultSetInstanceData

Устанавливает instanceData результата для proc в data. Это возвращает ненулевое значение в случае успеха и ноль в случае ошибки. (Ошибка возможна только если proc не был правильно зарегистрирован в результате).

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

Будьте осторожны, что любое хранилище, представленное data, не будет учтено PQresultMemorySize, если оно не выделено с использованием PQresultAlloc. (Рекомендуется делать это, поскольку это позволяет избежать необходимости явно освобождать такое хранилище при уничтожении результата).

PQresultInstanceData

Возвращает instanceData результата, связанного с proc, или 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 */
}