30.12. イベントシステム

libpqのイベントシステムは、PGconnおよび PGresultオブジェクトの作成と削除のような関心を引くlibpqイベントについて登録されたイベントハンドラに通知を行うため設計されています。主たる使用状況は、アプリケーションがそれ自身のデータをPGconnまたはPGresultと提携させ、データが適切な時間に開放されることを保証するものです。

それぞれの登録されたイベントハンドラはo libpqだけの曖昧としたvoid *ポインタとして知られる2つのデータの断片と提携します。イベントハンドラがPGconnで登録された時にアプリケーションが提供する通過地点ポインタがあります。通過地点ポインタはPGconnの寿命中決して変更せず、全ての(複数の)PGresultはそれから生成されます。従って使用された場合、長期間生存しているデータを指し示します。更に、インスタンスデータポインタがあって、それは全てのPGconnPGresultでNULLから開始します。ポインタは、 PQinstanceDataPQsetInstanceDataPQresultInstanceDataおよび PQsetResultInstanceData関数を使って操作することができます。 通過地点ポインタの様にではなく、PGconnインスタンスデータはそれから作成されたPGresultにより自動的に継承されません。libpqは通過地点とインスタンスデータポインタが何を(もしあれば)指し示すのか判らず、決して開放しようとは試みません。それはイベントハンドラの責任です。

30.12.1. イベントの種類

enum PGEventIdはイベントシステムにより処理されるイベントの種類に名前をつけます。全てのその値はPGEVTで始まる名前を持っています。それぞれのイベントの種類に対し、イベントハンドラに渡されるパラメータを運ぶ関連したイベント情報構造体があります。イベントの種類は以下のようです。

PGEVT_REGISTER

登録イベントはPQregisterEventProcが呼ばれたとき発生します。それはイベントプロシージャが必要とするかもしれないどんなinstanceDataでもを初期化する理想的な時間です。接続毎、イベントハンドラ毎、たった1つの登録イベントが起動されます。イベントプロシージャが失敗すると、登録は中止されます。

typedef struct
{
    PGconn *conn;
} PGEventRegister;
      

PGEVT_REGISTERイベントが受け取られると、evtInfoポインタはPGEventRegister *にキャストされなければなりません。この構造体はCONNECTION_OKステータスの中になくてはならないPGconnを含んでいます。そしてそれは、効果のあるPGconnを取得した直後、1つがPQregisterEventProcを呼び出せば、保証されます。失敗コードを返すとき、PGEVT_CONNDESTROYイベントが送られないので、全ての掃除が実行されなければなりません。

PGEVT_CONNRESET

接続初期化イベントはPQresetまたはPQresetPollの完了時点で起動されます。どちらの場合も、初期化が成功したときのみ起動されます。イベントプロシージャが失敗すると、全部の接続初期化が失敗します。PGconnCONNECTION_BADステータスの中に置かれ、PQresetPollPGRES_POLLING_FAILEDを返します。

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をも初期化する理想的な場所です。イベントプロシージャが失敗すると、結果は掃除され、失敗は伝播します。イベントプロシージャはそれ自身のため結果オブジェクトをPQclearしようと試みてはいけません。失敗コードを返す時、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結果が複写の目的場所である限り複写されるものです。このイベントはPQcopyResultがその様に行えない理由からinstanceDataの深い複写を提供するために使用されます。もしイベントプロシージャが失敗すると、全体の複写操作は失敗になり、dest結果は除去されます。失敗コードを返す時、PGEVT_RESULTDESTROYイベントは目的結果に対し送られないため全ての掃除が行われなければなりません。

PGEVT_RESULTDESTROY

結果破棄イベントはPQclearの応答として起動されます。libpqはこのメモリーを管理する能力に欠けるため、そのイベントデータを的確に掃除するのはイベントプロシージャの責任です。掃除の失敗はメモリーリークに通じます。

typedef struct
{
    PGresult *result;
} PGEventResultDestroy;
      

PGEVT_RESULTDESTROYが受け取られた時、evtInfoポインタはPGEventResultDestroy *にキャストされなければなりません。このイベントはPQclearがその他の掃除を行う以前に起動されなければなりません。イベントプロシージャの戻り値は、PQclearから失敗を示唆する方法がないので無視されます。同時に、イベントプロシージャの失敗が望まれないメモリー掃除プロセスを中止してはなりません。

30.12.2. イベントコールバックプロシージャ

PGEventProc

PGEventProcはイベントプロシージャへのポインタに対するtypedefです。つまり、libpqからイベントを受け取るユーザコールバック関数です。イベントプロシージャの署名は以下でなければなりません。

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

evtIdパラメータはどのevtIdイベントが発生したかを示します。evtInfoポインタは、イベントに対する追加的情報を入手するため適切な構造体型にキャストされなければなりません。passThroughパラメータは、イベントプロシージャが登録された時、PQregisterEventProcに提供されるポインタです。関数は成功した場合非ゼロを、失敗した場合ゼロを返さなければなりません。

特定のイベントプロシージャはどんなPGconnにおいても一回だけ登録することができます。これは、関連するインスタンスデータを特定する検索キーとして用いられるからです。

注意

Windowsにおいて、関数は2つの異なるアドレスを持つことができます。外部から可視のDLLと内部から可視のDLLです。libpqのイベントプロシージャ関数ではこれらのアドレスのうちの1つだけが使用されます。さもないと、混乱が起きます。正常に機能するコードを書く最も単純な規則は、イベントプロシージャがstaticとして宣言されることを確実にすることです。もし、プロシージャのアドレスがそれ自身のファイルの外部から有効とならなければならない場合、アドレスを返すため別の関数を公開します。

30.12.3. イベント支援関数

PQregisterEventProc

libpqでイベントコールバックプロシージャを登録します。

        int PQregisterEventProc(PGconn *conn, PGEventProc proc,
                                const char *name, void *passThrough);
       

それに関するイベントを取得したいそれぞれのPGconnで1回イベントプロシージャは登録されなければなりません。接続で登録可能な多くのイベントプロシージャ上で、メモリーを除いて制限はありません。関数は成功した場合非ゼロ、失敗の場合ゼロを返します。

libpqイベントが起動されたときproc引数が呼ばれます。そのメモリーアドレスはinstanceDataを検索するのにも使用されます。name引数はエラーメッセージ内のイベントプロシージャを参照するために使用されます。この値はNULLもしくは空文字列であってはなりません。名目文字列は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);
       

PQresultInstanceData

procに関連した結果のinstanceData、または存在しなければNULLを返します。

        void *PQresultInstanceData(const PGresult *res, PGEventProc proc);
       

30.12.4. イベント事例

以下にlibpq接続と結果に関連したprivate dataマッピングの骨格事例を示します。


/* 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");

    if (PQstatus(conn) != CONNECTION_OK)
    {
        fprintf(stderr, "Connection to database failed: %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);
    /* If 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から複写して)関連付ける */
            PQsetResultInstanceData(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);

            /* アプリ特有のデータを結果と(結果から複写して)関連付ける */
            PQsetResultInstanceData(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; /* イベント処理成功 */
}