pqconnection.pp 47 KB

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