sqldbpool.pp 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292
  1. {
  2. Copyright (c) 2022 by Michael Van Canneyt
  3. SQLDB database pooling (thread-safe)
  4. See the file COPYING.FPC, included in this distribution,
  5. for details about the copyright.
  6. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  9. **********************************************************************}
  10. unit sqldbpool;
  11. {$mode objfpc}
  12. {$H+}
  13. interface
  14. uses
  15. Classes, SysUtils, db, sqldb, syncobjs, contnrs, baseunix;
  16. const
  17. DefaultDisconnectTimeOut = 10*60; // Number of seconds before connection is considered old and is discarded.
  18. type
  19. TPoolLogEvent = procedure(Sender : TObject; Const Msg : string) of object;
  20. ESQLDBPool = Class(EDatabaseError);
  21. { TSQLConnectionDef }
  22. { TSQLDBConnectionDef }
  23. TSQLDBConnectionDef = Class(TCollectionItem)
  24. private
  25. FConnectionClass: TSQLConnectionClass;
  26. FConnectionType: String;
  27. FDatabaseName: UTF8String;
  28. FEnabled: Boolean;
  29. FHostName: UTF8String;
  30. FName: UTF8String;
  31. FParams: TStrings;
  32. FPassword: UTF8string;
  33. FRole: UTF8String;
  34. FUserName: UTF8String;
  35. FKey : UTF8String;
  36. FCharSet : UTF8String;
  37. procedure DoChange(Sender: TObject);
  38. function GetPort: Word;
  39. procedure SetCharSet(AValue: UTF8String);
  40. procedure SetConnectionType(AValue: String);
  41. procedure SetDatabaseName(AValue: UTF8String);
  42. procedure SetHostName(AValue: UTF8String);
  43. procedure SetParams(AValue: TStrings);
  44. procedure SetPassword(AValue: UTF8string);
  45. procedure SetPort(AValue: Word);
  46. procedure SetRole(AValue: UTF8String);
  47. procedure SetUserName(AValue: UTF8String);
  48. Protected
  49. procedure AssignTo(Dest: TPersistent); override;
  50. procedure ClearKey;
  51. function GetName : UTF8String; virtual;
  52. Function GetDisplayName: string; override;
  53. function CreateKey : String; virtual;
  54. Public
  55. Constructor Create(ACollection: TCollection); override;
  56. Destructor Destroy; override;
  57. Procedure Assign(Source: TPersistent); override;
  58. Property ConnectionClass : TSQLConnectionClass Read FConnectionClass Write FConnectionClass;
  59. function GetDescription(Full: Boolean=False): string;
  60. Function ToString: string; override;
  61. Published
  62. // TSQLConnector type
  63. Property ConnectionType : String read FConnectionType write SetConnectionType;
  64. // Name for this connection
  65. Property Name : UTF8String read GetName write FName;
  66. // Database database name
  67. Property DatabaseName : UTF8String read FDatabaseName write SetDatabaseName;
  68. // Database hostname
  69. Property HostName : UTF8String read FHostName write SetHostName;
  70. // Database username
  71. Property UserName : UTF8String read FUserName write SetUserName;
  72. // Database role
  73. Property Role : UTF8String read FRole write SetRole;
  74. // Database user password
  75. Property Password : UTF8string read FPassword write SetPassword;
  76. // Other parameters
  77. Property Params : TStrings Read FParams Write SetParams;
  78. // Stored in Params.
  79. // Database character set
  80. Property CharSet : UTF8String Read FCharSet Write SetCharSet;
  81. // Port
  82. Property Port : Word Read GetPort Write SetPort;
  83. // Allow this connection to be used ?
  84. Property Enabled : Boolean Read FEnabled Write FEnabled default true;
  85. end;
  86. { TConnectionPoolData }
  87. TConnectionPoolData = Class(TObject)
  88. private
  89. FConnection: TSQLConnection;
  90. FLastUsed: TDateTime;
  91. FLocked: Boolean;
  92. Public
  93. Constructor Create(aConnection : TSQLConnection; aLocked : Boolean = true);
  94. Destructor Destroy; override;
  95. Procedure Lock;
  96. Procedure Unlock;
  97. Procedure FreeConnection;
  98. Property Connection : TSQLConnection Read FConnection;
  99. Property LastUsed : TDateTime Read FLastUsed Write FLastUsed;
  100. Property Locked : Boolean Read FLocked;
  101. end;
  102. { TSQLConnectionHelper }
  103. TSQLConnectionHelper = class helper for TSQLConnection
  104. Function GetDescription(Full : Boolean) : string;
  105. end;
  106. { TConnectionList }
  107. TConnectionList = Class (TFPObjectList)
  108. Private
  109. FonLog: TPoolLogEvent;
  110. FDisconnectTimeout: Integer;
  111. FLock : TCriticalSection;
  112. Protected
  113. Procedure Dolog(Const Msg : String);
  114. Procedure DoLog(Const Fmt : String; Args : Array of const);
  115. Function DoDisconnectOld(aTimeOut : Integer = -1) : Integer; virtual;
  116. function CreatePoolData(aConnection : TSQLConnection; aLocked : Boolean = True) : TConnectionPoolData;
  117. Public
  118. Constructor Create; reintroduce;
  119. Destructor Destroy; override;
  120. Procedure DisconnectAll;
  121. Function DisconnectOld(aTimeOut : Integer = -1) : Integer;
  122. function AddConnection (aConnection : TSQLConnection; aLocked : Boolean = True) : TConnectionPoolData;
  123. Function PopConnection : TSQLConnection;
  124. Function UnlockConnection(aConnection : TSQLConnection) : boolean;
  125. Property DisconnectTimeout : Integer Read FDisconnectTimeout Write FDisconnectTimeout;
  126. Property OnLog : TPoolLogEvent Read FonLog Write FOnLog;
  127. end;
  128. { TSQLDBConnectionPool }
  129. TSQLDBConnectionPool = class(TComponent)
  130. private
  131. FonLog: TPoolLogEvent;
  132. FPool : TFPObjectHashTable;
  133. FLock : TCriticalSection;
  134. procedure DisconnectAll;
  135. function GetCount: longword;
  136. protected
  137. Function CreateList : TConnectionList; virtual;
  138. Procedure Dolog(Const Msg : String);
  139. Procedure DoLog(Const Fmt : String; Args : Array of const);
  140. procedure Lock;
  141. procedure Unlock;
  142. function CreateKey(aDef : TSQLDBConnectionDef) : String; virtual;
  143. function CreateDef: TSQLDBConnectionDef;
  144. function DoFindConnection(const aConnectionDef: TSQLDBConnectionDef): TSQLConnection; virtual;
  145. procedure DoDisconnect(Item: TObject; const Key: string; var Continue: Boolean);
  146. public
  147. Constructor Create(aOwner : TComponent); override;
  148. Destructor Destroy; override;
  149. function CountConnections(aClass : TSQLConnectionClass; const aDatabaseName,aHostName,aUserName,aPassword: string; aParams:TStrings = nil):Integer;
  150. function CountConnections(aInstance : TSQLConnection):Integer;
  151. function CountConnections(aDef : TSQLDBConnectionDef):Integer;
  152. Function CountAllConnections : Integer;
  153. function FindConnection(aClass : TSQLConnectionClass; const aDatabaseName,aHostName,aUserName,aPassword: string; aParams:TStrings = nil):TSQLConnection;
  154. function FindConnection(const aConnectionDef : TSQLDBConnectionDef):TSQLConnection;
  155. procedure AddConnection(aConnection: TSQLConnection; aLocked: Boolean=True);
  156. function ReleaseConnection(aConnection: TSQLConnection) : Boolean;
  157. Property OnLog : TPoolLogEvent Read FonLog Write FOnLog;
  158. end;
  159. { TTypedConnectionPool }
  160. Generic TTypedConnectionPool<T: TSQLConnection> = class(TSQLDBConnectionPool)
  161. public
  162. function FindConnection(const aDatabaseName:string; const aHostName:string; const aUserName:string; const aPassword:string; aParams:TStrings=nil):T; overload;
  163. end;
  164. { TSQLDBConnectionDefList }
  165. TSQLDBConnectionDefList = Class(TOwnedCollection)
  166. private
  167. function GetD(aIndex : Integer): TSQLDBConnectionDef;
  168. procedure SetD(aIndex : Integer; AValue: TSQLDBConnectionDef);
  169. Public
  170. Function IndexOf(const aName : UTF8String) : Integer;
  171. Function Find(const aName : UTF8String) : TSQLDBConnectionDef;
  172. Function Get(const aName : UTF8String) : TSQLDBConnectionDef;
  173. Property Definitions[aIndex : Integer] : TSQLDBConnectionDef Read GetD Write SetD; default;
  174. end;
  175. { TSQLDBConnectionmanager }
  176. TSQLDBConnectionmanager = Class(TComponent)
  177. private
  178. FConnectionOwner: TComponent;
  179. FDefinitions: TSQLDBConnectionDefList;
  180. FMaxDBConnections: Word;
  181. FMaxTotalConnections: Cardinal;
  182. FOnLog: TPoolLogEvent;
  183. FPool : TSQLDBConnectionPool;
  184. FMyPool : TSQLDBConnectionPool;
  185. procedure SetConnectionOwner(AValue: TComponent);
  186. procedure SetDefinitions(AValue: TSQLDBConnectionDefList);
  187. procedure SetOnLog(AValue: TPoolLogEvent);
  188. procedure SetPool(AValue: TSQLDBConnectionPool);
  189. Protected
  190. Procedure DoLog(const Msg : String);
  191. Procedure DoLog(const Fmt : String; const aArgs : Array of const);
  192. function NewConnectionAllowed(aDef: TSQLDBConnectionDef; out aReason: string): Boolean; virtual;
  193. Procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  194. Function CreatePool : TSQLDBConnectionPool; virtual;
  195. Function CreateDefinitionList : TSQLDBConnectionDefList; virtual;
  196. Public
  197. Constructor Create(aOwner : TComponent); override;
  198. Destructor Destroy; override;
  199. Function CreateConnection(const aDef : TSQLDBConnectionDef; addToPool : Boolean) : TSQLConnection;
  200. Function CreateConnection(const aName : string; addToPool : Boolean) : TSQLConnection;
  201. Function GetConnection(const aDef : TSQLDBConnectionDef) : TSQLConnection;
  202. Function GetConnection(const aName : string) : TSQLConnection;
  203. Function ReleaseConnection(aConnection : TSQLConnection) : Boolean;
  204. Published
  205. Property Pool : TSQLDBConnectionPool Read FPool Write SetPool;
  206. Property Definitions : TSQLDBConnectionDefList Read FDefinitions Write SetDefinitions;
  207. Property MaxDBConnections : Word Read FMaxDBConnections Write FMaxDBConnections;
  208. Property MaxTotalConnections : Cardinal Read FMaxTotalConnections Write FMaxTotalConnections;
  209. Property ConnectionOwner : TComponent Read FConnectionOwner Write SetConnectionOwner;
  210. Property OnLog : TPoolLogEvent Read FOnLog Write SetOnLog;
  211. end;
  212. implementation
  213. uses typinfo, dateutils;
  214. Resourcestring
  215. SFindingConnection = 'Finding Connection (%s)';
  216. SFoundConnection = 'Found Connection (%s) : %x';
  217. SNoSuchConnection = 'No such Connection (%s)';
  218. SErrorDisconnecting = 'Error %s disconnecting connections : %s';
  219. SCreatingNewConnection = 'Creating new connection for connection definition (%s)';
  220. STimeoutReached = 'Timeout (%d>%d) reached, freeing connection (%s)';
  221. SReleasingConnections = 'Releasing connections (%s) (Current count: %d)';
  222. SErrCannotCreateNewConnection = 'Cannot create new connection for (%s): %s';
  223. SErrMaxNumberOfDefConnections = 'Max number of connections (%d) for this connection (%s) is reached';
  224. SErrMaxTotalConnectionReached = 'Max total number of connections (%d) is reached';
  225. SErrFreeingConnection = 'Error %s freeing connection %d : %s';
  226. { TSQLConnectionHelper }
  227. function TSQLConnectionHelper.GetDescription(Full: Boolean): string;
  228. Procedure AddTo(const aName,aValue : String);
  229. begin
  230. if aValue='' then
  231. exit;
  232. if Result<>'' then
  233. Result:=Result+', ';
  234. Result:=Result+aName+': '+aValue;
  235. end;
  236. var
  237. aPort : integer;
  238. begin
  239. Result:='';
  240. AddTo('Name',Name);
  241. AddTo('Host',HostName);
  242. AddTo('Database',DatabaseName);
  243. AddTo('User',Username);
  244. AddTo('Charset',CharSet);
  245. if IsPublishedProp(Self,'Port') then
  246. if PropIsType(Self,'Port',tkInteger) then
  247. begin
  248. aPort:=GetOrdProp(Self,'Port');
  249. if aPort>0 then
  250. AddTo('Port',IntToStr(aPort));
  251. end;
  252. if Full then
  253. begin
  254. AddTo('Password',Password);
  255. if Params.Count>0 then
  256. AddTo('Params',Params.CommaText);
  257. end;
  258. end;
  259. { TSQLDBConnectionDefList }
  260. function TSQLDBConnectionDefList.GetD(aIndex : Integer): TSQLDBConnectionDef;
  261. begin
  262. Result:=TSQLDBConnectionDef(Items[aIndex])
  263. end;
  264. procedure TSQLDBConnectionDefList.SetD(aIndex : Integer; AValue: TSQLDBConnectionDef
  265. );
  266. begin
  267. Items[aIndex]:=aValue;
  268. end;
  269. function TSQLDBConnectionDefList.IndexOf(const aName: UTF8String): Integer;
  270. begin
  271. Result:=Count-1;
  272. While (Result>=0) and not SameText(aName,GetD(Result).Name) do
  273. Dec(Result);
  274. end;
  275. function TSQLDBConnectionDefList.Find(const aName: UTF8String): TSQLDBConnectionDef;
  276. Var
  277. Idx : Integer;
  278. begin
  279. Result:=Nil;
  280. Idx:=IndexOf(aName);
  281. if Idx<>-1 then
  282. Result:=GetD(Idx);
  283. end;
  284. function TSQLDBConnectionDefList.Get(const aName: UTF8String): TSQLDBConnectionDef;
  285. begin
  286. Result:=Find(aName);
  287. if Result=Nil then
  288. end;
  289. { TSQLDBConnectionDef }
  290. procedure TSQLDBConnectionDef.DoChange(Sender: TObject);
  291. begin
  292. ClearKey;
  293. end;
  294. procedure TSQLDBConnectionDef.ClearKey;
  295. begin
  296. FKey:='';
  297. end;
  298. function TSQLDBConnectionDef.GetName: UTF8String;
  299. begin
  300. Result:=FName;
  301. end;
  302. function TSQLDBConnectionDef.GetPort: Word;
  303. begin
  304. Result:=StrToIntDef(FParams.Values['Port'],0);
  305. end;
  306. procedure TSQLDBConnectionDef.SetCharSet(AValue: UTF8String);
  307. begin
  308. FCharSet :=aValue;
  309. ClearKey;
  310. end;
  311. procedure TSQLDBConnectionDef.SetConnectionType(AValue: String);
  312. Var
  313. Def : TConnectionDef;
  314. begin
  315. if FConnectionType=AValue then Exit;
  316. FConnectionType:=AValue;
  317. if FConnectionType<>'' then
  318. begin
  319. Def:=GetConnectionDef(aValue);
  320. if Def<>Nil then
  321. ConnectionClass:=Def.ConnectionClass
  322. else
  323. ConnectionClass:=TSQLConnector;
  324. end
  325. else
  326. ConnectionClass:=Nil;
  327. end;
  328. procedure TSQLDBConnectionDef.SetDatabaseName(AValue: UTF8String);
  329. begin
  330. if FDatabaseName=AValue then Exit;
  331. FDatabaseName:=AValue;
  332. ClearKey;
  333. end;
  334. procedure TSQLDBConnectionDef.SetHostName(AValue: UTF8String);
  335. begin
  336. if FHostName=AValue then Exit;
  337. FHostName:=AValue;
  338. ClearKey;
  339. end;
  340. procedure TSQLDBConnectionDef.SetParams(AValue: TStrings);
  341. begin
  342. FParams.Assign(aValue);
  343. ClearKey;
  344. end;
  345. procedure TSQLDBConnectionDef.SetPassword(AValue: UTF8string);
  346. begin
  347. if FPassword=AValue then Exit;
  348. FPassword:=AValue;
  349. ClearKey;
  350. end;
  351. procedure TSQLDBConnectionDef.SetPort(AValue: Word);
  352. begin
  353. if aValue=0 then
  354. FParams.Values['Port']:=''
  355. else
  356. FParams.Values['Port']:=IntToStr(aValue)
  357. end;
  358. procedure TSQLDBConnectionDef.SetRole(AValue: UTF8String);
  359. begin
  360. if FRole=AValue then Exit;
  361. FRole:=AValue;
  362. ClearKey;
  363. end;
  364. procedure TSQLDBConnectionDef.SetUserName(AValue: UTF8String);
  365. begin
  366. if FUserName=AValue then Exit;
  367. FUserName:=AValue;
  368. ClearKey;
  369. end;
  370. procedure TSQLDBConnectionDef.AssignTo(Dest: TPersistent);
  371. var
  372. Conn : TSQLConnection absolute Dest;
  373. begin
  374. if Dest is TSQLDBConnectionDef then
  375. Dest.Assign(Self)
  376. else if Dest is TSQLConnection then
  377. begin
  378. Conn.DatabaseName := FDatabaseName;
  379. Conn.HostName := FHostName;
  380. Conn.Password := FPassword;
  381. Conn.UserName := FUserName;
  382. Conn.Role:=FRole;
  383. Conn.CharSet:=FCharSet;
  384. Conn.Params.Assign(Self.Params);
  385. if Conn is TSQLConnector then
  386. TSQLConnector(Conn).ConnectorType:=Self.ConnectionType;
  387. end
  388. else
  389. inherited AssignTo(Dest);
  390. end;
  391. function TSQLDBConnectionDef.GetDisplayName: string;
  392. begin
  393. Result:=Name;
  394. end;
  395. function TSQLDBConnectionDef.CreateKey: String;
  396. Var
  397. S : TStringList;
  398. N : String;
  399. begin
  400. if FKey<>'' then
  401. Exit(FKey);
  402. if Assigned(ConnectionClass) then
  403. N:=ConnectionClass.ClassName
  404. else
  405. N:=TSQLConnector.ClassName+'.'+ConnectionType;
  406. Result:=N
  407. +'#@'+HostName
  408. +'#@'+DatabaseName
  409. +'#@'+UserName
  410. +'#@'+Password
  411. +'#@'+Role
  412. +'#@'+CharSet;
  413. If Assigned(Params) then
  414. begin
  415. // Canonicalize
  416. S:=TStringList.Create;
  417. try
  418. S.Sorted:=true;
  419. S.AddStrings(Params);
  420. Result:=Result+'#@'+S.Text;
  421. finally
  422. S.Free;
  423. end;
  424. end;
  425. FKey:=Result;
  426. end;
  427. constructor TSQLDBConnectionDef.Create(ACollection: TCollection);
  428. begin
  429. inherited Create(ACollection);
  430. FParams:=TStringList.Create;
  431. TStringList(FParams).OnChange:=@DoChange;
  432. FEnabled:=True;
  433. end;
  434. destructor TSQLDBConnectionDef.Destroy;
  435. begin
  436. FParams.Free;
  437. inherited Destroy;
  438. end;
  439. procedure TSQLDBConnectionDef.Assign(Source: TPersistent);
  440. Var
  441. Def : TSQLDBConnectionDef absolute source;
  442. Conn : TSQLConnection absolute source;
  443. begin
  444. if Source is TSQLDBConnectionDef then
  445. begin
  446. FConnectionType:=Def.ConnectionType;
  447. FDatabaseName:=Def.DatabaseName;
  448. FHostName:=Def.HostName;
  449. FPassword:=Def.Password;
  450. FUserName:=Def.UserName;
  451. FName:=Def.Name;
  452. FCharSet:=Def.CharSet;
  453. FParams.Assign(Def.Params);
  454. FEnabled:=Def.Enabled;
  455. ClearKey;
  456. end
  457. else if Source is TSQLConnection then
  458. begin
  459. if Conn is TSQLConnector then
  460. FConnectionType:=TSQLConnector(Conn).ConnectorType
  461. else
  462. FConnectionClass:=TSQLConnectionClass(Conn.ClassType);
  463. FDatabaseName:=Conn.DatabaseName;
  464. FHostName:=Conn.HostName;
  465. FPassword:=Conn.Password;
  466. FUserName:=Conn.UserName;
  467. FName:='';
  468. FCharSet:=Conn.CharSet;
  469. FParams.Assign(Conn.Params);
  470. FEnabled:=Def.Enabled;
  471. ClearKey;
  472. end
  473. else
  474. inherited Assign(Source);
  475. end;
  476. function TSQLDBConnectionDef.GetDescription(Full : Boolean = False) : string;
  477. Procedure AddTo(const aName,aValue : String);
  478. begin
  479. if aValue='' then
  480. exit;
  481. if Result<>'' then
  482. Result:=Result+', ';
  483. Result:=Result+aName+': '+aValue;
  484. end;
  485. begin
  486. Result:='';
  487. AddTo('Name',Name);
  488. AddTo('Host',HostName);
  489. AddTo('Database',DatabaseName);
  490. AddTo('User',Username);
  491. AddTo('Charset',CharSet);
  492. if Port>0 then
  493. AddTo('Port',IntToStr(Port));
  494. if Full then
  495. begin
  496. AddTo('Password',Password);
  497. if Params.Count>0 then
  498. AddTo('Params',Params.CommaText);
  499. end;
  500. end;
  501. function TSQLDBConnectionDef.ToString: string;
  502. begin
  503. Result:=GetDescription;
  504. end;
  505. { TSQLDBConnectionmanager }
  506. procedure TSQLDBConnectionmanager.SetConnectionOwner(AValue: TComponent);
  507. begin
  508. if FConnectionOwner=AValue then Exit;
  509. if Assigned(FConnectionOwner) then
  510. FConnectionOwner.RemoveFreeNotification(Self);
  511. FConnectionOwner:=AValue;
  512. if Assigned(FConnectionOwner) then
  513. FConnectionOwner.FreeNotification(Self);
  514. end;
  515. procedure TSQLDBConnectionmanager.SetDefinitions(AValue: TSQLDBConnectionDefList);
  516. begin
  517. if FDefinitions=AValue then
  518. Exit;
  519. FDefinitions.Assign(AValue);
  520. end;
  521. procedure TSQLDBConnectionmanager.SetOnLog(AValue: TPoolLogEvent);
  522. begin
  523. if FOnLog=AValue then Exit;
  524. FOnLog:=AValue;
  525. if Assigned(FMyPool) then
  526. FMyPool.OnLog:=aValue;
  527. end;
  528. procedure TSQLDBConnectionmanager.SetPool(AValue: TSQLDBConnectionPool);
  529. begin
  530. if FPool=AValue then Exit;
  531. FPool:=AValue;
  532. if (FPool=Nil) then
  533. FPool:=FMyPool;
  534. end;
  535. procedure TSQLDBConnectionmanager.DoLog(const Msg: String);
  536. begin
  537. if Assigned(OnLog) then
  538. OnLog(Self,Msg);
  539. end;
  540. procedure TSQLDBConnectionmanager.DoLog(const Fmt: String;
  541. const aArgs: array of const);
  542. begin
  543. DoLog(Format(Fmt,aArgs));
  544. end;
  545. procedure TSQLDBConnectionmanager.Notification(AComponent: TComponent;
  546. Operation: TOperation);
  547. begin
  548. if (Operation=opRemove) then
  549. if (aComponent=FConnectionOwner) then
  550. FConnectionOwner:=Nil
  551. else
  552. begin
  553. if (aComponent=FMyPool) then
  554. FMyPool:=Nil;
  555. if (aComponent=FPool) then
  556. FPool:=Nil;
  557. end;
  558. inherited Notification(AComponent, Operation);
  559. end;
  560. constructor TSQLDBConnectionmanager.Create(aOwner: TComponent);
  561. begin
  562. inherited Create(aOwner);
  563. FMyPool:=CreatePool;
  564. FMyPool.SetSubComponent(True);
  565. FDefinitions:=CreateDefinitionList;
  566. FPool:=FMyPool;
  567. end;
  568. destructor TSQLDBConnectionmanager.Destroy;
  569. begin
  570. FreeAndNil(FPool);
  571. FreeAndNil(FDefinitions);
  572. inherited Destroy;
  573. end;
  574. function TSQLDBConnectionmanager.CreateConnection(const aDef: TSQLDBConnectionDef; addToPool: Boolean): TSQLConnection;
  575. var
  576. C : TSQLConnectionClass;
  577. begin
  578. C:=aDef.ConnectionClass;
  579. if (C=Nil) and (aDef.ConnectionType<>'') then
  580. C:=TSQLConnector;
  581. With aDef do
  582. DoLog(SCreatingNewConnection, [GetDescription]);
  583. Result:=C.Create(Self.ConnectionOwner);
  584. try
  585. aDef.AssignTo(Result);
  586. Result.Transaction:=TSQLTransaction.Create(Result);
  587. except
  588. Result.Free;
  589. Raise;
  590. end;
  591. if AddToPool then
  592. Pool.AddConnection(Result);
  593. end;
  594. function TSQLDBConnectionmanager.CreatePool : TSQLDBConnectionPool;
  595. begin
  596. Result:=TSQLDBConnectionPool.Create(Self);
  597. end;
  598. function TSQLDBConnectionmanager.CreateDefinitionList: TSQLDBConnectionDefList;
  599. begin
  600. Result:=TSQLDBConnectionDefList.Create(Self,TSQLDBConnectionDef);
  601. end;
  602. function TSQLDBConnectionmanager.CreateConnection(const aName: string;
  603. addToPool: Boolean): TSQLConnection;
  604. begin
  605. Result:=CreateConnection(Definitions.Get(aName),addToPool);
  606. end;
  607. function TSQLDBConnectionmanager.NewConnectionAllowed(aDef: TSQLDBConnectionDef; out aReason: string): Boolean;
  608. Var
  609. N: Integer;
  610. begin
  611. Result:=True;
  612. if (MaxDBConnections>0) then
  613. begin
  614. N:=FPool.CountConnections(aDef);
  615. if (N>MaxDBConnections) then
  616. AReason:=Format(SErrMaxNumberOfDefConnections, [MaxDBConnections, aDef.GetDescription(False)]);
  617. end;
  618. if (MaxTotalConnections>0) then
  619. begin
  620. N:=FPool.CountAllConnections;
  621. if (N>MaxDBConnections) then
  622. aReason:=Format(SErrMaxTotalConnectionReached, [MaxDBConnections]);
  623. end;
  624. Result:=aReason='';
  625. end;
  626. function TSQLDBConnectionmanager.GetConnection(const aDef: TSQLDBConnectionDef ): TSQLConnection;
  627. Var
  628. aReason,aErr : String;
  629. begin
  630. Result:=FPool.FindConnection(aDef);
  631. if Result=Nil then
  632. begin
  633. if Not NewConnectionAllowed(aDef,aReason) then
  634. begin
  635. aErr:=Format(SErrCannotCreateNewConnection, [aDef.GetDescription, aReason]);
  636. DoLog(aErr);
  637. Raise ESQLDBPool.Create(aErr);
  638. end;
  639. Result:=CreateConnection(aDef,True);
  640. end;
  641. end;
  642. function TSQLDBConnectionmanager.GetConnection(const aName: string
  643. ): TSQLConnection;
  644. begin
  645. Result:=GetConnection(Definitions.Get(aName));
  646. end;
  647. function TSQLDBConnectionmanager.ReleaseConnection(aConnection: TSQLConnection
  648. ): Boolean;
  649. begin
  650. Result:=FPool.ReleaseConnection(aConnection);
  651. end;
  652. { TConnectionPoolData }
  653. constructor TConnectionPoolData.Create(aConnection: TSQLConnection; aLocked : Boolean = true);
  654. begin
  655. FConnection:=aConnection;
  656. LastUsed:=Now;
  657. Flocked:=aLocked;
  658. end;
  659. destructor TConnectionPoolData.Destroy;
  660. begin
  661. inherited Destroy;
  662. end;
  663. procedure TConnectionPoolData.Lock;
  664. begin
  665. FLocked:=True;
  666. FLastUsed:=Now;
  667. end;
  668. procedure TConnectionPoolData.Unlock;
  669. begin
  670. FLocked:=False;
  671. FLastUsed:=Now;
  672. end;
  673. procedure TConnectionPoolData.FreeConnection;
  674. Var
  675. TR : TSQLTransaction;
  676. begin
  677. try
  678. TR:=Connection.Transaction;
  679. Connection.Transaction:=Nil;
  680. TR.Free;
  681. Connection.Connected:=False;
  682. finally
  683. FreeAndNil(FConnection);
  684. end;
  685. end;
  686. { TTypedConnectionPool }
  687. function TTypedConnectionPool.FindConnection(const aDatabaseName: string;
  688. const aHostName: string; const aUserName: string; const aPassword: string;
  689. aParams: TStrings): T;
  690. begin
  691. Result:=T(Inherited FindConnection(T,aDatabaseName,aHostName,aUserName,aPassword,aParams));
  692. end;
  693. { TConnectionList }
  694. constructor TConnectionList.Create;
  695. begin
  696. Inherited Create;
  697. FLock:=TCriticalSection.Create;
  698. FDisconnectTimeout:=DefaultDisconnectTimeout;
  699. end;
  700. destructor TConnectionList.Destroy;
  701. begin
  702. FreeAndNil(FLock);
  703. inherited Destroy;
  704. end;
  705. procedure TConnectionList.DisconnectAll;
  706. Var
  707. I : integer;
  708. CD : TConnectionPoolData;
  709. begin
  710. FLock.Enter;
  711. try
  712. For I:=Count-1 downto 0 do
  713. begin
  714. CD:=TConnectionPoolData(Items[i]);
  715. if (not CD.Locked) then
  716. begin
  717. CD.FreeConnection;
  718. Delete(I);
  719. end;
  720. end;
  721. finally
  722. FLock.Leave;
  723. end;
  724. end;
  725. procedure TConnectionList.Dolog(const Msg: String);
  726. begin
  727. If Assigned(OnLog) then
  728. OnLog(Self,Msg);
  729. end;
  730. procedure TConnectionList.DoLog(const Fmt: String; Args: array of const);
  731. begin
  732. DoLog(Format(Fmt,args));
  733. end;
  734. function TConnectionList.DoDisconnectOld(aTimeOut: Integer = -1): Integer;
  735. Var
  736. secs,I : integer;
  737. CD : TConnectionPoolData;
  738. N : TDateTime;
  739. begin
  740. Result:=0;
  741. N:=Now;
  742. if aTimeout<0 then
  743. aTimeout:=FDisconnectTimeout;
  744. for I:=Count-1 downto 0 do
  745. begin
  746. CD:=TConnectionPoolData(Items[i]);
  747. Secs:=SecondsBetween(N,CD.LastUsed);
  748. if (not CD.Locked) and (Secs>aTimeout) then
  749. begin
  750. With CD.Connection do
  751. DoLog(STimeoutReached, [Secs, aTimeout, GetDescription(False)]);
  752. try
  753. CD.FreeConnection;
  754. except
  755. on E : Exception do
  756. DoLog(SErrFreeingConnection, [E.ClassName, I, E.Message]);
  757. end;
  758. Delete(I);
  759. Inc(Result);
  760. end;
  761. end;
  762. end;
  763. function TConnectionList.CreatePoolData(aConnection: TSQLConnection;
  764. aLocked: Boolean): TConnectionPoolData;
  765. begin
  766. Result:=TConnectionPoolData.Create(aConnection,aLocked);
  767. end;
  768. function TConnectionList.DisconnectOld(aTimeOut: Integer): Integer;
  769. begin
  770. FLock.Enter;
  771. try
  772. Result:=DoDisconnectOld(aTimeout);
  773. finally
  774. FLock.Leave;
  775. end;
  776. end;
  777. function TConnectionList.AddConnection(aConnection: TSQLConnection; aLocked: Boolean
  778. ): TConnectionPoolData;
  779. begin
  780. FLock.Enter;
  781. try
  782. Result:=CreatePoolData(aConnection,aLocked);
  783. Add(Result);
  784. finally
  785. FLock.Leave;
  786. end;
  787. end;
  788. function TConnectionList.PopConnection: TSQLConnection;
  789. Var
  790. i : integer;
  791. CD : TConnectionPoolData;
  792. begin
  793. Result:=nil;
  794. FLock.Enter;
  795. try
  796. DoDisconnectOld;
  797. I:=0;
  798. While (Result=Nil) and (I<Count) do
  799. begin
  800. CD:=TConnectionPoolData(Items[i]);
  801. if not CD.Locked then
  802. begin
  803. CD.Lock;
  804. Result:=CD.Connection;
  805. end;
  806. Inc(I);
  807. end;
  808. finally
  809. Flock.Leave;
  810. end;
  811. end;
  812. function TConnectionList.UnlockConnection(aConnection: TSQLConnection): boolean;
  813. Var
  814. I : Integer;
  815. Data : TConnectionPoolData;
  816. begin
  817. Result:=False;
  818. FLock.Enter;
  819. try
  820. I:=Count-1;
  821. Data:=Nil;
  822. While (Data=Nil) and (I>=0) do
  823. begin
  824. Data:=TConnectionPoolData(Items[i]);
  825. if Data.Connection<>aConnection then
  826. Data:=Nil;
  827. Dec(i);
  828. end;
  829. if Assigned(Data) then
  830. begin
  831. Data.Unlock;
  832. Result:=True;
  833. end;
  834. finally
  835. FLock.Leave;
  836. end;
  837. IndexOf(aConnection)
  838. end;
  839. { TSQLDBConnectionPool }
  840. function TSQLDBConnectionPool.GetCount: longword;
  841. begin
  842. Result:=FPool.Count;
  843. end;
  844. function TSQLDBConnectionPool.CreateList: TConnectionList;
  845. begin
  846. Result:=TConnectionList.Create;
  847. end;
  848. procedure TSQLDBConnectionPool.Dolog(const Msg: String);
  849. begin
  850. If Assigned(OnLog) then
  851. OnLog(Self,Msg);
  852. end;
  853. procedure TSQLDBConnectionPool.DoLog(const Fmt: String; Args: array of const);
  854. begin
  855. DoLog(Format(Fmt,args));
  856. end;
  857. procedure TSQLDBConnectionPool.Lock;
  858. begin
  859. Flock.Enter;
  860. end;
  861. procedure TSQLDBConnectionPool.Unlock;
  862. begin
  863. Flock.Leave;
  864. end;
  865. function TSQLDBConnectionPool.CreateKey(aDef: TSQLDBConnectionDef): String;
  866. begin
  867. Result:=aDef.CreateKey;
  868. end;
  869. function TSQLDBConnectionPool.CreateDef : TSQLDBConnectionDef;
  870. begin
  871. Result:=TSQLDBConnectionDef.Create(Nil);
  872. end;
  873. function TSQLDBConnectionPool.FindConnection(aClass : TSQLConnectionClass; const aDatabaseName, aHostName,
  874. aUserName, aPassword: string; aParams: TStrings): TSQLConnection;
  875. Var
  876. Def : TSQLDBConnectionDef;
  877. begin
  878. Result:=nil;
  879. Def:=CreateDef;
  880. try
  881. Def.ConnectionClass:=aClass;
  882. Def.DatabaseName:=aDatabaseName;
  883. Def.HostName:=aHostName;
  884. Def.UserName:=aUserName;
  885. Def.Password:=aPassword;
  886. if Assigned(aParams) then
  887. Def.Params:=aParams;
  888. Result:=FindConnection(Def);
  889. finally
  890. Def.Free;
  891. end;
  892. end;
  893. function TSQLDBConnectionPool.FindConnection(const aConnectionDef: TSQLDBConnectionDef): TSQLConnection;
  894. Var
  895. N : String;
  896. begin
  897. Result:=nil;
  898. with aConnectionDef do
  899. begin
  900. N:=ConnectionType;
  901. if (N='') and Assigned(ConnectionClass) then
  902. N:=ConnectionClass.ClassName;
  903. DoLog(SFindingConnection,[GetDescription]);
  904. Result:=DoFindConnection(aConnectionDef);
  905. If (Result=Nil) then
  906. DoLog(SNoSuchConnection,[GetDescription])
  907. else
  908. DoLog(SFoundConnection,[GetDescription, PtrInt(Result)])
  909. end;
  910. end;
  911. function TSQLDBConnectionPool.DoFindConnection(const aConnectionDef: TSQLDBConnectionDef): TSQLConnection;
  912. Var
  913. Key : String;
  914. L : TConnectionList;
  915. begin
  916. Result:=Nil;
  917. Key:=CreateKey(aConnectionDef);
  918. Lock;
  919. try
  920. L:=TConnectionList(FPool.Items[Key]);
  921. if L=Nil then
  922. Exit;
  923. Result:=L.PopConnection;
  924. finally
  925. Unlock;
  926. end;
  927. end;
  928. (*
  929. result:=TSQLConnection(FPool[key]);
  930. if result=nil then
  931. begin
  932. result:=CreateConn(AOwner);
  933. result.HostName:=GetFirstNonNull(sHostName,FHostName);
  934. // Force local connection
  935. if result.HostName=MyServerName then
  936. Result.HostName:='';
  937. result.DatabaseName:=GetFirstNonNull(sDatabaseName,FDatabaseName);
  938. result.UserName:=GetFirstNonNull(sUserName,FUserName);
  939. result.Password:=GetFirstNonNull(sPassword,FPassword);
  940. result.Params:=GetFirstNonNull(ssParams,FParams);
  941. result.CharSet:='UTF8';
  942. if not CreateDisconnected then
  943. Result.Open;
  944. FPool.Add(key,result);
  945. end;
  946. end;
  947. *)
  948. procedure TSQLDBConnectionPool.DoDisconnect(Item: TObject; const Key: string;
  949. var Continue: Boolean);
  950. Var
  951. L : TConnectionList absolute item;
  952. begin
  953. Continue:=True;
  954. try
  955. L.DisconnectOld();
  956. except
  957. on E : Exception do
  958. DoLog(SErrorDisconnecting,[E.ClassName,E.Message]);
  959. end;
  960. end;
  961. procedure TSQLDBConnectionPool.DisconnectAll;
  962. begin
  963. Lock;
  964. try
  965. FPool.Iterate(@DoDisconnect);
  966. finally
  967. UnLock;
  968. end;
  969. end;
  970. destructor TSQLDBConnectionPool.Destroy;
  971. begin
  972. FLock.Free;
  973. FPool.Destroy;
  974. inherited Destroy;
  975. end;
  976. function TSQLDBConnectionPool.CountConnections(aClass: TSQLConnectionClass;
  977. const aDatabaseName, aHostName, aUserName, aPassword: string;
  978. aParams: TStrings): Integer;
  979. Var
  980. Def : TSQLDBConnectionDef;
  981. begin
  982. Result:=0;
  983. Def:=CreateDef;
  984. try
  985. Def.ConnectionClass:=aClass;
  986. Def.DatabaseName:=aDatabaseName;
  987. Def.HostName:=aHostName;
  988. Def.UserName:=aUserName;
  989. Def.Password:=aPassword;
  990. if Assigned(aParams) then
  991. Def.Params:=aParams;
  992. Result:=CountConnections(Def);
  993. finally
  994. Def.Free;
  995. end;
  996. end;
  997. function TSQLDBConnectionPool.CountConnections(aInstance: TSQLConnection): Integer;
  998. begin
  999. With aInstance do
  1000. Result:=CountConnections(TSQLConnectionClass(ClassType),DatabaseName,HostName,UserName,Password,Params);
  1001. end;
  1002. function TSQLDBConnectionPool.CountConnections(aDef: TSQLDBConnectionDef): Integer;
  1003. Var
  1004. Key : String;
  1005. L : TConnectionList;
  1006. begin
  1007. Key:=CreateKey(aDef);
  1008. Lock;
  1009. try
  1010. L:=TConnectionList(FPool.Items[Key]);
  1011. if L<>Nil then
  1012. Result:=L.Count;
  1013. finally
  1014. UnLock;
  1015. end;
  1016. end;
  1017. Type
  1018. { TConnectionCounter }
  1019. TConnectionCounter = Class(TObject)
  1020. private
  1021. FCount : Integer;
  1022. Public
  1023. Procedure DoCount(Item: TObject; const Key: string; var Continue: Boolean);
  1024. Property Count : Integer Read FCount;
  1025. end;
  1026. { TConnectionCounter }
  1027. procedure TConnectionCounter.DoCount(Item: TObject; const Key: string; var Continue: Boolean);
  1028. begin
  1029. FCount:=FCount+(Item as TConnectionList).Count;
  1030. Continue:=True;
  1031. end;
  1032. function TSQLDBConnectionPool.CountAllConnections: Integer;
  1033. var
  1034. Counter : TConnectionCounter;
  1035. begin
  1036. Counter:=Nil;
  1037. Lock;
  1038. try
  1039. Counter:=TConnectionCounter.Create;
  1040. FPool.Iterate(@Counter.DoCount);
  1041. Result:=Counter.Count;
  1042. finally
  1043. Unlock;
  1044. Counter.Free;
  1045. end;
  1046. end;
  1047. procedure TSQLDBConnectionPool.AddConnection(aConnection: TSQLConnection; aLocked : Boolean = True);
  1048. Var
  1049. Key : String;
  1050. L : TConnectionList;
  1051. aDef: TSQLDBConnectionDef;
  1052. begin
  1053. aDef:=Nil;
  1054. Lock;
  1055. try
  1056. aDef:=CreateDef;
  1057. aDef.Assign(aConnection);
  1058. Key:=CreateKey(aDef);
  1059. L:=TConnectionList(FPool.Items[Key]);
  1060. if L=Nil then
  1061. begin
  1062. L:=CreateList;
  1063. L.FonLog:=Self.OnLog;
  1064. FPool.Add(Key,L);
  1065. end;
  1066. L.AddConnection(aConnection,aLocked);
  1067. finally
  1068. Unlock;
  1069. aDef.Free;
  1070. end;
  1071. end;
  1072. function TSQLDBConnectionPool.ReleaseConnection(aConnection: TSQLConnection): Boolean;
  1073. Var
  1074. Key : String;
  1075. L : TConnectionList;
  1076. aDef: TSQLDBConnectionDef;
  1077. begin
  1078. Result:=False;
  1079. aDef:=Nil;
  1080. Lock;
  1081. try
  1082. aDef:=CreateDef;
  1083. aDef.Assign(aConnection);
  1084. Key:=CreateKey(aDef);
  1085. L:=TConnectionList(FPool.Items[Key]);
  1086. if Assigned(L) then
  1087. begin
  1088. With aConnection do
  1089. DoLog(SReleasingConnections, [GetDescription(False), L.Count]);
  1090. Result:=L.UnlockConnection(aConnection);
  1091. end;
  1092. finally
  1093. Unlock;
  1094. aDef.Free;
  1095. end;
  1096. end;
  1097. constructor TSQLDBConnectionPool.Create(aOwner: TComponent);
  1098. begin
  1099. FPool:=TFPObjectHashTable.Create(True);
  1100. FLock:=TCriticalSection.Create;
  1101. end;
  1102. end.