31.12. イベントシステム

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

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

31.12.1. イベントの種類

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の完了時点で発行されます。 どちらの場合も、初期化が成功したときのみ発行されます。 イベントプロシージャが失敗すると、接続初期化全体が失敗します。 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結果はコピー先です。 このイベントはinstanceDataのディープコピーを提供するために使用されます。 PQcopyResultではこれを行うことができないためです。 もしイベントプロシージャが失敗すると、コピー操作全体は失敗になり、dest結果は消去されます。 失敗コードを返す時、PGEVT_RESULTDESTROYイベントがコピー先の結果に対し送られないため、すべての消去を行われなければなりません。

PGEVT_RESULTDESTROY

結果破棄イベントはPQclearに対応して発行されます。 libpqはこのメモリを管理する機能がありませんので、そのイベントデータを的確に消去するのはイベントプロシージャの責任です。 消去の失敗はメモリーリークに通じます。

typedef struct
{
    PGresult *result;
} PGEventResultDestroy;

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

31.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として宣言されることを確実にすることです。 もし、プロシージャのアドレスがそれ自身のファイルの外部から有効とならなければならない場合、アドレスを返すため別の関数を公開します。

31.12.3. イベントサポート関数

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接続のinstanceDatadataに設定します。 成功の場合非ゼロ、失敗の場合ゼロが返ります。 (connprocが正しく登録されていない場合のみ失敗する可能性があります。)

int PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data);

PQinstanceData

procプロシージャに関連したconn接続のinstanceData、または存在しなければNULLを返します。

void *PQinstanceData(const PGconn *conn, PGEventProc proc);

PQresultSetInstanceData

procに対する結果のinstanceDatadataに設定します。 成功の場合非ゼロ、失敗の場合ゼロが返ります。 (結果でproc正しく登録されていない場合のみ失敗する可能性があります。)

int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data);

PQresultInstanceData

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

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

31.12.4. イベント事例

以下に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");

    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);
    /* 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; /* イベント処理成功 */
}