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