pqconnection.pp 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712
  1. unit PQConnection;
  2. {
  3. This file is part of the Free Pascal run time library.
  4. Copyright (c) 1999-2022 by Michael van Canney and other members of the
  5. Free Pascal development team
  6. Postgres database connection component
  7. See the file COPYING.FPC, included in this distribution,
  8. for details about the copyright.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  12. **********************************************************************}
  13. {$mode objfpc}{$H+}
  14. {$Define LinkDynamically}
  15. interface
  16. uses
  17. Classes, SysUtils, sqldb, db, dbconst,bufdataset,
  18. {$IfDef LinkDynamically}
  19. postgres3dyn;
  20. {$Else}
  21. postgres3;
  22. {$EndIf}
  23. type
  24. TPQCursor = Class;
  25. { TPQTrans }
  26. TPQTrans = Class(TSQLHandle)
  27. protected
  28. PGConn : PPGConn;
  29. FList : TThreadList;
  30. Procedure RegisterCursor(Cursor : TPQCursor);
  31. Procedure UnRegisterCursor(Cursor : TPQCursor);
  32. Public
  33. Constructor Create;
  34. Destructor Destroy; override;
  35. end;
  36. // TField and TFieldDef only support a limited amount of fields.
  37. // TFieldBinding and TExtendedFieldType can be used to map PQ types
  38. // on standard fields and retain mapping info.
  39. TExtendedFieldType = (eftNone,eftEnum,eftCitext);
  40. TFieldBinding = record
  41. FieldDef : TSQLDBFieldDef; // FieldDef this is associated with
  42. Index : Integer; // Tuple index
  43. TypeOID : oid; // Filled with type OID if it is not standard.
  44. TypeName : String; // Filled with type name by GetExtendedFieldInfo
  45. ExtendedFieldType: TExtendedFieldType; //
  46. end;
  47. PFieldBinding = ^TFieldBinding;
  48. TFieldBindings = Array of TFieldBinding;
  49. { TPQCursor }
  50. TPQCursor = Class(TSQLCursor)
  51. protected
  52. Statement : string;
  53. StmtName : string;
  54. tr : TPQTrans;
  55. res : PPGresult;
  56. CurTuple : integer;
  57. FieldBinding : TFieldBindings;
  58. Function GetFieldBinding(F : TFieldDef): PFieldBinding;
  59. Public
  60. Destructor Destroy; override;
  61. end;
  62. { EPQDatabaseError }
  63. EPQDatabaseError = class(EDatabaseError)
  64. public
  65. SEVERITY:string;
  66. SQLSTATE: string;
  67. MESSAGE_PRIMARY:string;
  68. MESSAGE_DETAIL:string;
  69. MESSAGE_HINT:string;
  70. STATEMENT_POSITION:string;
  71. SCHEMA_NAME: string;
  72. TABLE_NAME: string;
  73. COLUMN_NAME: string;
  74. DATATYPE_NAME: string;
  75. CONSTRAINT_NAME: string;
  76. end;
  77. { TPQTranConnection }
  78. TPQTranConnection = class
  79. protected
  80. FPGConn : PPGConn;
  81. FTranActive : boolean;
  82. end;
  83. { TPQConnection }
  84. TPQConnection = class (TSQLConnection)
  85. private
  86. FConnectionPool : TThreadList;
  87. FCursorCount : dword;
  88. FConnectString : string;
  89. FIntegerDateTimes : boolean;
  90. FVerboseErrors : Boolean;
  91. protected
  92. // Protected so they can be used by descendents.
  93. procedure CheckConnectionStatus(var conn: PPGconn);
  94. procedure CheckResultError(var res: PPGresult; conn:PPGconn; ErrMsg: string);
  95. function TranslateFldType(res : PPGresult; Tuple : integer; out Size : integer; Out ATypeOID : oid) : TFieldType;
  96. procedure ExecuteDirectPG(const Query : String);
  97. Procedure GetExtendedFieldInfo(cursor: TPQCursor; Bindings : TFieldBindings);
  98. procedure ApplyFieldUpdate(C : TSQLCursor; P: TSQLDBParam; F: TField; UseOldValue: Boolean); override;
  99. Function ErrorOnUnknownType : Boolean;
  100. // Add connection to pool.
  101. procedure AddConnection(T: TPQTranConnection);
  102. // Release connection in pool.
  103. procedure ReleaseConnection(Conn: PPGConn; DoClear : Boolean);
  104. function PortParamName: string; override;
  105. procedure DoInternalConnect; override;
  106. procedure DoInternalDisconnect; override;
  107. function GetHandle : pointer; override;
  108. Function AllocateCursorHandle : TSQLCursor; override;
  109. Procedure DeAllocateCursorHandle(var cursor : TSQLCursor); override;
  110. Function AllocateTransactionHandle : TSQLHandle; override;
  111. procedure PrepareStatement(cursor: TSQLCursor;ATransaction : TSQLTransaction;buf : string; AParams : TParams); override;
  112. procedure Execute(cursor: TSQLCursor;atransaction:tSQLtransaction; AParams : TParams); override;
  113. procedure AddFieldDefs(cursor: TSQLCursor; FieldDefs : TfieldDefs); override;
  114. function Fetch(cursor : TSQLCursor) : boolean; override;
  115. procedure UnPrepareStatement(cursor : TSQLCursor); override;
  116. function LoadField(cursor : TSQLCursor;FieldDef : TfieldDef;buffer : pointer; out CreateBlob : boolean) : boolean; override;
  117. function GetTransactionHandle(trans : TSQLHandle): pointer; override;
  118. function RollBack(trans : TSQLHandle) : boolean; override;
  119. function Commit(trans : TSQLHandle) : boolean; override;
  120. procedure CommitRetaining(trans : TSQLHandle); override;
  121. function StartImplicitTransaction(trans : TSQLHandle; AParams : string) : boolean; override;
  122. function StartDBTransaction(trans : TSQLHandle; AParams : string) : boolean; override;
  123. procedure RollBackRetaining(trans : TSQLHandle); override;
  124. procedure UpdateIndexDefs(IndexDefs : TIndexDefs;TableName : string); override;
  125. procedure LoadBlobIntoBuffer(FieldDef: TFieldDef;ABlobBuf: PBufBlobField; cursor: TSQLCursor;ATransaction : TSQLTransaction); override;
  126. function RowsAffected(cursor: TSQLCursor): TRowsCount; override;
  127. function GetSchemaInfoSQL(SchemaType : TSchemaType; SchemaObjectName, SchemaPattern : string) : string; override;
  128. function GetNextValueSQL(const SequenceName: string; IncrementBy: Integer): string; override;
  129. public
  130. constructor Create(AOwner : TComponent); override;
  131. destructor Destroy; override;
  132. function GetConnectionInfo(InfoType:TConnInfoType): string; override;
  133. procedure CreateDB; override;
  134. procedure DropDB; override;
  135. published
  136. property DatabaseName;
  137. property KeepConnection;
  138. property LoginPrompt;
  139. property Params;
  140. property OnLogin;
  141. Property VerboseErrors : Boolean Read FVerboseErrors Write FVerboseErrors default true;
  142. end;
  143. { TPQConnectionDef }
  144. TPQConnectionDef = Class(TConnectionDef)
  145. Class Function TypeName : String; override;
  146. Class Function ConnectionClass : TSQLConnectionClass; override;
  147. Class Function Description : String; override;
  148. Class Function DefaultLibraryName : String; override;
  149. Class Function LoadFunction : TLibraryLoadFunction; override;
  150. Class Function UnLoadFunction : TLibraryUnLoadFunction; override;
  151. Class Function LoadedLibraryName: string; override;
  152. end;
  153. implementation
  154. uses math, strutils, FmtBCD;
  155. ResourceString
  156. SErrRollbackFailed = 'Rollback transaction failed';
  157. SErrCommitFailed = 'Commit transaction failed';
  158. SErrConnectionFailed = 'Connection to database failed';
  159. SErrTransactionFailed = 'Start of transacion failed';
  160. SErrExecuteFailed = 'Execution of query failed';
  161. SErrPrepareFailed = 'Preparation of query failed.';
  162. SErrUnPrepareFailed = 'Unpreparation of query failed.';
  163. const Oid_Bool = 16;
  164. Oid_Bytea = 17;
  165. Oid_char = 18;
  166. Oid_Text = 25;
  167. Oid_Oid = 26;
  168. Oid_Name = 19;
  169. Oid_Int8 = 20;
  170. Oid_int2 = 21;
  171. Oid_Int4 = 23;
  172. Oid_JSON = 114;
  173. Oid_Float4 = 700;
  174. Oid_Money = 790;
  175. Oid_Float8 = 701;
  176. Oid_Unknown = 705;
  177. Oid_MacAddr = 829;
  178. Oid_Inet = 869;
  179. Oid_bpchar = 1042;
  180. Oid_varchar = 1043;
  181. oid_date = 1082;
  182. oid_time = 1083;
  183. Oid_timeTZ = 1266;
  184. Oid_timestamp = 1114;
  185. Oid_timestampTZ = 1184;
  186. Oid_interval = 1186;
  187. oid_numeric = 1700;
  188. Oid_uuid = 2950;
  189. { TPQTrans }
  190. constructor TPQTrans.Create;
  191. begin
  192. FList:=TThreadList.Create;
  193. FList.Duplicates:=dupIgnore;
  194. end;
  195. destructor TPQTrans.Destroy;
  196. Var
  197. L : TList;
  198. I : integer;
  199. begin
  200. L:=FList.LockList;
  201. try
  202. For I:=0 to L.Count-1 do
  203. TPQCursor(L[i]).tr:=Nil;
  204. finally
  205. FList.UnlockList;
  206. end;
  207. FreeAndNil(FList);
  208. inherited Destroy;
  209. end;
  210. procedure TPQTrans.RegisterCursor(Cursor: TPQCursor);
  211. begin
  212. FList.Add(Cursor);
  213. Cursor.tr:=Self;
  214. end;
  215. procedure TPQTrans.UnRegisterCursor(Cursor: TPQCursor);
  216. begin
  217. Cursor.tr:=Nil;
  218. FList.Remove(Cursor);
  219. end;
  220. { TPQCursor }
  221. destructor TPQCursor.Destroy;
  222. begin
  223. if Assigned(tr) then
  224. tr.UnRegisterCursor(Self);
  225. inherited Destroy;
  226. end;
  227. function TPQCursor.GetFieldBinding(F: TFieldDef): PFieldBinding;
  228. Var
  229. I : Integer;
  230. begin
  231. Result:=Nil;
  232. if (F=Nil) then exit;
  233. // This is an optimization: it is so for 99% of cases (FieldNo-1=array index)
  234. if F is TSQLDBFieldDef then
  235. Result:=PFieldBinding(TSQLDBFieldDef(F).SQLDBData)
  236. else If (FieldBinding[F.FieldNo-1].FieldDef=F) then
  237. Result:=@FieldBinding[F.FieldNo-1]
  238. else
  239. begin
  240. I:=Length(FieldBinding)-1;
  241. While (I>=0) and (FieldBinding[i].FieldDef<>F) do
  242. Dec(I);
  243. if I>=0 then
  244. Result:=@FieldBinding[i];
  245. end;
  246. end;
  247. { TPQConnection }
  248. constructor TPQConnection.Create(AOwner : TComponent);
  249. begin
  250. inherited;
  251. FConnOptions := FConnOptions + [sqSupportParams, sqSupportEmptyDatabaseName, sqEscapeRepeat, sqEscapeSlash, sqImplicitTransaction,sqSupportReturning,sqSequences];
  252. FieldNameQuoteChars:=DoubleQuotes;
  253. VerboseErrors:=True;
  254. FConnectionPool:=TThreadlist.Create;
  255. end;
  256. destructor TPQConnection.Destroy;
  257. begin
  258. // We must disconnect here. If it is done in inherited, then connection pool is gone.
  259. Connected:=False;
  260. FreeAndNil(FConnectionPool);
  261. inherited destroy;
  262. end;
  263. procedure TPQConnection.CreateDB;
  264. begin
  265. ExecuteDirectPG('CREATE DATABASE ' +DatabaseName);
  266. end;
  267. procedure TPQConnection.DropDB;
  268. begin
  269. ExecuteDirectPG('DROP DATABASE ' +DatabaseName);
  270. end;
  271. procedure TPQConnection.ExecuteDirectPG(const Query: String);
  272. var ASQLDatabaseHandle : PPGConn;
  273. res : PPGresult;
  274. begin
  275. CheckDisConnected;
  276. {$IfDef LinkDynamically}
  277. InitialisePostgres3;
  278. {$EndIf}
  279. FConnectString := '';
  280. if (UserName <> '') then FConnectString := FConnectString + ' user=''' + UserName + '''';
  281. if (Password <> '') then FConnectString := FConnectString + ' password=''' + Password + '''';
  282. if (HostName <> '') then FConnectString := FConnectString + ' host=''' + HostName + '''';
  283. FConnectString := FConnectString + ' dbname=''template1''';
  284. if (Params.Text <> '') then FConnectString := FConnectString + ' '+Params.Text;
  285. ASQLDatabaseHandle := PQconnectdb(pchar(FConnectString));
  286. CheckConnectionStatus(ASQLDatabaseHandle);
  287. res := PQexec(ASQLDatabaseHandle,pchar(query));
  288. CheckResultError(res,ASQLDatabaseHandle,SDBCreateDropFailed);
  289. PQclear(res);
  290. PQFinish(ASQLDatabaseHandle);
  291. {$IfDef LinkDynamically}
  292. ReleasePostgres3;
  293. {$EndIf}
  294. end;
  295. procedure TPQConnection.GetExtendedFieldInfo(cursor: TPQCursor;
  296. Bindings: TFieldBindings);
  297. Var
  298. tt,tc,Tn,S : String;
  299. I,J : Integer;
  300. Res : PPGResult;
  301. toid : oid;
  302. begin
  303. For I:=0 to Length(Bindings)-1 do
  304. if (Bindings[i].TypeOID>0) then
  305. begin
  306. if (S<>'') then
  307. S:=S+', ';
  308. S:=S+IntToStr(Bindings[i].TypeOID);
  309. end;
  310. if (S='') then
  311. exit;
  312. S:='select oid,typname,typtype,typcategory from pg_type where oid in ('+S+') order by oid';
  313. Res:=PQExec(Cursor.tr.PGConn,PChar(S));
  314. if (PQresultStatus(res)<>PGRES_TUPLES_OK) then
  315. CheckResultError(Res,Cursor.tr.PGConn,'Error getting type info');
  316. try
  317. For I:=0 to PQntuples(Res)-1 do
  318. begin
  319. toid:=Strtoint(pqgetvalue(Res,i,0));
  320. tn:=pqgetvalue(Res,i,1);
  321. tt:=pqgetvalue(Res,i,2);
  322. tc:=pqgetvalue(Res,i,3);
  323. J:=length(Bindings)-1;
  324. while (J>= 0) do
  325. begin
  326. if (Bindings[j].TypeOID=toid) then
  327. Case tt of
  328. 'e':
  329. Bindings[j].ExtendedFieldType:=eftEnum;
  330. 'citext':
  331. Bindings[j].ExtendedFieldType:=eftCitext;
  332. end;
  333. Dec(J);
  334. end;
  335. end;
  336. finally
  337. PQClear(Res);
  338. end;
  339. end;
  340. procedure TPQConnection.ApplyFieldUpdate(C : TSQLCursor; P: TSQLDBParam; F: TField;
  341. UseOldValue: Boolean);
  342. begin
  343. inherited ApplyFieldUpdate(C,P, F, UseOldValue);
  344. if (C is TPQCursor) then
  345. P.SQLDBData:=TPQCursor(C).GetFieldBinding(F.FieldDef);
  346. end;
  347. function TPQConnection.ErrorOnUnknownType: Boolean;
  348. begin
  349. Result:=False;
  350. end;
  351. procedure TPQConnection.AddConnection(T: TPQTranConnection);
  352. begin
  353. FConnectionPool.Add(T);
  354. end;
  355. procedure TPQConnection.ReleaseConnection(Conn: PPGConn; DoClear: Boolean);
  356. Var
  357. I : Integer;
  358. L : TList;
  359. T : TPQTranConnection;
  360. begin
  361. L:=FConnectionPool.LockList;
  362. // make connection available in pool
  363. try
  364. for i:=0 to L.Count-1 do
  365. begin
  366. T:=TPQTranConnection(L[i]);
  367. if (T.FPGConn=Conn) then
  368. begin
  369. T.FTranActive:=false;
  370. if DoClear then
  371. T.FPGConn:=Nil;
  372. break;
  373. end;
  374. end
  375. finally
  376. FConnectionPool.UnlockList;
  377. end;
  378. end;
  379. function TPQConnection.GetTransactionHandle(trans : TSQLHandle): pointer;
  380. begin
  381. Result := trans;
  382. end;
  383. function TPQConnection.RollBack(trans : TSQLHandle) : boolean;
  384. var
  385. res : PPGresult;
  386. tr : TPQTrans;
  387. i : Integer;
  388. L : TList;
  389. begin
  390. result := false;
  391. tr := trans as TPQTrans;
  392. // unprepare statements associated with given transaction
  393. L:=tr.FList.LockList;
  394. try
  395. For I:=0 to L.Count-1 do
  396. begin
  397. UnprepareStatement(TPQCursor(L[i]));
  398. TPQCursor(L[i]).tr:=Nil;
  399. end;
  400. L.Clear;
  401. finally
  402. tr.FList.UnlockList;
  403. end;
  404. res := PQexec(tr.PGConn, 'ROLLBACK');
  405. CheckResultError(res,tr.PGConn,SErrRollbackFailed);
  406. PQclear(res);
  407. ReleaseConnection(tr.PGCOnn,false);
  408. result := true;
  409. end;
  410. function TPQConnection.Commit(trans : TSQLHandle) : boolean;
  411. var
  412. res : PPGresult;
  413. tr : TPQTrans;
  414. begin
  415. result := false;
  416. tr := trans as TPQTrans;
  417. res := PQexec(tr.PGConn, 'COMMIT');
  418. CheckResultError(res,tr.PGConn,SErrCommitFailed);
  419. PQclear(res);
  420. //make connection available in pool
  421. ReleaseConnection(tr.PGConn,false);
  422. result := true;
  423. end;
  424. procedure TPQConnection.RollBackRetaining(trans : TSQLHandle);
  425. var
  426. res : PPGresult;
  427. tr : TPQTrans;
  428. begin
  429. tr := trans as TPQTrans;
  430. res := PQexec(tr.PGConn, 'ROLLBACK');
  431. CheckResultError(res,tr.PGConn,SErrRollbackFailed);
  432. PQclear(res);
  433. res := PQexec(tr.PGConn, 'BEGIN');
  434. CheckResultError(res,tr.PGConn,sErrTransactionFailed);
  435. PQclear(res);
  436. end;
  437. procedure TPQConnection.CommitRetaining(trans : TSQLHandle);
  438. var
  439. res : PPGresult;
  440. tr : TPQTrans;
  441. begin
  442. tr := trans as TPQTrans;
  443. res := PQexec(tr.PGConn, 'COMMIT');
  444. CheckResultError(res,tr.PGConn,SErrCommitFailed);
  445. PQclear(res);
  446. res := PQexec(tr.PGConn, 'BEGIN');
  447. CheckResultError(res,tr.PGConn,sErrTransactionFailed);
  448. PQclear(res);
  449. end;
  450. function TPQConnection.StartImplicitTransaction(trans : TSQLHandle; AParams : string) : boolean;
  451. var
  452. i : Integer;
  453. T : TPQTranConnection;
  454. L : TList;
  455. begin
  456. //find an unused connection in the pool
  457. i:=0;
  458. T:=Nil;
  459. L:=FConnectionPool.LockList;
  460. try
  461. while (i<L.Count) do
  462. begin
  463. T:=TPQTranConnection(L[i]);
  464. if (T.FPGConn=nil) or not T.FTranActive then
  465. break
  466. else
  467. T:=Nil;
  468. i:=i+1;
  469. end;
  470. // set to active now, so when we exit critical section,
  471. // it will be marked active and will not be found.
  472. if Assigned(T) then
  473. T.FTranActive:=true;
  474. finally
  475. FConnectionPool.UnLockList;
  476. end;
  477. if (T=Nil) then
  478. begin
  479. T:=TPQTranConnection.Create;
  480. T.FTranActive:=True;
  481. AddConnection(T);
  482. end;
  483. if (T.FPGConn=nil) then
  484. begin
  485. T.FPGConn := PQconnectdb(pchar(FConnectString));
  486. CheckConnectionStatus(T.FPGConn);
  487. if CharSet <> '' then
  488. PQsetClientEncoding(T.FPGConn, pchar(CharSet));
  489. end;
  490. TPQTrans(trans).PGConn := T.FPGConn;
  491. Result := true;
  492. end;
  493. function TPQConnection.StartDBTransaction(trans: TSQLHandle; AParams: string
  494. ): boolean;
  495. Var
  496. res : PPGresult;
  497. tr : TPQTrans;
  498. begin
  499. Result:=StartImplicitTransaction(trans, AParams);
  500. if Result then
  501. begin
  502. tr := trans as TPQTrans;
  503. res := PQexec(tr.PGConn, 'BEGIN');
  504. CheckResultError(res,tr.PGConn,sErrTransactionFailed);
  505. PQclear(res);
  506. end;
  507. end;
  508. procedure TPQConnection.DoInternalConnect;
  509. var
  510. ASQLDatabaseHandle : PPGConn;
  511. T : TPQTranConnection;
  512. begin
  513. {$IfDef LinkDynamically}
  514. InitialisePostgres3;
  515. {$EndIf}
  516. inherited DoInternalConnect;
  517. FConnectString := '';
  518. if (UserName <> '') then FConnectString := FConnectString + ' user=''' + UserName + '''';
  519. if (Password <> '') then FConnectString := FConnectString + ' password=''' + Password + '''';
  520. if (HostName <> '') then FConnectString := FConnectString + ' host=''' + HostName + '''';
  521. if (DatabaseName <> '') then FConnectString := FConnectString + ' dbname=''' + DatabaseName + '''';
  522. if (Params.Text <> '') then FConnectString := FConnectString + ' '+Params.Text;
  523. ASQLDatabaseHandle := PQconnectdb(pchar(FConnectString));
  524. try
  525. CheckConnectionStatus(ASQLDatabaseHandle);
  526. except
  527. DoInternalDisconnect;
  528. raise;
  529. end;
  530. // This only works for pg>=8.0, so timestamps won't work with earlier versions of pg which are compiled with integer_datetimes on
  531. if PQparameterStatus<>nil then
  532. FIntegerDateTimes := PQparameterStatus(ASQLDatabaseHandle,'integer_datetimes') = 'on';
  533. T:=TPQTranConnection.Create;
  534. T.FPGConn:=ASQLDatabaseHandle;
  535. T.FTranActive:=false;
  536. AddConnection(T);
  537. end;
  538. procedure TPQConnection.DoInternalDisconnect;
  539. var
  540. i:integer;
  541. L : TList;
  542. T : TPQTranConnection;
  543. begin
  544. Inherited;
  545. L:=FConnectionPool.LockList;
  546. try
  547. for i:=0 to L.Count-1 do
  548. begin
  549. T:=TPQTranConnection(L[i]);
  550. if assigned(T.FPGConn) then
  551. PQfinish(T.FPGConn);
  552. T.Free;
  553. end;
  554. L.Clear;
  555. finally
  556. FConnectionPool.UnLockList;
  557. end;
  558. {$IfDef LinkDynamically}
  559. ReleasePostgres3;
  560. {$EndIf}
  561. end;
  562. procedure TPQConnection.CheckConnectionStatus(var conn: PPGconn);
  563. var sErr: string;
  564. begin
  565. if (PQstatus(conn) = CONNECTION_BAD) then
  566. begin
  567. sErr := PQerrorMessage(conn);
  568. //make connection available in pool
  569. ReleaseConnection(Conn,True);
  570. PQfinish(conn);
  571. DatabaseError(sErrConnectionFailed + ' (PostgreSQL: ' + sErr + ')', Self);
  572. end;
  573. end;
  574. procedure TPQConnection.CheckResultError(var res: PPGresult; conn: PPGconn;
  575. ErrMsg: string);
  576. Procedure MaybeAdd(Var S : String; Prefix,Msg : String);
  577. begin
  578. if (Msg='') then
  579. exit;
  580. S:=S+LineEnding+Prefix+': '+Msg;
  581. end;
  582. var
  583. E: EPQDatabaseError;
  584. sErr: string;
  585. CompName: string;
  586. SEVERITY: string;
  587. SQLSTATE: string;
  588. MESSAGE_PRIMARY: string;
  589. MESSAGE_DETAIL: string;
  590. MESSAGE_HINT: string;
  591. STATEMENT_POSITION: string;
  592. SCHEMA_NAME: string;
  593. TABLE_NAME: string;
  594. COLUMN_NAME: string;
  595. DATATYPE_NAME: string;
  596. CONSTRAINT_NAME: string;
  597. P : Pchar;
  598. haveError : Boolean;
  599. begin
  600. HaveError:=False;
  601. if (Res=Nil) then
  602. begin
  603. HaveError:=True;
  604. P:=PQerrorMessage(conn);
  605. If Assigned(p) then
  606. ErrMsg:=StrPas(P);
  607. end
  608. else if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  609. begin
  610. HaveError:=True;
  611. SEVERITY:=PQresultErrorField(res,PG_DIAG_SEVERITY);
  612. SQLSTATE:=PQresultErrorField(res,PG_DIAG_SQLSTATE);
  613. MESSAGE_PRIMARY:=PQresultErrorField(res,PG_DIAG_MESSAGE_PRIMARY);
  614. MESSAGE_DETAIL:=PQresultErrorField(res,PG_DIAG_MESSAGE_DETAIL);
  615. MESSAGE_HINT:=PQresultErrorField(res,PG_DIAG_MESSAGE_HINT);
  616. STATEMENT_POSITION:=PQresultErrorField(res,PG_DIAG_STATEMENT_POSITION);
  617. SCHEMA_NAME:=PQresultErrorField(res,PG_DIAG_SCHEMA_NAME);
  618. TABLE_NAME:=PQresultErrorField(res,PG_DIAG_TABLE_NAME);
  619. COLUMN_NAME:=PQresultErrorField(res,PG_DIAG_COLUMN_NAME);
  620. DATATYPE_NAME:=PQresultErrorField(res,PG_DIAG_DATATYPE_NAME);
  621. CONSTRAINT_NAME:=PQresultErrorField(res,PG_DIAG_CONSTRAINT_NAME);
  622. sErr:=PQresultErrorMessage(res);
  623. if VerboseErrors then
  624. begin
  625. MaybeAdd(sErr,'Severity',SEVERITY);
  626. MaybeAdd(sErr,'SQL State',SQLSTATE);
  627. MaybeAdd(sErr,'Primary Error',MESSAGE_PRIMARY);
  628. MaybeAdd(sErr,'Error Detail',MESSAGE_DETAIL);
  629. MaybeAdd(sErr,'Hint',MESSAGE_HINT);
  630. MaybeAdd(sErr,'Character',STATEMENT_POSITION);
  631. MaybeAdd(sErr,'Schema',SCHEMA_NAME);
  632. MaybeAdd(sErr,'Table',TABLE_NAME);
  633. MaybeAdd(sErr,'Column',COLUMN_NAME);
  634. MaybeAdd(sErr,'Data Type',DATATYPE_NAME);
  635. MaybeAdd(sErr,'Constraint',CONSTRAINT_NAME);
  636. end;
  637. end;
  638. if HaveError then
  639. begin
  640. if (Self.Name='') then CompName := Self.ClassName else CompName := Self.Name;
  641. E:=EPQDatabaseError.CreateFmt('%s : %s (PostgreSQL: %s)', [CompName, ErrMsg, sErr]);
  642. E.SEVERITY:=SEVERITY;
  643. E.SQLSTATE:=SQLSTATE;
  644. E.MESSAGE_PRIMARY:=MESSAGE_PRIMARY;
  645. E.MESSAGE_DETAIL:=MESSAGE_DETAIL;
  646. E.MESSAGE_HINT:=MESSAGE_HINT;
  647. E.STATEMENT_POSITION:=STATEMENT_POSITION;
  648. E.SCHEMA_NAME:=SCHEMA_NAME;
  649. E.TABLE_NAME:=TABLE_NAME;
  650. E.COLUMN_NAME:=COLUMN_NAME;
  651. E.DATATYPE_NAME:=DATATYPE_NAME;
  652. E.CONSTRAINT_NAME:=CONSTRAINT_NAME;
  653. PQclear(res);
  654. res:=nil;
  655. if assigned(conn) then
  656. begin
  657. PQFinish(conn);
  658. ReleaseConnection(Conn,True);
  659. end;
  660. raise E;
  661. end;
  662. end;
  663. function TPQConnection.TranslateFldType(res: PPGresult; Tuple: integer; out
  664. Size: integer; out ATypeOID: oid): TFieldType;
  665. const
  666. VARHDRSZ=sizeof(longint);
  667. var
  668. li : longint;
  669. aoid : oid;
  670. begin
  671. Size := 0;
  672. ATypeOID:=0;
  673. AOID:=PQftype(res,Tuple);
  674. case AOID of
  675. Oid_varchar,Oid_bpchar,
  676. Oid_name : begin
  677. Result := ftString;
  678. size := PQfsize(Res, Tuple);
  679. if (size = -1) then
  680. begin
  681. li := PQfmod(res,Tuple);
  682. if li = -1 then
  683. size := dsMaxStringSize
  684. else
  685. size := (li-VARHDRSZ) and $FFFF;
  686. end;
  687. if size > MaxSmallint then size := MaxSmallint;
  688. end;
  689. // Oid_text : Result := ftString;
  690. Oid_text,Oid_JSON : Result := ftMemo;
  691. Oid_Bytea : Result := ftBlob;
  692. Oid_oid : Result := ftInteger;
  693. Oid_int8 : Result := ftLargeInt;
  694. Oid_int4 : Result := ftInteger;
  695. Oid_int2 : Result := ftSmallInt;
  696. Oid_Float4 : Result := ftFloat;
  697. Oid_Float8 : Result := ftFloat;
  698. Oid_TimeStamp,
  699. Oid_TimeStampTZ : Result := ftDateTime;
  700. Oid_Date : Result := ftDate;
  701. Oid_Interval,
  702. Oid_Time,
  703. Oid_TimeTZ : Result := ftTime;
  704. Oid_Bool : Result := ftBoolean;
  705. Oid_Numeric : begin
  706. Result := ftBCD;
  707. li := PQfmod(res,Tuple);
  708. if li = -1 then
  709. size := 4 // No information about the size available, use the maximum value
  710. else
  711. // The precision is the high 16 bits, the scale the
  712. // low 16 bits with an offset of sizeof(int32).
  713. begin
  714. size := (li-VARHDRSZ) and $FFFF;
  715. if (size > MaxBCDScale) or ((li shr 16)-size > MaxBCDPrecision-MaxBCDScale) then
  716. Result := ftFmtBCD;
  717. end;
  718. end;
  719. Oid_Money : Result := ftCurrency;
  720. Oid_char : begin
  721. Result := ftFixedChar;
  722. Size := 1;
  723. end;
  724. Oid_uuid : begin
  725. Result := ftGuid;
  726. Size := 38;
  727. end;
  728. Oid_MacAddr : begin
  729. Result := ftFixedChar;
  730. Size := 17;
  731. end;
  732. Oid_Inet : begin
  733. Result := ftString;
  734. Size := 39;
  735. end;
  736. Oid_Unknown : Result := ftUnknown;
  737. else
  738. Result:=ftUnknown;
  739. ATypeOID:=AOID;
  740. end;
  741. end;
  742. function TPQConnection.AllocateCursorHandle: TSQLCursor;
  743. begin
  744. result := TPQCursor.create;
  745. end;
  746. procedure TPQConnection.DeAllocateCursorHandle(var cursor: TSQLCursor);
  747. begin
  748. FreeAndNil(cursor);
  749. end;
  750. function TPQConnection.AllocateTransactionHandle: TSQLHandle;
  751. begin
  752. result := TPQTrans.create;
  753. end;
  754. procedure TPQConnection.PrepareStatement(cursor: TSQLCursor;ATransaction : TSQLTransaction;buf : string; AParams : TParams);
  755. const TypeStrings : array[TFieldType] of string =
  756. (
  757. 'Unknown', // ftUnknown
  758. 'text', // ftString
  759. 'smallint', // ftSmallint
  760. 'int', // ftInteger
  761. 'int', // ftWord
  762. 'bool', // ftBoolean
  763. 'float', // ftFloat
  764. 'money', // ftCurrency
  765. 'numeric', // ftBCD
  766. 'date', // ftDate
  767. 'time', // ftTime
  768. 'timestamp', // ftDateTime
  769. 'Unknown', // ftBytes
  770. 'bytea', // ftVarBytes
  771. 'Unknown', // ftAutoInc
  772. 'bytea', // ftBlob
  773. 'text', // ftMemo
  774. 'bytea', // ftGraphic
  775. 'text', // ftFmtMemo
  776. 'Unknown', // ftParadoxOle
  777. 'Unknown', // ftDBaseOle
  778. 'Unknown', // ftTypedBinary
  779. 'Unknown', // ftCursor
  780. 'char', // ftFixedChar
  781. 'text', // ftWideString
  782. 'bigint', // ftLargeint
  783. 'Unknown', // ftADT
  784. 'Unknown', // ftArray
  785. 'Unknown', // ftReference
  786. 'Unknown', // ftDataSet
  787. 'Unknown', // ftOraBlob
  788. 'Unknown', // ftOraClob
  789. 'Unknown', // ftVariant
  790. 'Unknown', // ftInterface
  791. 'Unknown', // ftIDispatch
  792. 'uuid', // ftGuid
  793. 'Unknown', // ftTimeStamp
  794. 'numeric', // ftFMTBcd
  795. 'Unknown', // ftFixedWideChar
  796. 'Unknown', // ftWideMemo
  797. 'Unknown', // ftOraTimeStamp
  798. 'Unknown', // ftOraInterval
  799. 'Unknown', // ftLongWord
  800. 'Unknown', // ftShortint
  801. 'Unknown', // ftByte
  802. 'Unknown', // ftExtended
  803. 'real' // ftSingle
  804. );
  805. var
  806. s,ts : string;
  807. i : integer;
  808. P : TParam;
  809. PQ : TSQLDBParam;
  810. begin
  811. with (cursor as TPQCursor) do
  812. begin
  813. FPrepared := False;
  814. FDirect := False;
  815. // Prior to v8 there is no support for cursors and parameters.
  816. // So that's not supported.
  817. if FStatementType in [stInsert,stUpdate,stDelete, stSelect] then
  818. begin
  819. StmtName := 'prepst'+inttostr(FCursorCount);
  820. InterlockedIncrement(FCursorCount);
  821. TPQTrans(aTransaction.Handle).RegisterCursor(Cursor as TPQCursor);
  822. // Only available for pq 8.0, so don't use it...
  823. // Res := pqprepare(tr,'prepst'+name+nr,pchar(buf),params.Count,pchar(''));
  824. s := 'prepare '+StmtName+' ';
  825. if Assigned(AParams) and (AParams.Count > 0) then
  826. begin
  827. s := s + '(';
  828. for i := 0 to AParams.Count-1 do
  829. begin
  830. P:=AParams[i];
  831. If (P is TSQLDBParam) then
  832. PQ:=TSQLDBParam(P)
  833. else
  834. PQ:=Nil;
  835. TS:=TypeStrings[P.DataType];
  836. if (TS<>'Unknown') then
  837. begin
  838. If Assigned(PQ)
  839. and Assigned(PQ.SQLDBData)
  840. and (PFieldBinding(PQ.SQLDBData)^.ExtendedFieldType=eftEnum) then
  841. ts:='unknown';
  842. s := s + ts + ','
  843. end
  844. else
  845. begin
  846. if P.DataType = ftUnknown then
  847. begin
  848. if P.IsNull then
  849. s:=s+' unknown ,'
  850. else
  851. DatabaseErrorFmt(SUnknownParamFieldType,[P.Name],self)
  852. end
  853. else
  854. DatabaseErrorFmt(SUnsupportedParameter,[Fieldtypenames[P.DataType]],self);
  855. end;
  856. end;
  857. s[length(s)] := ')';
  858. buf := AParams.ParseSQL(buf,false,sqEscapeSlash in ConnOptions, sqEscapeRepeat in ConnOptions,psPostgreSQL);
  859. end;
  860. s := s + ' as ' + buf;
  861. if LogEvent(detActualSQL) then
  862. Log(detActualSQL,S);
  863. res := PQexec(tr.PGConn,pchar(s));
  864. CheckResultError(res,nil,SErrPrepareFailed);
  865. // if statement is INSERT, UPDATE, DELETE with RETURNING clause, then
  866. // override the statement type derrived by parsing the query.
  867. if (FStatementType in [stInsert,stUpdate,stDelete]) and (pos('RETURNING', upcase(s)) > 0) then
  868. begin
  869. PQclear(res);
  870. res := PQdescribePrepared(tr.PGConn,pchar(StmtName));
  871. if (PQresultStatus(res) = PGRES_COMMAND_OK) and (PQnfields(res) > 0) then
  872. FStatementType := stSelect;
  873. end;
  874. FPrepared := True;
  875. end
  876. else
  877. begin
  878. if Assigned(AParams) then
  879. Statement := AParams.ParseSQL(buf,false,sqEscapeSlash in ConnOptions, sqEscapeRepeat in ConnOptions,psPostgreSQL)
  880. else
  881. Statement:=Buf;
  882. FDirect:=True;
  883. end;
  884. end;
  885. end;
  886. procedure TPQConnection.UnPrepareStatement(cursor : TSQLCursor);
  887. begin
  888. with (cursor as TPQCursor) do
  889. begin
  890. PQclear(res);
  891. res:=nil;
  892. if ForcedClose then
  893. begin
  894. FPrepared := False;
  895. exit;
  896. end;
  897. if FPrepared then
  898. begin
  899. if assigned(tr) and (PQtransactionStatus(tr.PGConn) <> PQTRANS_INERROR) then
  900. begin
  901. res := PQexec(tr.PGConn,pchar('deallocate '+StmtName));
  902. CheckResultError(res,nil,SErrUnPrepareFailed);
  903. PQclear(res);
  904. res:=nil;
  905. end;
  906. FPrepared := False;
  907. end;
  908. end;
  909. end;
  910. procedure TPQConnection.Execute(cursor: TSQLCursor;atransaction:tSQLtransaction;AParams : TParams);
  911. var ar : array of PAnsiChar;
  912. handled : boolean;
  913. l,i : integer;
  914. s : RawByteString;
  915. bd : TBlobData;
  916. lengths,formats : array of integer;
  917. ParamNames,
  918. ParamValues : array of string;
  919. cash: int64;
  920. function FormatTimeInterval(Time: TDateTime): string; // supports Time >= '24:00:00'
  921. var hour, minute, second, millisecond: word;
  922. begin
  923. DecodeTime(Time, hour, minute, second, millisecond);
  924. Result := Format('%.2d:%.2d:%.2d.%.3d',[Trunc(Time)*24+hour,minute,second,millisecond]);
  925. end;
  926. begin
  927. with cursor as TPQCursor do
  928. begin
  929. CurTuple:=-1;
  930. PQclear(res);
  931. if FStatementType in [stInsert,stUpdate,stDelete,stSelect] then
  932. begin
  933. if LogEvent(detParamValue) then
  934. LogParams(AParams);
  935. if Assigned(AParams) and (AParams.Count > 0) then
  936. begin
  937. l:=AParams.Count;
  938. setlength(ar,l);
  939. setlength(lengths,l);
  940. setlength(formats,l);
  941. for i := 0 to AParams.Count -1 do if not AParams[i].IsNull then
  942. begin
  943. handled:=False;
  944. case AParams[i].DataType of
  945. ftDateTime:
  946. s := FormatDateTime('yyyy"-"mm"-"dd hh":"nn":"ss.zzz', AParams[i].AsDateTime);
  947. ftDate:
  948. s := FormatDateTime('yyyy"-"mm"-"dd', AParams[i].AsDateTime);
  949. ftTime:
  950. s := FormatTimeInterval(AParams[i].AsDateTime);
  951. ftFloat:
  952. Str(AParams[i].AsFloat, s);
  953. ftBCD:
  954. Str(AParams[i].AsCurrency, s);
  955. ftCurrency:
  956. begin
  957. cash:=NtoBE(round(AParams[i].AsCurrency*100));
  958. setlength(s, sizeof(cash));
  959. Move(cash, s[1], sizeof(cash));
  960. end;
  961. ftFmtBCD:
  962. s := BCDToStr(AParams[i].AsFMTBCD, FSQLFormatSettings);
  963. ftBlob, ftGraphic, ftVarBytes:
  964. begin
  965. Handled:=true;
  966. bd:= AParams[i].AsBlob;
  967. l:=length(BD);
  968. if l>0 then
  969. begin
  970. GetMem(ar[i],l+1);
  971. ar[i][l]:=#0;
  972. Move(BD[0],ar[i]^, L);
  973. lengths[i]:=l;
  974. end;
  975. end
  976. else
  977. s := GetAsString(AParams[i]);
  978. end; {case}
  979. if not handled then
  980. begin
  981. l:=length(s);
  982. GetMem(ar[i],l+1);
  983. StrMove(PAnsiChar(ar[i]), PAnsiChar(s), L+1);
  984. lengths[i]:=L;
  985. end;
  986. if (AParams[i].DataType in [ftBlob,ftMemo,ftGraphic,ftCurrency,ftVarBytes]) then
  987. Formats[i]:=1
  988. else
  989. Formats[i]:=0;
  990. end
  991. else
  992. FreeAndNil(ar[i]);
  993. res := PQexecPrepared(tr.PGConn,pchar(StmtName),AParams.Count,@Ar[0],@Lengths[0],@Formats[0],1);
  994. for i := 0 to AParams.Count -1 do
  995. FreeMem(ar[i]);
  996. end
  997. else
  998. res := PQexecPrepared(tr.PGConn,pchar(StmtName),0,nil,nil,nil,1);
  999. end
  1000. else
  1001. begin
  1002. // RegisterCursor sets tr
  1003. TPQTrans(aTransaction.Handle).RegisterCursor(Cursor as TPQCursor);
  1004. if Assigned(AParams) and (AParams.Count > 0) then
  1005. begin
  1006. setlength(ParamNames,AParams.Count);
  1007. setlength(ParamValues,AParams.Count);
  1008. for i := 0 to AParams.Count -1 do
  1009. begin
  1010. ParamNames[AParams.Count-i-1] := '$'+inttostr(AParams[i].index+1);
  1011. ParamValues[AParams.Count-i-1] := GetAsSQLText(AParams[i]);
  1012. end;
  1013. s := stringsreplace(Statement,ParamNames,ParamValues,[rfReplaceAll]);
  1014. end
  1015. else
  1016. s := Statement;
  1017. res := PQexec(tr.PGConn,pchar(s));
  1018. if (PQresultStatus(res) in [PGRES_COMMAND_OK]) then
  1019. begin
  1020. PQclear(res);
  1021. res:=nil;
  1022. end;
  1023. end;
  1024. if assigned(res) and not (PQresultStatus(res) in [PGRES_COMMAND_OK,PGRES_TUPLES_OK]) then
  1025. begin
  1026. // Don't perform the rollback, only make it possible to do a rollback.
  1027. // The other databases also don't do this.
  1028. //atransaction.Rollback;
  1029. CheckResultError(res,nil,SErrExecuteFailed);
  1030. end;
  1031. FSelectable := assigned(res) and (PQresultStatus(res)=PGRES_TUPLES_OK);
  1032. end;
  1033. end;
  1034. procedure TPQConnection.AddFieldDefs(cursor: TSQLCursor; FieldDefs : TfieldDefs);
  1035. var
  1036. i : integer;
  1037. asize : integer;
  1038. aoid : oid;
  1039. fieldtype : tfieldtype;
  1040. nFields : integer;
  1041. b : Boolean;
  1042. Q : TPQCursor;
  1043. FD : TSQLDBFieldDef;
  1044. FB : PFieldBinding;
  1045. begin
  1046. B:=False;
  1047. Q:=cursor as TPQCursor;
  1048. with Q do
  1049. begin
  1050. nFields := PQnfields(Res);
  1051. setlength(FieldBinding,nFields);
  1052. for i := 0 to nFields-1 do
  1053. begin
  1054. fieldtype := TranslateFldType(Res, i, asize, aoid );
  1055. FD := AddFieldDef(FieldDefs, i+1, PQfname(Res, i), fieldtype, asize, -1, False, False, False) as TSQLDBFieldDef;
  1056. With FD do
  1057. begin
  1058. SQLDBData:=@FieldBinding[i];
  1059. FieldBinding[i].Index:=i;
  1060. FieldBinding[i].FieldDef:=FD;
  1061. FieldBinding[i].TypeOID:=aOID;
  1062. B:=B or (aOID>0);
  1063. end;
  1064. end;
  1065. CurTuple := -1;
  1066. end;
  1067. if B then
  1068. begin
  1069. // get all information in 1 go.
  1070. GetExtendedFieldInfo(Q,Q.FieldBinding);
  1071. For I:=0 to Length(Q.FieldBinding)-1 do
  1072. begin
  1073. FB:[email protected][i];
  1074. if (FB^.TypeOID>0) then
  1075. begin
  1076. FD:=FB^.FieldDef;
  1077. Case FB^.ExtendedFieldType of
  1078. eftEnum :
  1079. begin
  1080. FD.DataType:=ftString;
  1081. FD.Size:=64;
  1082. //FD.Attributes:=FD.Attributes+[faReadonly];
  1083. end;
  1084. eftCitext:
  1085. begin
  1086. FD.DataType:=ftMemo;
  1087. end
  1088. else
  1089. if ErrorOnUnknownType then
  1090. DatabaseError('Unhandled field type :'+FB^.TypeName,Self);
  1091. end;
  1092. end;
  1093. end;
  1094. end;
  1095. end;
  1096. function TPQConnection.GetHandle: pointer;
  1097. var
  1098. i:integer;
  1099. L : TList;
  1100. T : TPQTranConnection;
  1101. begin
  1102. result:=nil;
  1103. if not Connected then
  1104. exit;
  1105. //Get any handle that is (still) connected
  1106. L:=FConnectionPool.LockList;
  1107. try
  1108. I:=L.Count-1;
  1109. While (I>=0) and (Result=Nil) do
  1110. begin
  1111. T:=TPQTranConnection(L[i]);
  1112. if assigned(T.FPGConn) and (PQstatus(T.FPGConn)<>CONNECTION_BAD) then
  1113. Result:=T.FPGConn;
  1114. Dec(I);
  1115. end;
  1116. finally
  1117. FConnectionPool.UnLockList;
  1118. end;
  1119. if Result<>Nil then
  1120. exit;
  1121. //Nothing connected!! Reconnect
  1122. // T is element 0 after loop
  1123. if assigned(T.FPGConn) then
  1124. PQreset(T.FPGConn)
  1125. else
  1126. T.FPGConn := PQconnectdb(pchar(FConnectString));
  1127. CheckConnectionStatus(T.FPGConn);
  1128. if CharSet <> '' then
  1129. PQsetClientEncoding(T.FPGConn, pchar(CharSet));
  1130. result:=T.FPGConn;
  1131. end;
  1132. function TPQConnection.Fetch(cursor : TSQLCursor) : boolean;
  1133. begin
  1134. with cursor as TPQCursor do
  1135. begin
  1136. inc(CurTuple);
  1137. Result := (PQntuples(res)>CurTuple);
  1138. end;
  1139. end;
  1140. function TPQConnection.LoadField(cursor : TSQLCursor;FieldDef : TfieldDef;buffer : pointer; out CreateBlob : boolean) : boolean;
  1141. const NBASE=10000;
  1142. DAYS_PER_MONTH=30;
  1143. type TNumericRecord = record
  1144. Digits : SmallInt;
  1145. Weight : SmallInt;
  1146. Sign : SmallInt;
  1147. Scale : Smallint;
  1148. end;
  1149. TIntervalRec = packed record
  1150. time : int64;
  1151. day : longint;
  1152. month : longint;
  1153. end;
  1154. TMacAddrRec = packed record
  1155. a, b, c, d, e, f: byte;
  1156. end;
  1157. TInetRec = packed record
  1158. family : byte;
  1159. bits : byte;
  1160. is_cidr: byte;
  1161. nb : byte;
  1162. ipaddr : array[1..16] of byte;
  1163. end;
  1164. var
  1165. x,i : integer;
  1166. s : string;
  1167. li : Longint;
  1168. CurrBuff : pchar;
  1169. dbl : pdouble;
  1170. cur : currency;
  1171. NumericRecord : ^TNumericRecord;
  1172. guid : TGUID;
  1173. bcd : TBCD;
  1174. macaddr : ^TMacAddrRec;
  1175. inet : ^TInetRec;
  1176. begin
  1177. Createblob := False;
  1178. with cursor as TPQCursor do
  1179. begin
  1180. x := GetFieldBinding(FieldDef)^.Index;
  1181. // Joost, 5 jan 2006: I disabled the following, since it's useful for
  1182. // debugging, but it also slows things down. In principle things can only go
  1183. // wrong when FieldDefs is changed while the dataset is opened. A user just
  1184. // shoudn't do that. ;) (The same is done in IBConnection)
  1185. //if PQfname(Res, x) <> FieldDef.Name then
  1186. // DatabaseErrorFmt(SFieldNotFound,[FieldDef.Name],self);
  1187. if pqgetisnull(res,CurTuple,x)=1 then
  1188. result := false
  1189. else
  1190. begin
  1191. CurrBuff := pqgetvalue(res,CurTuple,x);
  1192. result := true;
  1193. case FieldDef.DataType of
  1194. ftInteger, ftSmallint, ftLargeInt :
  1195. case PQfsize(res, x) of // postgres returns big-endian numbers
  1196. sizeof(int64) : pint64(buffer)^ := BEtoN(pint64(CurrBuff)^); // INT8
  1197. sizeof(integer) : pinteger(buffer)^ := BEtoN(pinteger(CurrBuff)^); // INT4
  1198. sizeof(smallint) : psmallint(buffer)^ := BEtoN(psmallint(CurrBuff)^); // INT2
  1199. end; {case}
  1200. ftFloat :
  1201. case PQfsize(res, x) of // postgres returns big-endian numbers
  1202. sizeof(int64) : // FLOAT8
  1203. pint64(buffer)^ := BEtoN(pint64(CurrBuff)^);
  1204. sizeof(integer) : // FLOAT4
  1205. begin
  1206. li := BEtoN(pinteger(CurrBuff)^);
  1207. pdouble(buffer)^ := psingle(@li)^
  1208. end;
  1209. end; {case}
  1210. ftString, ftFixedChar :
  1211. begin
  1212. case PQftype(res, x) of
  1213. Oid_MacAddr:
  1214. begin
  1215. macaddr := Pointer(CurrBuff);
  1216. li := FormatBuf(Buffer^, FieldDef.Size, '%.2x:%.2x:%.2x:%.2x:%.2x:%.2x', 29,
  1217. [macaddr^.a,macaddr^.b,macaddr^.c,macaddr^.d,macaddr^.e,macaddr^.f]);
  1218. end;
  1219. Oid_Inet:
  1220. begin
  1221. inet := Pointer(CurrBuff);
  1222. if inet^.nb = 4 then
  1223. li := FormatBuf(Buffer^, FieldDef.Size, '%d.%d.%d.%d', 11,
  1224. [inet^.ipaddr[1],inet^.ipaddr[2],inet^.ipaddr[3],inet^.ipaddr[4]])
  1225. else if inet^.nb = 16 then
  1226. li := FormatBuf(Buffer^, FieldDef.Size, '%x%.2x:%x%.2x:%x%.2x:%x%.2x:%x%.2x:%x%.2x:%x%.2x:%x%.2x', 55,
  1227. [inet^.ipaddr[1],inet^.ipaddr[2],inet^.ipaddr[3],inet^.ipaddr[4],inet^.ipaddr[5],inet^.ipaddr[6],inet^.ipaddr[7],inet^.ipaddr[8],inet^.ipaddr[9],inet^.ipaddr[10],inet^.ipaddr[11],inet^.ipaddr[12],inet^.ipaddr[13],inet^.ipaddr[14],inet^.ipaddr[15],inet^.ipaddr[16]])
  1228. else
  1229. li := 0;
  1230. end
  1231. else
  1232. begin
  1233. li := pqgetlength(res,curtuple,x);
  1234. if li > FieldDef.Size then li := FieldDef.Size;
  1235. Move(CurrBuff^, Buffer^, li);
  1236. end;
  1237. end;
  1238. pchar(Buffer + li)^ := #0;
  1239. end;
  1240. ftBlob, ftMemo, ftVarBytes :
  1241. CreateBlob := True;
  1242. ftDate :
  1243. begin
  1244. dbl := pointer(buffer);
  1245. dbl^ := BEtoN(plongint(CurrBuff)^) + 36526;
  1246. end;
  1247. ftDateTime, ftTime :
  1248. begin
  1249. dbl := pointer(buffer);
  1250. if FIntegerDateTimes then
  1251. dbl^ := BEtoN(pint64(CurrBuff)^) / 1000000
  1252. else
  1253. pint64(dbl)^ := BEtoN(pint64(CurrBuff)^);
  1254. case PQftype(res, x) of
  1255. Oid_Timestamp, Oid_TimestampTZ:
  1256. dbl^ := dbl^ + 3.1558464E+009; // postgres counts seconds elapsed since 1-1-2000
  1257. Oid_Interval:
  1258. dbl^ := dbl^ + BEtoN(plongint(CurrBuff+ 8)^) * SecsPerDay
  1259. + BEtoN(plongint(CurrBuff+12)^) * SecsPerDay * DAYS_PER_MONTH;
  1260. end;
  1261. dbl^ := dbl^ / SecsPerDay;
  1262. // Now convert the mathematically-correct datetime to the
  1263. // illogical windows/delphi/fpc TDateTime:
  1264. if (dbl^ <= 0) and (frac(dbl^) < 0) then
  1265. dbl^ := trunc(dbl^)-2-frac(dbl^);
  1266. end;
  1267. ftBCD, ftFmtBCD:
  1268. begin
  1269. NumericRecord := pointer(CurrBuff);
  1270. NumericRecord^.Digits := BEtoN(NumericRecord^.Digits);
  1271. NumericRecord^.Weight := BEtoN(NumericRecord^.Weight);
  1272. NumericRecord^.Sign := BEtoN(NumericRecord^.Sign);
  1273. NumericRecord^.Scale := BEtoN(NumericRecord^.Scale);
  1274. inc(pointer(currbuff),sizeof(TNumericRecord));
  1275. if (NumericRecord^.Digits = 0) and (NumericRecord^.Scale = 0) then // = NaN, which is not supported by Currency-type, so we return NULL
  1276. result := false
  1277. else if FieldDef.DataType = ftBCD then
  1278. begin
  1279. cur := 0;
  1280. for i := 0 to NumericRecord^.Digits-1 do
  1281. begin
  1282. cur := cur + beton(pword(CurrBuff)^) * intpower(NBASE, NumericRecord^.weight-i);
  1283. inc(pointer(CurrBuff),2);
  1284. end;
  1285. if NumericRecord^.Sign <> 0 then cur := -cur;
  1286. Move(Cur, Buffer^, sizeof(currency));
  1287. end
  1288. else //ftFmtBCD
  1289. begin
  1290. bcd := 0;
  1291. for i := 0 to NumericRecord^.Digits-1 do
  1292. begin
  1293. BCDAdd(bcd, beton(pword(CurrBuff)^) * intpower(NBASE, NumericRecord^.weight-i), bcd);
  1294. inc(pointer(CurrBuff),2);
  1295. end;
  1296. if NumericRecord^.Sign <> 0 then BCDNegate(bcd);
  1297. Move(bcd, Buffer^, sizeof(bcd));
  1298. end;
  1299. end;
  1300. ftCurrency :
  1301. begin
  1302. dbl := pointer(buffer);
  1303. dbl^ := BEtoN(PInt64(CurrBuff)^) / 100;
  1304. end;
  1305. ftBoolean:
  1306. pchar(buffer)[0] := CurrBuff[0];
  1307. ftGuid:
  1308. begin
  1309. Move(CurrBuff^, guid, sizeof(guid));
  1310. guid.D1:=BEtoN(guid.D1);
  1311. guid.D2:=BEtoN(guid.D2);
  1312. guid.D3:=BEtoN(guid.D3);
  1313. s:=GUIDToString(guid);
  1314. StrPLCopy(PChar(Buffer), s, FieldDef.Size);
  1315. end
  1316. else
  1317. result := false;
  1318. end;
  1319. end;
  1320. end;
  1321. end;
  1322. function TPQConnection.PortParamName: string;
  1323. begin
  1324. Result := 'port';
  1325. end;
  1326. procedure TPQConnection.UpdateIndexDefs(IndexDefs : TIndexDefs;TableName : string);
  1327. var qry : TSQLQuery;
  1328. relname : string;
  1329. begin
  1330. if not assigned(Transaction) then
  1331. DatabaseError(SErrConnTransactionnSet);
  1332. if (length(TableName)>2) and (TableName[1]='"') and (TableName[length(TableName)]='"') then
  1333. relname := QuotedStr(AnsiDequotedStr(TableName, '"'))
  1334. else
  1335. relname := 'lower(' + QuotedStr(TableName) + ')'; // unquoted names are stored lower case in PostgreSQL which is incompatible with the SQL standard
  1336. qry := tsqlquery.Create(nil);
  1337. qry.transaction := Transaction;
  1338. qry.database := Self;
  1339. with qry do
  1340. begin
  1341. ReadOnly := True;
  1342. sql.clear;
  1343. sql.add('select '+
  1344. 'ic.relname as indexname, '+
  1345. 'tc.relname as tablename, '+
  1346. 'ia.attname, '+
  1347. 'i.indisprimary, '+
  1348. 'i.indisunique '+
  1349. 'from '+
  1350. 'pg_attribute ta, '+
  1351. 'pg_attribute ia, '+
  1352. 'pg_class tc, '+
  1353. 'pg_class ic, '+
  1354. 'pg_index i '+
  1355. 'where '+
  1356. '(i.indrelid = tc.oid) and '+
  1357. '(ta.attrelid = tc.oid) and '+
  1358. '(ia.attrelid = i.indexrelid) and '+
  1359. '(ic.oid = i.indexrelid) and '+
  1360. '(ta.attnum = i.indkey[ia.attnum-1]) and '+
  1361. '(tc.relname = ' + relname + ') '+
  1362. 'order by '+
  1363. 'ic.relname;');
  1364. open;
  1365. end;
  1366. while not qry.eof do with IndexDefs.AddIndexDef do
  1367. begin
  1368. Name := trim(qry.fields[0].asstring);
  1369. Fields := trim(qry.Fields[2].asstring);
  1370. If qry.fields[3].asboolean then options := options + [ixPrimary];
  1371. If qry.fields[4].asboolean then options := options + [ixUnique];
  1372. qry.next;
  1373. while (name = qry.fields[0].asstring) and (not qry.eof) do
  1374. begin
  1375. Fields := Fields + ';' + trim(qry.Fields[2].asstring);
  1376. qry.next;
  1377. end;
  1378. end;
  1379. qry.close;
  1380. qry.free;
  1381. end;
  1382. function TPQConnection.GetSchemaInfoSQL(SchemaType: TSchemaType;
  1383. SchemaObjectName, SchemaPattern: string): string;
  1384. var s : string;
  1385. begin
  1386. // select * from information_schema.tables with
  1387. // where table_schema [not] in ('pg_catalog','information_schema') may be better.
  1388. // But the following should work:
  1389. case SchemaType of
  1390. stTables : s := 'select '+
  1391. 'relfilenode as recno, '+
  1392. 'current_database() as catalog_name, '+
  1393. 'nspname as schema_name, '+
  1394. 'relname as table_name, '+
  1395. '0 as table_type '+
  1396. 'from pg_class c '+
  1397. 'left join pg_namespace n on c.relnamespace=n.oid '+
  1398. 'where (relkind=''r'') and not (nspname in (''pg_catalog'',''information_schema''))' +
  1399. 'order by relname';
  1400. stSysTables : s := 'select '+
  1401. 'relfilenode as recno, '+
  1402. 'current_database() as catalog_name, '+
  1403. 'nspname as schema_name, '+
  1404. 'relname as table_name, '+
  1405. '0 as table_type '+
  1406. 'from pg_class c '+
  1407. 'left join pg_namespace n on c.relnamespace=n.oid '+
  1408. 'where (relkind=''r'') and nspname in ((''pg_catalog'',''information_schema'')) ' + // only system tables
  1409. 'order by relname';
  1410. stColumns : s := 'select '+
  1411. 'a.attnum as recno, '+
  1412. 'current_database() as catalog_name, '+
  1413. 'nspname as schema_name, '+
  1414. 'c.relname as table_name, '+
  1415. 'a.attname as column_name, '+
  1416. 'a.attnum as column_position, '+
  1417. '0 as column_type, '+
  1418. 'a.atttypid as column_datatype, '+
  1419. 't.typname as column_typename, '+
  1420. '0 as column_subtype, '+
  1421. '0 as column_precision, '+
  1422. '0 as column_scale, '+
  1423. 'a.atttypmod as column_length, '+
  1424. 'not a.attnotnull as column_nullable '+
  1425. 'from pg_class c '+
  1426. 'join pg_attribute a on c.oid=a.attrelid '+
  1427. 'join pg_type t on t.oid=a.atttypid '+
  1428. 'left join pg_namespace n on c.relnamespace=n.oid '+
  1429. // This can lead to problems when case-sensitive tablenames are used.
  1430. 'where (a.attnum>0) and (not a.attisdropped) and (upper(c.relname)=''' + Uppercase(SchemaObjectName) + ''') '+
  1431. 'order by a.attname';
  1432. else
  1433. s := inherited;
  1434. end; {case}
  1435. result := s;
  1436. end;
  1437. function TPQConnection.GetNextValueSQL(const SequenceName: string; IncrementBy: Integer): string;
  1438. begin
  1439. Result := Format('SELECT nextval(''%s'')', [SequenceName]);
  1440. end;
  1441. procedure TPQConnection.LoadBlobIntoBuffer(FieldDef: TFieldDef;
  1442. ABlobBuf: PBufBlobField; cursor: TSQLCursor; ATransaction: TSQLTransaction);
  1443. var
  1444. x : integer;
  1445. li : Longint;
  1446. begin
  1447. with cursor as TPQCursor do
  1448. begin
  1449. x := FieldBinding[FieldDef.FieldNo-1].Index;
  1450. li := pqgetlength(res,curtuple,x);
  1451. ReAllocMem(ABlobBuf^.BlobBuffer^.Buffer,li);
  1452. Move(pqgetvalue(res,CurTuple,x)^, ABlobBuf^.BlobBuffer^.Buffer^, li);
  1453. ABlobBuf^.BlobBuffer^.Size := li;
  1454. end;
  1455. end;
  1456. function TPQConnection.RowsAffected(cursor: TSQLCursor): TRowsCount;
  1457. begin
  1458. if assigned(cursor) and assigned((cursor as TPQCursor).res) then
  1459. Result := StrToIntDef(PQcmdTuples((cursor as TPQCursor).res),-1)
  1460. else
  1461. Result := -1;
  1462. end;
  1463. function TPQConnection.GetConnectionInfo(InfoType: TConnInfoType): string;
  1464. begin
  1465. Result:='';
  1466. try
  1467. {$IFDEF LinkDynamically}
  1468. InitialisePostgres3;
  1469. {$ENDIF}
  1470. case InfoType of
  1471. citServerType:
  1472. Result:=TPQConnectionDef.TypeName;
  1473. citServerVersion,
  1474. citServerVersionString:
  1475. if Connected then
  1476. Result:=format('%6.6d', [PQserverVersion(GetHandle)]);
  1477. citClientName:
  1478. Result:=TPQConnectionDef.LoadedLibraryName;
  1479. else
  1480. Result:=inherited GetConnectionInfo(InfoType);
  1481. end;
  1482. finally
  1483. {$IFDEF LinkDynamically}
  1484. ReleasePostgres3;
  1485. {$ENDIF}
  1486. end;
  1487. end;
  1488. { TPQConnectionDef }
  1489. class function TPQConnectionDef.TypeName: String;
  1490. begin
  1491. Result:='PostgreSQL';
  1492. end;
  1493. class function TPQConnectionDef.ConnectionClass: TSQLConnectionClass;
  1494. begin
  1495. Result:=TPQConnection;
  1496. end;
  1497. class function TPQConnectionDef.Description: String;
  1498. begin
  1499. Result:='Connect to a PostgreSQL database directly via the client library';
  1500. end;
  1501. class function TPQConnectionDef.DefaultLibraryName: String;
  1502. begin
  1503. {$IfDef LinkDynamically}
  1504. Result:=pqlib;
  1505. {$else}
  1506. Result:='';
  1507. {$endif}
  1508. end;
  1509. class function TPQConnectionDef.LoadFunction: TLibraryLoadFunction;
  1510. begin
  1511. {$IfDef LinkDynamically}
  1512. Result:=@InitialisePostgres3;
  1513. {$else}
  1514. Result:=Nil;
  1515. {$endif}
  1516. end;
  1517. class function TPQConnectionDef.UnLoadFunction: TLibraryUnLoadFunction;
  1518. begin
  1519. {$IfDef LinkDynamically}
  1520. Result:=@ReleasePostgres3;
  1521. {$else}
  1522. Result:=Nil;
  1523. {$endif}
  1524. end;
  1525. class function TPQConnectionDef.LoadedLibraryName: string;
  1526. begin
  1527. {$IfDef LinkDynamically}
  1528. Result:=Postgres3LoadedLibrary;
  1529. {$else}
  1530. Result:='';
  1531. {$endif}
  1532. end;
  1533. initialization
  1534. RegisterConnection(TPQConnectionDef);
  1535. finalization
  1536. UnRegisterConnection(TPQConnectionDef);
  1537. end.