pqeventmonitor.pp 8.1 KB

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