イベントシステム

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

# 31.12. イベントシステム

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

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

## 31.12.1. イベントの種類

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

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

`PQregisterEventProc`

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

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

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

  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.12.4. イベント事例

以下にlibpq接続と結果に関連したプライベートデータを管理する例の大枠を示します。

    /* libpqイベントに必要なヘッダ（覚書：libpq-fe.hのインクルード) */
    #include
    /* 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);
        /* 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; /* イベント処理成功 */
    }

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