pqconnection.pp 48 KB

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