イベントシステム

PostgreSQL 8.4.1文書
前のページ | 巻戻し | 第 30章libpq - C ライブラリ | 早送り | 次のページ

# 30.12. イベントシステム

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

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

## 30.12.1. イベントの種類

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

`PGEVT_REGISTER`

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

      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`結果が複写の目的場所である限り複写されるものです。このイベントは`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において、関数は２つの異なるアドレスを持つことができます。外部から可視のDLLと内部から可視のDLLです。libpqのイベントプロシージャ関数ではこれらのアドレスのうちの１つだけが使用されます。さもないと、混乱が起きます。正常に機能するコードを書く最も単純な規則は、イベントプロシージャが`static`として宣言されることを確実にすることです。もし、プロシージャのアドレスがそれ自身のファイルの外部から有効とならなければならない場合、アドレスを返すため別の関数を公開します。

## 30.12.3. イベント支援関数

`PQregisterEventProc`

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

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

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

  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;
        }
        /* イベントを受け取るべき全ての接続で１回呼ばれる
         * 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; /* イベント処理成功 */
    }

前のページ | ホーム | 次のページ
警告処理 | 上に戻る | 環境変数