33.13. Event System
  
   libpq
  
  's event system is designed to notify
   registered event handlers about interesting
  
   libpq
  
  events, such as the creation or
   destruction of
  
   PGconn
  
  and
  
   PGresult
  
  objects.  A principal use case is that
   this allows applications to associate their own data with a
  
   PGconn
  
  or
  
   PGresult
  
  and ensure that that data is freed at an appropriate time.
 
  Each registered event handler is associated with two pieces of data,
   known to
  
   libpq
  
  only as opaque
  
   void *
  
  pointers.  There is a
  
   passthrough
  
  pointer that is provided
   by the application when the event handler is registered with a
  
   PGconn
  
  .  The passthrough pointer never changes for the
   life of the
  
   PGconn
  
  and all
  
   PGresult
  
  s
   generated from it; so if used, it must point to long-lived data.
   In addition there is an
  
   instance data
  
  pointer, which starts
   out
  
   NULL
  
  in every
  
   PGconn
  
  and
  
   PGresult
  
  .
   This pointer can be manipulated using the
  
   PQinstanceData
  
  ,
  
   PQsetInstanceData
  
  ,
  
   PQresultInstanceData
  
  and
  
   PQsetResultInstanceData
  
  functions.  Note that
   unlike the passthrough pointer, instance data of a
  
   PGconn
  
  is not automatically inherited by
  
   PGresult
  
  s created from
   it.
  
   libpq
  
  does not know what passthrough
   and instance data pointers point to (if anything) and will never attempt
   to free them - that is the responsibility of the event handler.
 
33.13.1. Event Types
   The enum
   
    PGEventId
   
   names the types of events handled by
    the event system.  All its values have names beginning with
   
    PGEVT
   
   .  For each event type, there is a corresponding
    event info structure that carries the parameters passed to the event
    handlers.  The event types are:
  
- 
     
      PGEVT_REGISTER
- 
     The register event occurs when PQregisterEventProcis called. It is the ideal time to initialize anyinstanceDataan event procedure may need. Only one register event will be fired per event handler per connection. If the event procedure fails, the registration is aborted.typedef struct { PGconn *conn; } PGEventRegister;When a PGEVT_REGISTERevent is received, theevtInfopointer should be cast to aPGEventRegister *. This structure contains aPGconnthat should be in theCONNECTION_OKstatus; guaranteed if one callsPQregisterEventProcright after obtaining a goodPGconn. When returning a failure code, all cleanup must be performed as noPGEVT_CONNDESTROYevent will be sent.
- 
     
      PGEVT_CONNRESET
- 
     The connection reset event is fired on completion of PQresetorPQresetPoll. In both cases, the event is only fired if the reset was successful. If the event procedure fails, the entire connection reset will fail; thePGconnis put intoCONNECTION_BADstatus andPQresetPollwill returnPGRES_POLLING_FAILED.typedef struct { PGconn *conn; } PGEventConnReset;When a PGEVT_CONNRESETevent is received, theevtInfopointer should be cast to aPGEventConnReset *. Although the containedPGconnwas just reset, all event data remains unchanged. This event should be used to reset/reload/requery any associatedinstanceData. Note that even if the event procedure fails to processPGEVT_CONNRESET, it will still receive aPGEVT_CONNDESTROYevent when the connection is closed.
- 
     
      PGEVT_CONNDESTROY
- 
     The connection destroy event is fired in response to PQfinish. It is the event procedure's responsibility to properly clean up its event data as libpq has no ability to manage this memory. Failure to clean up will lead to memory leaks.typedef struct { PGconn *conn; } PGEventConnDestroy;When a PGEVT_CONNDESTROYevent is received, theevtInfopointer should be cast to aPGEventConnDestroy *. This event is fired prior toPQfinishperforming any other cleanup. The return value of the event procedure is ignored since there is no way of indicating a failure fromPQfinish. Also, an event procedure failure should not abort the process of cleaning up unwanted memory.
- 
     
      PGEVT_RESULTCREATE
- 
     The result creation event is fired in response to any query execution function that generates a result, including PQgetResult. This event will only be fired after the result has been created successfully.typedef struct { PGconn *conn; PGresult *result; } PGEventResultCreate;When a PGEVT_RESULTCREATEevent is received, theevtInfopointer should be cast to aPGEventResultCreate *. Theconnis the connection used to generate the result. This is the ideal place to initialize anyinstanceDatathat needs to be associated with the result. If the event procedure fails, the result will be cleared and the failure will be propagated. The event procedure must not try toPQclearthe result object for itself. When returning a failure code, all cleanup must be performed as noPGEVT_RESULTDESTROYevent will be sent.
- 
     
      PGEVT_RESULTCOPY
- 
     The result copy event is fired in response to PQcopyResult. This event will only be fired after the copy is complete. Only event procedures that have successfully handled thePGEVT_RESULTCREATEorPGEVT_RESULTCOPYevent for the source result will receivePGEVT_RESULTCOPYevents.typedef struct { const PGresult *src; PGresult *dest; } PGEventResultCopy;When a PGEVT_RESULTCOPYevent is received, theevtInfopointer should be cast to aPGEventResultCopy *. Thesrcresult is what was copied while thedestresult is the copy destination. This event can be used to provide a deep copy ofinstanceData, sincePQcopyResultcannot do that. If the event procedure fails, the entire copy operation will fail and thedestresult will be cleared. When returning a failure code, all cleanup must be performed as noPGEVT_RESULTDESTROYevent will be sent for the destination result.
- 
     
      PGEVT_RESULTDESTROY
- 
     The result destroy event is fired in response to a PQclear. It is the event procedure's responsibility to properly clean up its event data as libpq has no ability to manage this memory. Failure to clean up will lead to memory leaks.typedef struct { PGresult *result; } PGEventResultDestroy;When a PGEVT_RESULTDESTROYevent is received, theevtInfopointer should be cast to aPGEventResultDestroy *. This event is fired prior toPQclearperforming any other cleanup. The return value of the event procedure is ignored since there is no way of indicating a failure fromPQclear. Also, an event procedure failure should not abort the process of cleaning up unwanted memory.
33.13.2. Event Callback Procedure
- 
     
      PGEventProc
- 
     PGEventProcis a typedef for a pointer to an event procedure, that is, the user callback function that receives events from libpq. The signature of an event procedure must beint eventproc(PGEventId evtId, void *evtInfo, void *passThrough) The evtIdparameter indicates whichPGEVTevent occurred. TheevtInfopointer must be cast to the appropriate structure type to obtain further information about the event. ThepassThroughparameter is the pointer provided toPQregisterEventProcwhen the event procedure was registered. The function should return a non-zero value if it succeeds and zero if it fails.A particular event procedure can be registered only once in any PGconn. This is because the address of the procedure is used as a lookup key to identify the associated instance data.CautionOn Windows, functions can have two different addresses: one visible from outside a DLL and another visible from inside the DLL. One should be careful that only one of these addresses is used with libpq 's event-procedure functions, else confusion will result. The simplest rule for writing code that will work is to ensure that event procedures are declared static. If the procedure's address must be available outside its own source file, expose a separate function to return the address.
33.13.3. Event Support Functions
- 
     
      PQregisterEventProc
- 
     Registers an event callback procedure with libpq. int PQregisterEventProc(PGconn *conn, PGEventProc proc, const char *name, void *passThrough);An event procedure must be registered once on each PGconnyou want to receive events about. There is no limit, other than memory, on the number of event procedures that can be registered with a connection. The function returns a non-zero value if it succeeds and zero if it fails.The procargument will be called when a libpq event is fired. Its memory address is also used to lookupinstanceData. Thenameargument is used to refer to the event procedure in error messages. This value cannot beNULLor a zero-length string. The name string is copied into thePGconn, so what is passed need not be long-lived. ThepassThroughpointer is passed to theprocwhenever an event occurs. This argument can beNULL.
- 
     
      PQsetInstanceData
- 
     Sets the connection conn'sinstanceDatafor procedureproctodata. This returns non-zero for success and zero for failure. (Failure is only possible ifprochas not been properly registered inconn.)int PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data); 
- 
     
      PQinstanceData
- 
     Returns the connection conn'sinstanceDataassociated with procedureproc, orNULLif there is none.void *PQinstanceData(const PGconn *conn, PGEventProc proc); 
- 
     
      PQresultSetInstanceData
- 
     Sets the result's instanceDataforproctodata. This returns non-zero for success and zero for failure. (Failure is only possible ifprochas not been properly registered in the result.)int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data); Beware that any storage represented by datawill not be accounted for byPQresultMemorySize, unless it is allocated usingPQresultAlloc. (Doing so is recommendable because it eliminates the need to free such storage explicitly when the result is destroyed.)
- 
     
      PQresultInstanceData
- 
     Returns the result's instanceDataassociated withproc, orNULLif there is none.void *PQresultInstanceData(const PGresult *res, PGEventProc proc); 
33.13.4. Event Example
Here is a skeleton example of managing private data associated with libpq connections and results.
/* required header for libpq events (note: includes libpq-fe.h) */ #include/* The 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) { fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn)); PQfinish(conn); return 1; } /* called once on any connection that should receive events. * Sends a PGEVT_REGISTER to myEventProc. */ if (!PQregisterEventProc(conn, myEventProc, "mydata_proc", NULL)) { fprintf(stderr, "Cannot register PGEventProc\n"); PQfinish(conn); return 1; } /* conn instanceData is available */ data = PQinstanceData(conn, myEventProc); /* Sends a PGEVT_RESULTCREATE to myEventProc */ res = PQexec(conn, "SELECT 1 + 1"); /* result instanceData is available */ data = PQresultInstanceData(res, myEventProc); /* If PG_COPYRES_EVENTS is used, sends a PGEVT_RESULTCOPY to myEventProc */ res_copy = PQcopyResult(res, PG_COPYRES_TUPLES | PG_COPYRES_EVENTS); /* result instanceData is available if PG_COPYRES_EVENTS was * used during the PQcopyResult call. */ data = PQresultInstanceData(res_copy, myEventProc); /* Both clears send a PGEVT_RESULTDESTROY to myEventProc */ PQclear(res); PQclear(res_copy); /* Sends a PGEVT_CONNDESTROY to 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); /* associate app specific data with connection */ 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); /* free instance data because the conn is being destroyed */ 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); /* associate app specific data with result (copy it from 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); /* associate app specific data with result (copy it from a result) */ PQsetResultInstanceData(e->dest, myEventProc, dest_data); break; } case PGEVT_RESULTDESTROY: { PGEventResultDestroy *e = (PGEventResultDestroy *)evtInfo; mydata *data = PQresultInstanceData(e->result, myEventProc); /* free instance data because the result is being destroyed */ if (data) free_mydata(data); break; } /* unknown event ID, just return true. */ default: break; } return true; /* event processing succeeded */ }