pqconnection.pp 49 KB

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