libpqのイベントシステムは、PGconn
およびPGresult
オブジェクトの作成と削除のような関心を引くlibpqイベントについて登録されたイベントハンドラに通知を行うため設計されています。
主たる使用状況は、アプリケーションがそれ自身のデータをPGconn
またはPGresult
と提携させ、データが適切な時間に解放されることを保証するものです。
それぞれの登録されたイベントハンドラは、libpqからは不透明なvoid *
ポインタとしてだけ知られる2つのデータの断片と提携します。
イベントハンドラがPGconn
で登録された時にアプリケーションが提供する通過地点ポインタがあります。
通過地点ポインタはPGconn
やそれから生成されたすべての(複数の)PGresult
が有効な間決して変わることはありません。
したがって使用された場合、長期間生存しているデータを指し示します。
さらに、インスタンスデータポインタがあって、それはすべてのPGconn
とPGresult
でNULL
から開始します。
ポインタは、PQinstanceData
、PQsetInstanceData
、PQresultInstanceData
およびPQresultSetInstanceData
関数を使って操作することができます。
通過地点ポインタとは異なり、PGconn
のインスタンスデータはそれから作成されたPGresult
により自動的に継承されません。
libpqは通過地点とインスタンスデータポインタが(もしあったとしても)何を指し示すのか判らず、決して解放しようとは試みません。
それはイベントハンドラの責任です。
PGEventId
列挙はイベントシステムにより処理されるイベントの種類に名前をつけます。
その値はすべてPGEVT
で始まる名前を持っています。
それぞれのイベントの種類に対し、イベントハンドラに渡されるパラメータを運ぶ関連したイベント情報構造体があります。
イベントの種類を以下に示します。
PGEVT_REGISTER
#
登録イベントはPQregisterEventProc
が呼ばれたとき発生します。
イベントプロシージャが必要とするかもしれない任意のinstanceData
を初期化するために、これは理想的な機会です。
接続毎、イベントハンドラ毎でただ1つの登録イベントが発行されます。
イベントプロシージャが失敗する(ゼロが返される)と、登録はキャンセルされます。
typedef struct { PGconn *conn; } PGEventRegister;
PGEVT_REGISTER
イベントが受け取られると、evtInfo
ポインタはPGEventRegister *
にキャストされなければなりません。
この構造体はCONNECTION_OK
状態ではなくてはならないPGconn
を含んでいます。
そしてそれは、効果のあるPGconn
を取得した直後、PQregisterEventProc
を呼び出せば、保証されます。
失敗コードを返すとき、PGEVT_CONNDESTROY
イベントが送られないので、すべての消去が実行されなければなりません。
PGEVT_CONNRESET
#
接続初期化イベントはPQreset
またはPQresetPoll
の完了時点で発行されます。
どちらの場合も、初期化が成功したときのみ発行されます。
PostgreSQLv15以降では、イベントプロシージャの返り値は無視されます。
しかし、以前のバージョンでは、成功(ゼロ以外)を返すことが重要です。
そうしないと接続は中断されます。
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
はイベントプロシージャへのポインタに対するtypedefです。
つまり、libpqからイベントを受け取るユーザコールバック関数です。
イベントプロシージャのシグネチャは以下でなければなりません。
int eventproc(PGEventId evtId, void *evtInfo, void *passThrough)
evtId
パラメータはどのPGEVT
イベントが発生したかを示します。
evtInfo
ポインタは、イベントに対する追加情報を入手するため適切な構造体型にキャストされなければなりません。
passThrough
パラメータは、イベントプロシージャが登録された時、PQregisterEventProc
に提供されるポインタです。
関数は成功した場合非ゼロを、失敗した場合ゼロを返さなければなりません。
特定のイベントプロシージャは任意のPGconn
において一回だけ登録できます。
これは、プロシージャのアドレスが関連するインスタンスデータを特定する検索キーとして用いられるからです。
Windowsにおいて、関数は2つの異なるアドレスを持つことができます。
外部から可視のDLLと内部から可視のDLLです。
libpqのイベントプロシージャ関数ではこれらのアドレスのうちの1つだけが使用されることに注意してください。
さもないと、混乱が起きます。
正常に機能するコードを書く最も単純な規則は、イベントプロシージャがstatic
として宣言されることを確実にすることです。
もし、プロシージャのアドレスがそれ自身のファイルの外部から有効とならなければならない場合、アドレスを返すため別の関数を公開します。
PQregisterEventProc
#libpqでイベントコールバックプロシージャを登録します。
int PQregisterEventProc(PGconn *conn, PGEventProc proc, const char *name, void *passThrough);
そのイベントを取得したいそれぞれのPGconn
で1回イベントプロシージャは登録されなければなりません。
一つの接続に登録できるイベントプロシージャの数には、メモリ以外の制限はありません。
関数は成功した場合非ゼロ、失敗の場合ゼロを返します。
libpqイベントが発行されたときproc
引数が呼ばれます。
そのメモリアドレスはinstanceData
を検索するのにも使用されます。
name
引数はエラーメッセージ内でイベントプロシージャを参照するために使用されます。
この値はNULL
もしくは空文字列であってはなりません。
このname文字列はPGconn
にコピーされますので、渡されたものは長寿命である必要がありません。
passThrough
ポインタはイベントが発生した時はいつでもproc
に渡されます。
この引数はNULL
であっても構いません。
PQsetInstanceData
#
proc
プロシージャに対するconn
接続のinstanceData
をdata
に設定します。
成功の場合非ゼロ、失敗の場合ゼロが返ります。
(conn
でproc
が正しく登録されていない場合のみ失敗する可能性があります。)
int PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data);
PQinstanceData
#
proc
プロシージャに関連したconn
接続のinstanceData
、または存在しなければNULL
を返します。
void *PQinstanceData(const PGconn *conn, PGEventProc proc);
PQresultSetInstanceData
#
proc
に対する結果のinstanceData
をdata
に設定します。
成功の場合非ゼロ、失敗の場合ゼロが返ります。
(結果でproc
正しく登録されていない場合のみ失敗する可能性があります。)
int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data);
data
で示された領域は、PQresultAlloc
を使って割り当てたのでない限り、PQresultMemorySize
では考慮されないことに注意してください。
(結果を破棄する時に、領域を明示的に解放する必要がなくなりますので、PQresultAlloc
を使って割り当てるのがお勧めです。)
PQresultInstanceData
#
proc
に関連した結果のinstanceData
、または存在しなければ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's result includes a trailing newline */ fprintf(stderr, "%s", PQerrorMessage(conn)); PQfinish(conn); return 1; } /* イベントを受け取るべきすべての接続で1回呼ばれる * myEventProcにPGEVT_REGISTERを送る */ if (!PQregisterEventProc(conn, myEventProc, "mydata_proc", NULL)) { fprintf(stderr, "Cannot register PGEventProc\n"); PQfinish(conn); return 1; } /* conn instanceDataが有効 */ data = PQinstanceData(conn, myEventProc); /* myEventProcにPGEVT_RESULTCREATEを送る */ 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); /* PQcopyResult呼び出しの過程でPG_COPYRES_EVENTSが使用された場合、 * 結果 instanceDataが有効 */ data = PQresultInstanceData(res_copy, myEventProc); /* 双方のclearが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; /* イベント処理成功 */ }