無料スクリプト配布のPHP.TO   PHPの実用的なtips PHPマニュアル MySQLマニュアル Apacheマニュアル PostgreSQLマニュアル マニュアル検索    

31.13. イベントシステム

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

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

31.13.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 の完了時点で発行されます。 どちらの場合も、初期化が成功したときのみ発行されます。 イベントプロシージャが失敗すると、接続初期化全体が失敗します。 PGconn CONNECTION_BAD 状態になり、 PQresetPoll PGRES_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.13.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.13.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 接続の 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);

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

powered by SEO.CUG.NET