pqeventmonitor.pp 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. unit PQEventMonitor;
  2. { PostGresql notification monitor
  3. Copyright (C) 2012 Ludo Brands
  4. This library is free software; you can redistribute it and/or modify it
  5. under the terms of the GNU Library General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or (at your
  7. option) any later version with the following modification:
  8. As a special exception, the copyright holders of this library give you
  9. permission to link this library with independent modules to produce an
  10. executable, regardless of the license terms of these independent modules,and
  11. to copy and distribute the resulting executable under terms of your choice,
  12. provided that you also meet, for each linked independent module, the terms
  13. and conditions of the license of that module. An independent module is a
  14. module which is not derived from or based on this library. If you modify
  15. this library, you may extend this exception to your version of the library,
  16. but you are not obligated to do so. If you do not wish to do so, delete this
  17. exception statement from your version.
  18. This program is distributed in the hope that it will be useful, but WITHOUT
  19. ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  20. FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
  21. for more details.
  22. You should have received a copy of the GNU Library General Public License
  23. along with this library; if not, write to the Free Software Foundation,
  24. Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  25. }
  26. {$mode objfpc}{$H+}
  27. {$Define LinkDynamically}
  28. interface
  29. uses
  30. Classes, SysUtils,pqconnection,db,dbconst,
  31. {$IfDef LinkDynamically}
  32. postgres3dyn;
  33. {$Else}
  34. postgres3;
  35. {$EndIf}
  36. type
  37. TEventAlert = procedure(Sender: TObject; EventName: string; EventCount: longint;
  38. var CancelAlerts: boolean) of object;
  39. TEventAlertPayload = procedure(Sender: TObject; EventName, PayLoad: string; EventCount: longint;
  40. var CancelAlerts: boolean) of object;
  41. TErrorEvent = procedure(Sender: TObject; ErrorCode: integer) of object;
  42. { TPQEventMonitor }
  43. TPQEventMonitor=class (TComponent)
  44. private
  45. FConnection: TPQConnection;
  46. FDBHandle: PPGconn;
  47. FErrorMsg: string;
  48. FEvents: TStrings;
  49. FOnError: TErrorEvent;
  50. FOnEventAlert: TEventAlert;
  51. FOnEventAlertPayLoad: TEventAlertPayload;
  52. FRegistered: Boolean;
  53. function GetNativeHandle: pointer;
  54. procedure SetConnection(AValue: TPQConnection);
  55. procedure SetEvents(AValue: TStrings);
  56. procedure SetRegistered(AValue: Boolean);
  57. public
  58. constructor Create(AOwner: TComponent); override;
  59. destructor Destroy; override;
  60. procedure Poll;
  61. procedure RegisterEvents; virtual;
  62. procedure UnRegisterEvents; virtual;
  63. property ErrorMsg:string read FErrorMsg;
  64. property NativeHandle: pointer read GetNativeHandle;
  65. published
  66. property Connection: TPQConnection read FConnection write SetConnection;
  67. property Events: TStrings read FEvents write SetEvents;
  68. property Registered: Boolean read FRegistered write SetRegistered;
  69. property OnEventAlert: TEventAlert read FOnEventAlert write FOnEventAlert;
  70. property OnEventAlertPayload: TEventAlertPayload read FOnEventAlertPayload write FOnEventAlertPayload;
  71. property OnError: TErrorEvent read FOnError write FOnError;
  72. end;
  73. implementation
  74. ResourceString
  75. SErrConnectionFailed = 'Connection to database failed';
  76. SErrExecuteFailed = 'Execution of query failed';
  77. { TPQEventMonitor }
  78. function TPQEventMonitor.GetNativeHandle: pointer;
  79. begin
  80. result:=FDBHandle;
  81. end;
  82. procedure TPQEventMonitor.SetConnection(AValue: TPQConnection);
  83. begin
  84. if FConnection=AValue then Exit;
  85. If not (csDesigning in ComponentState) and FRegistered then
  86. begin
  87. if assigned(FConnection) then
  88. FConnection.RemoveFreeNotification(self); // remove us from the old connection
  89. UnRegisterEvents;
  90. FConnection:=AValue;
  91. if assigned(FConnection) then
  92. begin
  93. RegisterEvents;
  94. end;
  95. end
  96. else
  97. FConnection:=AValue;
  98. if assigned(FConnection) then
  99. FConnection.FreeNotification(Self); //in case Connection is destroyed before we are
  100. end;
  101. procedure TPQEventMonitor.SetEvents(AValue: TStrings);
  102. begin
  103. FEvents.Assign(AValue);
  104. end;
  105. procedure TPQEventMonitor.SetRegistered(AValue: Boolean);
  106. begin
  107. if not (csDesigning in ComponentState) then
  108. if AValue then
  109. RegisterEvents
  110. else
  111. UnRegisterEvents;
  112. end;
  113. constructor TPQEventMonitor.Create(AOwner: TComponent);
  114. begin
  115. inherited Create(AOwner);
  116. FEvents:=TStringList.Create;
  117. {$IfDef LinkDynamically}
  118. InitialisePostgres3; // stick to library in case connection closes before us
  119. {$EndIf}
  120. end;
  121. destructor TPQEventMonitor.Destroy;
  122. begin
  123. if FRegistered then
  124. UnRegisterEvents;
  125. if assigned(FConnection) then
  126. FConnection.RemoveFreeNotification(self);
  127. FEvents.Free;
  128. {$IfDef LinkDynamically}
  129. ReleasePostgres3;
  130. {$EndIf}
  131. inherited Destroy;
  132. end;
  133. procedure TPQEventMonitor.Poll;
  134. var
  135. notify:PpgNotify;
  136. CancelAlerts:boolean;
  137. begin
  138. if FConnection.Connected and FRegistered and (PQconsumeInput(FDBHandle)=1) then
  139. begin
  140. CancelAlerts:=false;
  141. repeat
  142. notify:=PQnotifies(FDBHandle);
  143. if assigned(notify) then
  144. begin
  145. if assigned(OnEventAlert) then
  146. OnEventAlert(Self,notify^.relname,1,CancelAlerts);
  147. if assigned(OnEventAlertPayLoad) then
  148. OnEventAlertPayLoad(Self,notify^.relname,Notify^.Extra,1,CancelAlerts);
  149. PQfreemem(notify);
  150. end;
  151. until not assigned(notify) or CancelAlerts;
  152. if CancelAlerts then
  153. UnRegisterEvents;
  154. end;
  155. end;
  156. procedure TPQEventMonitor.RegisterEvents;
  157. var
  158. i:Integer;
  159. sConn: String;
  160. res: PPGresult;
  161. msg:string;
  162. notify:PpgNotify;
  163. CancelAlerts:boolean;
  164. begin
  165. If not assigned(FConnection) then
  166. DatabaseError(SErrNoDatabaseAvailable,Self);
  167. if not(csDesigning in ComponentState) and not FRegistered and (Events.Count>0) then
  168. begin
  169. sConn := '';
  170. if (FConnection.UserName <> '') then sConn := sConn + ' user=''' + FConnection.UserName + '''';
  171. if (FConnection.Password <> '') then sConn := sConn + ' password=''' + FConnection.Password + '''';
  172. if (FConnection.HostName <> '') then sConn := sConn + ' host=''' + FConnection.HostName + '''';
  173. if (FConnection.DatabaseName <> '') then sConn := sConn + ' dbname=''' + FConnection.DatabaseName + '''';
  174. if (FConnection.Params.Text <> '') then sConn := sConn + ' '+FConnection.Params.Text;
  175. FDBHandle := PQconnectdb(pchar(sConn));
  176. if (PQstatus(FDBHandle) <> CONNECTION_OK) then
  177. begin
  178. msg := PQerrorMessage(FDBHandle);
  179. PQFinish(FDBHandle);
  180. DatabaseError(sErrConnectionFailed + ' (TPQEventMonitor: ' + Msg + ')',self);
  181. end;
  182. for i:=0 to Events.Count-1 do
  183. begin
  184. res := PQexec(FDBHandle,pchar('LISTEN '+ Events[i]));
  185. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  186. begin
  187. msg := PQerrorMessage(FDBHandle);
  188. PQclear(res);
  189. PQFinish(FDBHandle);
  190. FDBHandle:=nil;
  191. DatabaseError(SErrExecuteFailed + ' (TPQEventMonitor: ' + Msg + ')',self);
  192. end
  193. else
  194. PQclear(res);
  195. end;
  196. FRegistered :=true;
  197. end;
  198. end;
  199. procedure TPQEventMonitor.UnRegisterEvents;
  200. var
  201. i: Integer;
  202. res: PPGresult;
  203. msg:string;
  204. begin
  205. if not (csDesigning in ComponentState) and FRegistered then
  206. begin
  207. for i:=0 to Events.Count-1 do
  208. begin
  209. res := PQexec(FDBHandle,pchar('unlisten '+ Events[i]));
  210. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  211. begin
  212. msg := PQerrorMessage(FDBHandle);
  213. PQclear(res);
  214. PQFinish(FDBHandle);
  215. FDBHandle:=nil;
  216. DatabaseError(SErrExecuteFailed + ' (TPQEventMonitor: ' + Msg + ')',self);
  217. end
  218. else
  219. PQclear(res);
  220. end;
  221. PQFinish(FDBHandle);
  222. FDBHandle:=nil;
  223. FRegistered :=false;
  224. end;
  225. end;
  226. end.