34.14. Система событий #
Система событий libpq разработана для уведомления функций-обработчиков об интересных событиях libpq, например, о создании и уничтожении объектов PGconn
и PGresult
. Основное их предназначение в том, чтобы позволить приложениям связать собственные данные с объектами PGconn
и PGresult
и обеспечить их освобождение в нужное время.
Каждый зарегистрированный обработчик событий связывается с двумя элементами данных, которые известны libpq только как скрытые указатели void *
. Первый сквозной указатель передаётся приложением, когда обработчик событий регистрируется в PGconn
. Этот указатель никогда не меняется на протяжении жизни PGconn
и все объекты PGresult
создаются с ним; поэтому, если он используется, он должен указывать на долгоживущие данные. В дополнение к нему имеется указатель данных экземпляра, который изначально равен NULL
во всех объектах PGconn
и PGresult
. Этим указателем можно управлять с помощью функций PQinstanceData
, PQsetInstanceData
, PQresultInstanceData
и PQresultSetInstanceData
. Заметьте, что в отличие от сквозного указателя, данные экземпляра PGconn
автоматически не наследуются объектами PGresult
, создаваемыми из него. Библиотека libpq не знает, на что указывают сквозной указатель и указатель данных экземпляра (если они ненулевые), и никогда не будет пытаться освобождать их — за это отвечает обработчик событий.
34.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
. В обоих случаях это событие вызывается, только если сброс был успешным. Значение, возвращаемое процедурой события, в Postgres Pro v15 и более поздних версиях игнорируется. Однако с более ранними версиями важно возвращать успешный результат (отличный от нуля), иначе соединение будет прервано.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
никак нельзя сообщить об ошибке. Кроме того, ошибка в обработчике событий не должна прерывать процесс очистки ставшей ненужной памяти.
34.14.2. Процедура обработки событий #
PGEventProc
#PGEventProc
— это определение типа для указателя на обработчик событий, то есть функцию обратного вызова, получающую события от libpq. Обработчик событий должен иметь такую сигнатуру:int eventproc(PGEventId evtId, void *evtInfo, void *passThrough)
Параметр
evtId
говорит, какое событиеPGEVT
произошло. УказательevtInfo
должен приводиться к типу определённой структуры для получения дополнительной информации о событии. В параметреpassThrough
передаётся сквозной указатель, поступивший вPQregisterEventProc
при регистрации обработчика события. Эта функция должна вернуть ненулевое значение в случае успеха или ноль в противном случае.Обработчик определённого события может быть зарегистрирован в любом
PGconn
только раз. Это связано с тем, что адрес обработчика используется как ключ для выбора связанных данных экземпляра.Внимание
В Windows функции могут иметь два разных адреса: один, видимый снаружи DLL, и второй, видимый внутри DLL. Учитывая это, надо позаботиться о том, чтобы только один из адресов использовался с функциями обработки событий libpq, иначе возникнет путаница. Самый простой способ написать код, который будет работать — всегда помечать обработчик событий как
static
. Если адрес обработчика нужно получить вне его исходного файла, экспортируйте отдельную функцию, которая будет возвращать этот адрес.
34.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
#Устанавливает для подключения
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);
Имейте в виду, что память, представленная параметром
data
, не будет учитываться вPQresultMemorySize
, если только она не была выделена функциейPQresultAlloc
. (Этой функцией рекомендуется пользоваться, так как это избавляет от необходимости явно освобождать память после уничтожения результата.)PQresultInstanceData
#Возвращает для объекта результата (
res
) указатель наinstanceData
, связанный с обработчикомproc
, либоNULL
, если такого обработчика нет.void *PQresultInstanceData(const PGresult *res, PGEventProc proc);
34.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 */ }