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

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

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



Типы событий

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

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

Событие уничтожения результата запускается в ответ на выполнение функции PQclear. Процедура этого события отвечает за корректную очистку своих данных событий, поскольку libpq неспособна управлять этой памятью. Невыполнение очистки приведет к утечкам памяти.

typedef struct
{
    PGresult *result;
} PGEventResultDestroy;

При получении события PGEVT_RESULTDESTROY указатель evtInfo следует привести к PGEventResultDestroy *. Это событие запускается до того, как функция PQclear производит всю остальную очистку. Значение, возвращаемое процедурой событий, игнорируется, так как из PQclear никак нельзя сообщить об ошибке. Кроме того, ошибка в процедуре событий не должна прерывать процесс очистки ненужной памяти.



Процедура обратного вызова для событий

PGEventProc

PGEventProc — это определение типа для указателя на процедуру событий, то есть пользовательскую функцию обратного вызова, получающую события от libpq. Сигнатура процедуры событий должна быть следующей:

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

Параметр evtId указывает, какое событие PGEVT произошло. Для получения дополнительной информации о событии указатель evtInfo следует привести к подходящему типу структуры. Параметр passThrough является указателем, переданным в функцию PQregisterEventProc при регистрации процедуры событий. Эта функция должна вернуть ненулевое значение в случае успехе, и ноль в случае ошибки.

Процедура определенного события может быть зарегистрирована в любом PGconn только один раз. Это связано с тем, что адрес процедуры используется как ключ поиска для определения связанных данных экземпляра.



Вспомогательные функции событий

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

Устанавливает для результата указатель 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);


Пример события

Здесь представлен схематичный пример управления внутренними данными, связанными с подключениями и результатами libpq.

/* требуется заголовок для событий libpq (примечание: включает libpq-fe.h) */
#include <libpq-events.h>

/* 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 включает завершающий символ перевода строки */
        fprintf(stderr, "%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;
    }

    /* доступен указатель на instanceData для подключения conn */
    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 для результата доступен, если при вызове
     * функции PQcopyResult использовался PG_COPYRES_EVENTS.
     */
    data = PQresultInstanceData(res_copy, myEventProc);

    /* Обе функции очистки передают событие 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) */
            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);

            /* связать внутренние данные приложения с результатом (скопировать его из результата) */
            PQresultSetInstanceData(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;
        }

        /* неизвестный идентификатор события, просто вернуть true. */
        default:
            break;
    }

    return true; /* обработка события успешно завершена */
}