pqconnection.pp 42 KB

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