他のバージョンの文書 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9.6 | 9.5 | 9.4 | 9.3 | 9.2 | 9.1 | 9.0 | 8.4 | 8.3 | 8.2 | 8.1 | 8.0 | 7.4 | 7.3 | 7.2

34.14. イベントシステム #

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

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

34.14.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の完了時点で発行されます。 どちらの場合も、初期化が成功したときのみ発行されます。 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から失敗を示唆する方法がないので無視されます。 同時に、イベントプロシージャの失敗が不要なメモリ消去処理を中止してはなりません。

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

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

34.14.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);

dataで示された領域は、PQresultAllocを使って割り当てたのでない限り、PQresultMemorySizeでは考慮されないことに注意してください。 (結果を破棄する時に、領域を明示的に解放する必要がなくなりますので、PQresultAllocを使って割り当てるのがお勧めです。)

PQresultInstanceData #

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

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

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