pqconnection.pp 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013
  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. TPQTrans = Class(TSQLHandle)
  14. protected
  15. PGConn : PPGConn;
  16. ErrorOccured : boolean;
  17. end;
  18. TPQCursor = Class(TSQLCursor)
  19. protected
  20. Statement : string;
  21. tr : TPQTrans;
  22. res : PPGresult;
  23. CurTuple : integer;
  24. Nr : string;
  25. FieldBinding : array of integer;
  26. end;
  27. { TPQConnection }
  28. TPQConnection = class (TSQLConnection)
  29. private
  30. FCursorCount : word;
  31. FConnectString : string;
  32. FSQLDatabaseHandle : pointer;
  33. FIntegerDateTimes : boolean;
  34. function TranslateFldType(res : PPGresult; Tuple : integer; out Size : integer) : TFieldType;
  35. procedure ExecuteDirectPG(const Query : String);
  36. protected
  37. procedure DoInternalConnect; override;
  38. procedure DoInternalDisconnect; override;
  39. function GetHandle : pointer; override;
  40. Function AllocateCursorHandle : TSQLCursor; override;
  41. Procedure DeAllocateCursorHandle(var cursor : TSQLCursor); override;
  42. Function AllocateTransactionHandle : TSQLHandle; override;
  43. procedure PrepareStatement(cursor: TSQLCursor;ATransaction : TSQLTransaction;buf : string; AParams : TParams); override;
  44. procedure Execute(cursor: TSQLCursor;atransaction:tSQLtransaction; AParams : TParams); override;
  45. procedure AddFieldDefs(cursor: TSQLCursor; FieldDefs : TfieldDefs); override;
  46. function Fetch(cursor : TSQLCursor) : boolean; override;
  47. procedure UnPrepareStatement(cursor : TSQLCursor); override;
  48. function LoadField(cursor : TSQLCursor;FieldDef : TfieldDef;buffer : pointer; out CreateBlob : boolean) : boolean; override;
  49. function GetTransactionHandle(trans : TSQLHandle): pointer; override;
  50. function RollBack(trans : TSQLHandle) : boolean; override;
  51. function Commit(trans : TSQLHandle) : boolean; override;
  52. procedure CommitRetaining(trans : TSQLHandle); override;
  53. function StartdbTransaction(trans : TSQLHandle; AParams : string) : boolean; override;
  54. procedure RollBackRetaining(trans : TSQLHandle); override;
  55. procedure UpdateIndexDefs(IndexDefs : TIndexDefs;TableName : string); override;
  56. function GetSchemaInfoSQL(SchemaType : TSchemaType; SchemaObjectName, SchemaPattern : string) : string; override;
  57. procedure LoadBlobIntoBuffer(FieldDef: TFieldDef;ABlobBuf: PBufBlobField; cursor: TSQLCursor;ATransaction : TSQLTransaction); override;
  58. function RowsAffected(cursor: TSQLCursor): TRowsCount; override;
  59. public
  60. constructor Create(AOwner : TComponent); override;
  61. procedure CreateDB; override;
  62. procedure DropDB; override;
  63. published
  64. property DatabaseName;
  65. property KeepConnection;
  66. property LoginPrompt;
  67. property Params;
  68. property OnLogin;
  69. end;
  70. { TPQConnectionDef }
  71. TPQConnectionDef = Class(TConnectionDef)
  72. Class Function TypeName : String; override;
  73. Class Function ConnectionClass : TSQLConnectionClass; override;
  74. Class Function Description : String; override;
  75. end;
  76. implementation
  77. uses math, strutils, FmtBCD;
  78. ResourceString
  79. SErrRollbackFailed = 'Rollback transaction failed';
  80. SErrCommitFailed = 'Commit transaction failed';
  81. SErrConnectionFailed = 'Connection to database failed';
  82. SErrTransactionFailed = 'Start of transacion failed';
  83. SErrClearSelection = 'Clear of selection failed';
  84. SErrExecuteFailed = 'Execution of query failed';
  85. SErrFieldDefsFailed = 'Can not extract field information from query';
  86. SErrFetchFailed = 'Fetch of data failed';
  87. SErrPrepareFailed = 'Preparation of query failed.';
  88. const Oid_Bool = 16;
  89. Oid_Bytea = 17;
  90. Oid_char = 18;
  91. Oid_Text = 25;
  92. Oid_Oid = 26;
  93. Oid_Name = 19;
  94. Oid_Int8 = 20;
  95. Oid_int2 = 21;
  96. Oid_Int4 = 23;
  97. Oid_Float4 = 700;
  98. Oid_Money = 790;
  99. Oid_Float8 = 701;
  100. Oid_Unknown = 705;
  101. Oid_bpchar = 1042;
  102. Oid_varchar = 1043;
  103. Oid_timestamp = 1114;
  104. oid_date = 1082;
  105. oid_time = 1083;
  106. oid_numeric = 1700;
  107. Oid_uuid = 2950;
  108. constructor TPQConnection.Create(AOwner : TComponent);
  109. begin
  110. inherited;
  111. FConnOptions := FConnOptions + [sqSupportParams] + [sqEscapeRepeat] + [sqEscapeSlash];
  112. FieldNameQuoteChars:=DoubleQuotes;
  113. end;
  114. procedure TPQConnection.CreateDB;
  115. begin
  116. ExecuteDirectPG('CREATE DATABASE ' +DatabaseName);
  117. end;
  118. procedure TPQConnection.DropDB;
  119. begin
  120. ExecuteDirectPG('DROP DATABASE ' +DatabaseName);
  121. end;
  122. procedure TPQConnection.ExecuteDirectPG(const query : string);
  123. var ASQLDatabaseHandle : PPGConn;
  124. res : PPGresult;
  125. msg : String;
  126. begin
  127. CheckDisConnected;
  128. {$IfDef LinkDynamically}
  129. InitialisePostgres3;
  130. {$EndIf}
  131. FConnectString := '';
  132. if (UserName <> '') then FConnectString := FConnectString + ' user=''' + UserName + '''';
  133. if (Password <> '') then FConnectString := FConnectString + ' password=''' + Password + '''';
  134. if (HostName <> '') then FConnectString := FConnectString + ' host=''' + HostName + '''';
  135. FConnectString := FConnectString + ' dbname=''template1''';
  136. if (Params.Text <> '') then FConnectString := FConnectString + ' '+Params.Text;
  137. ASQLDatabaseHandle := PQconnectdb(pchar(FConnectString));
  138. if (PQstatus(ASQLDatabaseHandle) = CONNECTION_BAD) then
  139. begin
  140. msg := PQerrorMessage(ASQLDatabaseHandle);
  141. PQFinish(ASQLDatabaseHandle);
  142. DatabaseError(sErrConnectionFailed + ' (PostgreSQL: ' + Msg + ')',self);
  143. end;
  144. res := PQexec(ASQLDatabaseHandle,pchar(query));
  145. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  146. begin
  147. msg := PQerrorMessage(ASQLDatabaseHandle);
  148. PQclear(res);
  149. PQFinish(ASQLDatabaseHandle);
  150. DatabaseError(SDBCreateDropFailed + ' (PostgreSQL: ' + Msg + ')',self);
  151. end
  152. else
  153. begin
  154. PQclear(res);
  155. PQFinish(ASQLDatabaseHandle);
  156. end;
  157. {$IfDef LinkDynamically}
  158. ReleasePostgres3;
  159. {$EndIf}
  160. end;
  161. function TPQConnection.GetTransactionHandle(trans : TSQLHandle): pointer;
  162. begin
  163. Result := trans;
  164. end;
  165. function TPQConnection.RollBack(trans : TSQLHandle) : boolean;
  166. var
  167. res : PPGresult;
  168. tr : TPQTrans;
  169. begin
  170. result := false;
  171. tr := trans as TPQTrans;
  172. res := PQexec(tr.PGConn, 'ROLLBACK');
  173. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  174. begin
  175. PQclear(res);
  176. result := false;
  177. DatabaseError(SErrRollbackFailed + ' (PostgreSQL: ' + PQerrorMessage(tr.PGConn) + ')',self);
  178. end
  179. else
  180. begin
  181. PQclear(res);
  182. PQFinish(tr.PGConn);
  183. result := true;
  184. end;
  185. end;
  186. function TPQConnection.Commit(trans : TSQLHandle) : boolean;
  187. var
  188. res : PPGresult;
  189. tr : TPQTrans;
  190. begin
  191. result := false;
  192. tr := trans as TPQTrans;
  193. res := PQexec(tr.PGConn, 'COMMIT');
  194. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  195. begin
  196. PQclear(res);
  197. result := false;
  198. DatabaseError(SErrCommitFailed + ' (PostgreSQL: ' + PQerrorMessage(tr.PGConn) + ')',self);
  199. end
  200. else
  201. begin
  202. PQclear(res);
  203. PQFinish(tr.PGConn);
  204. result := true;
  205. end;
  206. end;
  207. function TPQConnection.StartdbTransaction(trans : TSQLHandle; AParams : string) : boolean;
  208. var
  209. res : PPGresult;
  210. tr : TPQTrans;
  211. msg : string;
  212. begin
  213. result := false;
  214. tr := trans as TPQTrans;
  215. tr.PGConn := PQconnectdb(pchar(FConnectString));
  216. if (PQstatus(tr.PGConn) = CONNECTION_BAD) then
  217. begin
  218. result := false;
  219. PQFinish(tr.PGConn);
  220. DatabaseError(SErrConnectionFailed + ' (PostgreSQL: ' + PQerrorMessage(tr.PGConn) + ')',self);
  221. end
  222. else
  223. begin
  224. tr.ErrorOccured := False;
  225. res := PQexec(tr.PGConn, 'BEGIN');
  226. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  227. begin
  228. result := false;
  229. PQclear(res);
  230. msg := PQerrorMessage(tr.PGConn);
  231. PQFinish(tr.PGConn);
  232. DatabaseError(sErrTransactionFailed + ' (PostgreSQL: ' + msg + ')',self);
  233. end
  234. else
  235. begin
  236. PQclear(res);
  237. result := true;
  238. end;
  239. end;
  240. end;
  241. procedure TPQConnection.RollBackRetaining(trans : TSQLHandle);
  242. var
  243. res : PPGresult;
  244. tr : TPQTrans;
  245. msg : string;
  246. begin
  247. tr := trans as TPQTrans;
  248. res := PQexec(tr.PGConn, 'ROLLBACK');
  249. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  250. begin
  251. PQclear(res);
  252. DatabaseError(SErrRollbackFailed + ' (PostgreSQL: ' + PQerrorMessage(tr.PGConn) + ')',self);
  253. end
  254. else
  255. begin
  256. PQclear(res);
  257. res := PQexec(tr.PGConn, 'BEGIN');
  258. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  259. begin
  260. PQclear(res);
  261. msg := PQerrorMessage(tr.PGConn);
  262. PQFinish(tr.PGConn);
  263. DatabaseError(sErrTransactionFailed + ' (PostgreSQL: ' + msg + ')',self);
  264. end
  265. else
  266. PQclear(res);
  267. end;
  268. end;
  269. procedure TPQConnection.CommitRetaining(trans : TSQLHandle);
  270. var
  271. res : PPGresult;
  272. tr : TPQTrans;
  273. msg : string;
  274. begin
  275. tr := trans as TPQTrans;
  276. res := PQexec(tr.PGConn, 'COMMIT');
  277. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  278. begin
  279. PQclear(res);
  280. DatabaseError(SErrCommitFailed + ' (PostgreSQL: ' + PQerrorMessage(tr.PGConn) + ')',self);
  281. end
  282. else
  283. begin
  284. PQclear(res);
  285. res := PQexec(tr.PGConn, 'BEGIN');
  286. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  287. begin
  288. PQclear(res);
  289. msg := PQerrorMessage(tr.PGConn);
  290. PQFinish(tr.PGConn);
  291. DatabaseError(sErrTransactionFailed + ' (PostgreSQL: ' + msg + ')',self);
  292. end
  293. else
  294. PQclear(res);
  295. end;
  296. end;
  297. procedure TPQConnection.DoInternalConnect;
  298. var msg : string;
  299. begin
  300. {$IfDef LinkDynamically}
  301. InitialisePostgres3;
  302. {$EndIf}
  303. inherited dointernalconnect;
  304. FConnectString := '';
  305. if (UserName <> '') then FConnectString := FConnectString + ' user=''' + UserName + '''';
  306. if (Password <> '') then FConnectString := FConnectString + ' password=''' + Password + '''';
  307. if (HostName <> '') then FConnectString := FConnectString + ' host=''' + HostName + '''';
  308. if (DatabaseName <> '') then FConnectString := FConnectString + ' dbname=''' + DatabaseName + '''';
  309. if (Params.Text <> '') then FConnectString := FConnectString + ' '+Params.Text;
  310. FSQLDatabaseHandle := PQconnectdb(pchar(FConnectString));
  311. if (PQstatus(FSQLDatabaseHandle) = CONNECTION_BAD) then
  312. begin
  313. msg := PQerrorMessage(FSQLDatabaseHandle);
  314. dointernaldisconnect;
  315. DatabaseError(sErrConnectionFailed + ' (PostgreSQL: ' + msg + ')',self);
  316. end;
  317. // This does only work for pg>=8.0, so timestamps won't work with earlier versions of pg which are compiled with integer_datetimes on
  318. if PQparameterStatus<>nil then
  319. FIntegerDatetimes := pqparameterstatus(FSQLDatabaseHandle,'integer_datetimes') = 'on';
  320. end;
  321. procedure TPQConnection.DoInternalDisconnect;
  322. begin
  323. PQfinish(FSQLDatabaseHandle);
  324. {$IfDef LinkDynamically}
  325. ReleasePostgres3;
  326. {$EndIf}
  327. end;
  328. function TPQConnection.TranslateFldType(res : PPGresult; Tuple : integer; out Size : integer) : TFieldType;
  329. const VARHDRSZ=sizeof(longint);
  330. var li : longint;
  331. begin
  332. Size := 0;
  333. case PQftype(res,Tuple) of
  334. Oid_varchar,Oid_bpchar,
  335. Oid_name : begin
  336. Result := ftstring;
  337. size := PQfsize(Res, Tuple);
  338. if (size = -1) then
  339. begin
  340. li := PQfmod(res,Tuple);
  341. if li = -1 then
  342. size := dsMaxStringSize
  343. else
  344. size := (li-VARHDRSZ) and $FFFF;
  345. end;
  346. if size > dsMaxStringSize then size := dsMaxStringSize;
  347. end;
  348. // Oid_text : Result := ftstring;
  349. Oid_text : Result := ftMemo;
  350. Oid_Bytea : Result := ftBlob;
  351. Oid_oid : Result := ftInteger;
  352. Oid_int8 : Result := ftLargeInt;
  353. Oid_int4 : Result := ftInteger;
  354. Oid_int2 : Result := ftSmallInt;
  355. Oid_Float4 : Result := ftFloat;
  356. Oid_Float8 : Result := ftFloat;
  357. Oid_TimeStamp : Result := ftDateTime;
  358. Oid_Date : Result := ftDate;
  359. Oid_Time : Result := ftTime;
  360. Oid_Bool : Result := ftBoolean;
  361. Oid_Numeric : begin
  362. Result := ftBCD;
  363. li := PQfmod(res,Tuple);
  364. if li = -1 then
  365. size := 4 // No information about the size available, use the maximum value
  366. else
  367. // The precision is the high 16 bits, the scale the
  368. // low 16 bits with an offset of sizeof(int32).
  369. begin
  370. size := (li-VARHDRSZ) and $FFFF;
  371. if (size > MaxBCDScale) or ((li shr 16)-size > MaxBCDPrecision-MaxBCDScale) then
  372. Result := ftFmtBCD;
  373. end;
  374. end;
  375. Oid_Money : Result := ftCurrency;
  376. Oid_char : begin
  377. Result := ftFixedChar;
  378. Size := 1;
  379. end;
  380. Oid_uuid : begin
  381. Result := ftGuid;
  382. Size := 38;
  383. end;
  384. Oid_Unknown : Result := ftUnknown;
  385. else
  386. Result := ftUnknown;
  387. end;
  388. end;
  389. Function TPQConnection.AllocateCursorHandle : TSQLCursor;
  390. begin
  391. result := TPQCursor.create;
  392. end;
  393. Procedure TPQConnection.DeAllocateCursorHandle(var cursor : TSQLCursor);
  394. begin
  395. FreeAndNil(cursor);
  396. end;
  397. Function TPQConnection.AllocateTransactionHandle : TSQLHandle;
  398. begin
  399. result := TPQTrans.create;
  400. end;
  401. procedure TPQConnection.PrepareStatement(cursor: TSQLCursor;ATransaction : TSQLTransaction;buf : string; AParams : TParams);
  402. const TypeStrings : array[TFieldType] of string =
  403. (
  404. 'Unknown', // ftUnknown
  405. 'text', // ftString
  406. 'smallint', // ftSmallint
  407. 'int', // ftInteger
  408. 'int', // ftWord
  409. 'bool', // ftBoolean
  410. 'float', // ftFloat
  411. 'money', // ftCurrency
  412. 'numeric', // ftBCD
  413. 'date', // ftDate
  414. 'time', // ftTime
  415. 'timestamp', // ftDateTime
  416. 'Unknown', // ftBytes
  417. 'Unknown', // ftVarBytes
  418. 'Unknown', // ftAutoInc
  419. 'bytea', // ftBlob
  420. 'text', // ftMemo
  421. 'bytea', // ftGraphic
  422. 'text', // ftFmtMemo
  423. 'Unknown', // ftParadoxOle
  424. 'Unknown', // ftDBaseOle
  425. 'Unknown', // ftTypedBinary
  426. 'Unknown', // ftCursor
  427. 'char', // ftFixedChar
  428. 'text', // ftWideString
  429. 'bigint', // ftLargeint
  430. 'Unknown', // ftADT
  431. 'Unknown', // ftArray
  432. 'Unknown', // ftReference
  433. 'Unknown', // ftDataSet
  434. 'Unknown', // ftOraBlob
  435. 'Unknown', // ftOraClob
  436. 'Unknown', // ftVariant
  437. 'Unknown', // ftInterface
  438. 'Unknown', // ftIDispatch
  439. 'uuid', // ftGuid
  440. 'Unknown', // ftTimeStamp
  441. 'numeric', // ftFMTBcd
  442. 'Unknown', // ftFixedWideChar
  443. 'Unknown' // ftWideMemo
  444. );
  445. var s : string;
  446. i : integer;
  447. begin
  448. with (cursor as TPQCursor) do
  449. begin
  450. FPrepared := False;
  451. nr := inttostr(FCursorcount);
  452. inc(FCursorCount);
  453. // Prior to v8 there is no support for cursors and parameters.
  454. // So that's not supported.
  455. if FStatementType in [stInsert,stUpdate,stDelete, stSelect] then
  456. begin
  457. tr := TPQTrans(aTransaction.Handle);
  458. // Only available for pq 8.0, so don't use it...
  459. // Res := pqprepare(tr,'prepst'+name+nr,pchar(buf),params.Count,pchar(''));
  460. s := 'prepare prepst'+nr+' ';
  461. if Assigned(AParams) and (AParams.count > 0) then
  462. begin
  463. s := s + '(';
  464. for i := 0 to AParams.count-1 do if TypeStrings[AParams[i].DataType] <> 'Unknown' then
  465. s := s + TypeStrings[AParams[i].DataType] + ','
  466. else
  467. begin
  468. if AParams[i].DataType = ftUnknown then
  469. DatabaseErrorFmt(SUnknownParamFieldType,[AParams[i].Name],self)
  470. else
  471. DatabaseErrorFmt(SUnsupportedParameter,[Fieldtypenames[AParams[i].DataType]],self);
  472. end;
  473. s[length(s)] := ')';
  474. buf := AParams.ParseSQL(buf,false,sqEscapeSlash in ConnOptions, sqEscapeRepeat in ConnOptions,psPostgreSQL);
  475. end;
  476. s := s + ' as ' + buf;
  477. res := pqexec(tr.PGConn,pchar(s));
  478. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  479. begin
  480. pqclear(res);
  481. DatabaseError(SErrPrepareFailed + ' (PostgreSQL: ' + PQerrorMessage(tr.PGConn) + ')',self)
  482. end;
  483. FPrepared := True;
  484. end
  485. else
  486. statement := AParams.ParseSQL(buf,false,sqEscapeSlash in ConnOptions, sqEscapeRepeat in ConnOptions,psPostgreSQL);
  487. end;
  488. end;
  489. procedure TPQConnection.UnPrepareStatement(cursor : TSQLCursor);
  490. begin
  491. with (cursor as TPQCursor) do if FPrepared then
  492. begin
  493. if not tr.ErrorOccured then
  494. begin
  495. PQclear(res);
  496. res := pqexec(tr.PGConn,pchar('deallocate prepst'+nr));
  497. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  498. begin
  499. pqclear(res);
  500. DatabaseError(SErrPrepareFailed + ' (PostgreSQL: ' + PQerrorMessage(tr.PGConn) + ')',self)
  501. end
  502. else
  503. pqclear(res);
  504. end;
  505. FPrepared := False;
  506. end;
  507. end;
  508. procedure TPQConnection.Execute(cursor: TSQLCursor;atransaction:tSQLtransaction;AParams : TParams);
  509. var ar : array of pchar;
  510. l,i : integer;
  511. s : string;
  512. lengths,formats : array of integer;
  513. ParamNames,
  514. ParamValues : array of string;
  515. cash: int64;
  516. begin
  517. with cursor as TPQCursor do
  518. begin
  519. if FStatementType in [stInsert,stUpdate,stDelete,stSelect] then
  520. begin
  521. pqclear(res);
  522. if Assigned(AParams) and (AParams.count > 0) then
  523. begin
  524. l:=Aparams.count;
  525. setlength(ar,l);
  526. setlength(lengths,l);
  527. setlength(formats,l);
  528. for i := 0 to AParams.count -1 do if not AParams[i].IsNull then
  529. begin
  530. case AParams[i].DataType of
  531. ftDateTime:
  532. s := FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz', AParams[i].AsDateTime);
  533. ftDate:
  534. s := FormatDateTime('yyyy-mm-dd', AParams[i].AsDateTime);
  535. ftTime:
  536. s := FormatDateTime('hh:nn:ss.zzz', AParams[i].AsDateTime);
  537. ftFloat, ftBCD:
  538. Str(AParams[i].AsFloat, s);
  539. ftCurrency:
  540. begin
  541. cash:=NtoBE(round(AParams[i].AsCurrency*100));
  542. setlength(s, sizeof(cash));
  543. Move(cash, s[1], sizeof(cash));
  544. end;
  545. ftFmtBCD:
  546. s := BCDToStr(AParams[i].AsFMTBCD, FSQLFormatSettings);
  547. else
  548. s := AParams[i].AsString;
  549. end; {case}
  550. GetMem(ar[i],length(s)+1);
  551. StrMove(PChar(ar[i]),Pchar(s),Length(S)+1);
  552. lengths[i]:=Length(s);
  553. if (AParams[i].DataType in [ftBlob,ftMemo,ftGraphic,ftCurrency]) then
  554. Formats[i]:=1
  555. else
  556. Formats[i]:=0;
  557. end
  558. else
  559. FreeAndNil(ar[i]);
  560. res := PQexecPrepared(tr.PGConn,pchar('prepst'+nr),Aparams.count,@Ar[0],@Lengths[0],@Formats[0],1);
  561. for i := 0 to AParams.count -1 do
  562. FreeMem(ar[i]);
  563. end
  564. else
  565. res := PQexecPrepared(tr.PGConn,pchar('prepst'+nr),0,nil,nil,nil,1);
  566. end
  567. else
  568. begin
  569. tr := TPQTrans(aTransaction.Handle);
  570. if Assigned(AParams) and (AParams.count > 0) then
  571. begin
  572. setlength(ParamNames,AParams.Count);
  573. setlength(ParamValues,AParams.Count);
  574. for i := 0 to AParams.count -1 do
  575. begin
  576. ParamNames[AParams.count-i-1] := '$'+inttostr(AParams[i].index+1);
  577. ParamValues[AParams.count-i-1] := GetAsSQLText(AParams[i]);
  578. end;
  579. s := stringsreplace(statement,ParamNames,ParamValues,[rfReplaceAll]);
  580. end
  581. else
  582. s := Statement;
  583. res := pqexec(tr.PGConn,pchar(s));
  584. if (PQresultStatus(res) in [PGRES_COMMAND_OK,PGRES_TUPLES_OK]) then
  585. begin
  586. pqclear(res);
  587. res:=nil;
  588. end;
  589. end;
  590. if assigned(res) and not (PQresultStatus(res) in [PGRES_COMMAND_OK,PGRES_TUPLES_OK]) then
  591. begin
  592. s := PQerrorMessage(tr.PGConn);
  593. pqclear(res);
  594. tr.ErrorOccured := True;
  595. // Don't perform the rollback, only make it possible to do a rollback.
  596. // The other databases also don't do this.
  597. // atransaction.Rollback;
  598. DatabaseError(SErrExecuteFailed + ' (PostgreSQL: ' + s + ')',self);
  599. end;
  600. end;
  601. end;
  602. procedure TPQConnection.AddFieldDefs(cursor: TSQLCursor; FieldDefs : TfieldDefs);
  603. var
  604. i : integer;
  605. size : integer;
  606. fieldtype : tfieldtype;
  607. nFields : integer;
  608. begin
  609. with cursor as TPQCursor do
  610. begin
  611. nFields := PQnfields(Res);
  612. setlength(FieldBinding,nFields);
  613. for i := 0 to nFields-1 do
  614. begin
  615. fieldtype := TranslateFldType(Res, i,size);
  616. with TFieldDef.Create(FieldDefs, FieldDefs.MakeNameUnique(PQfname(Res, i)), fieldtype,size, False, (i + 1)) do
  617. FieldBinding[FieldNo-1] := i;
  618. end;
  619. CurTuple := -1;
  620. end;
  621. end;
  622. function TPQConnection.GetHandle: pointer;
  623. begin
  624. Result := FSQLDatabaseHandle;
  625. end;
  626. function TPQConnection.Fetch(cursor : TSQLCursor) : boolean;
  627. begin
  628. with cursor as TPQCursor do
  629. begin
  630. inc(CurTuple);
  631. Result := (PQntuples(res)>CurTuple);
  632. end;
  633. end;
  634. function TPQConnection.LoadField(cursor : TSQLCursor;FieldDef : TfieldDef;buffer : pointer; out CreateBlob : boolean) : boolean;
  635. const NBASE=10000;
  636. type TNumericRecord = record
  637. Digits : SmallInt;
  638. Weight : SmallInt;
  639. Sign : SmallInt;
  640. Scale : Smallint;
  641. end;
  642. var
  643. x,i,j : integer;
  644. s : string;
  645. li : Longint;
  646. CurrBuff : pchar;
  647. dbl : pdouble;
  648. cur : currency;
  649. NumericRecord : ^TNumericRecord;
  650. guid : TGUID;
  651. bcd : TBCD;
  652. begin
  653. Createblob := False;
  654. with cursor as TPQCursor do
  655. begin
  656. x := FieldBinding[FieldDef.FieldNo-1];
  657. // Joost, 5 jan 2006: I disabled the following, since it's useful for
  658. // debugging, but it also slows things down. In principle things can only go
  659. // wrong when FieldDefs is changed while the dataset is opened. A user just
  660. // shoudn't do that. ;) (The same is done in IBConnection)
  661. //if PQfname(Res, x) <> FieldDef.Name then
  662. // DatabaseErrorFmt(SFieldNotFound,[FieldDef.Name],self);
  663. if pqgetisnull(res,CurTuple,x)=1 then
  664. result := false
  665. else
  666. begin
  667. CurrBuff := pqgetvalue(res,CurTuple,x);
  668. result := true;
  669. case FieldDef.DataType of
  670. ftInteger, ftSmallint, ftLargeInt, ftFloat :
  671. begin
  672. i := PQfsize(res, x);
  673. case i of // postgres returns big-endian numbers
  674. sizeof(int64) : pint64(buffer)^ := BEtoN(pint64(CurrBuff)^);
  675. sizeof(integer) : pinteger(buffer)^ := BEtoN(pinteger(CurrBuff)^);
  676. sizeof(smallint) : psmallint(buffer)^ := BEtoN(psmallint(CurrBuff)^);
  677. else
  678. for j := 1 to i do
  679. pchar(Buffer)[j-1] := CurrBuff[i-j];
  680. end; {case}
  681. end;
  682. ftString, ftFixedChar :
  683. begin
  684. li := pqgetlength(res,curtuple,x);
  685. if li > dsMaxStringSize then li := dsMaxStringSize;
  686. Move(CurrBuff^, Buffer^, li);
  687. pchar(Buffer + li)^ := #0;
  688. end;
  689. ftBlob, ftMemo :
  690. CreateBlob := True;
  691. ftDate :
  692. begin
  693. dbl := pointer(buffer);
  694. dbl^ := BEtoN(plongint(CurrBuff)^) + 36526;
  695. end;
  696. ftDateTime, ftTime :
  697. begin
  698. pint64(buffer)^ := BEtoN(pint64(CurrBuff)^);
  699. dbl := pointer(buffer);
  700. if FIntegerDatetimes then dbl^ := pint64(buffer)^/1000000;
  701. if FieldDef.DataType = ftDateTime then
  702. dbl^ := dbl^ + 3.1558464E+009; // postgres counts seconds elapsed since 1-1-2000
  703. dbl^ := dbl^ / 86400;
  704. // Now convert the mathematically-correct datetime to the
  705. // illogical windows/delphi/fpc TDateTime:
  706. if (dbl^ <= 0) and (frac(dbl^)<0) then
  707. dbl^ := trunc(dbl^)-2-frac(dbl^);
  708. end;
  709. ftBCD, ftFmtBCD:
  710. begin
  711. NumericRecord := pointer(CurrBuff);
  712. NumericRecord^.Digits := BEtoN(NumericRecord^.Digits);
  713. NumericRecord^.Weight := BEtoN(NumericRecord^.Weight);
  714. NumericRecord^.Sign := BEtoN(NumericRecord^.Sign);
  715. NumericRecord^.Scale := BEtoN(NumericRecord^.Scale);
  716. inc(pointer(currbuff),sizeof(TNumericRecord));
  717. if (NumericRecord^.Digits = 0) and (NumericRecord^.Scale = 0) then // = NaN, which is not supported by Currency-type, so we return NULL
  718. result := false
  719. else if FieldDef.DataType = ftBCD then
  720. begin
  721. cur := 0;
  722. for i := 0 to NumericRecord^.Digits-1 do
  723. begin
  724. cur := cur + beton(pword(CurrBuff)^) * intpower(NBASE, NumericRecord^.weight-i);
  725. inc(pointer(CurrBuff),2);
  726. end;
  727. if NumericRecord^.Sign <> 0 then cur := -cur;
  728. Move(Cur, Buffer^, sizeof(currency));
  729. end
  730. else //ftFmtBCD
  731. begin
  732. bcd := 0;
  733. for i := 0 to NumericRecord^.Digits-1 do
  734. begin
  735. BCDAdd(bcd, beton(pword(CurrBuff)^) * intpower(NBASE, NumericRecord^.weight-i), bcd);
  736. inc(pointer(CurrBuff),2);
  737. end;
  738. if NumericRecord^.Sign <> 0 then BCDNegate(bcd);
  739. Move(bcd, Buffer^, sizeof(bcd));
  740. end;
  741. end;
  742. ftCurrency :
  743. begin
  744. dbl := pointer(buffer);
  745. dbl^ := BEtoN(PInt64(CurrBuff)^) / 100;
  746. end;
  747. ftBoolean:
  748. pchar(buffer)[0] := CurrBuff[0];
  749. ftGuid:
  750. begin
  751. Move(CurrBuff^, guid, sizeof(guid));
  752. guid.D1:=BEtoN(guid.D1);
  753. guid.D2:=BEtoN(guid.D2);
  754. guid.D3:=BEtoN(guid.D3);
  755. s:=GUIDToString(guid);
  756. StrPLCopy(PChar(Buffer), s, FieldDef.Size);
  757. end
  758. else
  759. result := false;
  760. end;
  761. end;
  762. end;
  763. end;
  764. procedure TPQConnection.UpdateIndexDefs(IndexDefs : TIndexDefs;TableName : string);
  765. var qry : TSQLQuery;
  766. begin
  767. if not assigned(Transaction) then
  768. DatabaseError(SErrConnTransactionnSet);
  769. qry := tsqlquery.Create(nil);
  770. qry.transaction := Transaction;
  771. qry.database := Self;
  772. with qry do
  773. begin
  774. ReadOnly := True;
  775. sql.clear;
  776. sql.add('select '+
  777. 'ic.relname as indexname, '+
  778. 'tc.relname as tablename, '+
  779. 'ia.attname, '+
  780. 'i.indisprimary, '+
  781. 'i.indisunique '+
  782. 'from '+
  783. 'pg_attribute ta, '+
  784. 'pg_attribute ia, '+
  785. 'pg_class tc, '+
  786. 'pg_class ic, '+
  787. 'pg_index i '+
  788. 'where '+
  789. '(i.indrelid = tc.oid) and '+
  790. '(ta.attrelid = tc.oid) and '+
  791. '(ia.attrelid = i.indexrelid) and '+
  792. '(ic.oid = i.indexrelid) and '+
  793. '(ta.attnum = i.indkey[ia.attnum-1]) and '+
  794. '(upper(tc.relname)=''' + UpperCase(TableName) +''') '+
  795. 'order by '+
  796. 'ic.relname;');
  797. open;
  798. end;
  799. while not qry.eof do with IndexDefs.AddIndexDef do
  800. begin
  801. Name := trim(qry.fields[0].asstring);
  802. Fields := trim(qry.Fields[2].asstring);
  803. If qry.fields[3].asboolean then options := options + [ixPrimary];
  804. If qry.fields[4].asboolean then options := options + [ixUnique];
  805. qry.next;
  806. while (name = qry.fields[0].asstring) and (not qry.eof) do
  807. begin
  808. Fields := Fields + ';' + trim(qry.Fields[2].asstring);
  809. qry.next;
  810. end;
  811. end;
  812. qry.close;
  813. qry.free;
  814. end;
  815. function TPQConnection.GetSchemaInfoSQL(SchemaType: TSchemaType;
  816. SchemaObjectName, SchemaPattern: string): string;
  817. var s : string;
  818. begin
  819. case SchemaType of
  820. stTables : s := 'select '+
  821. 'relfilenode as recno, '+
  822. '''' + DatabaseName + ''' as catalog_name, '+
  823. ''''' as schema_name, '+
  824. 'relname as table_name, '+
  825. '0 as table_type '+
  826. 'from '+
  827. 'pg_class '+
  828. 'where '+
  829. '(relowner > 1) and relkind=''r''' +
  830. 'order by relname';
  831. stSysTables : s := 'select '+
  832. 'relfilenode as recno, '+
  833. '''' + DatabaseName + ''' as catalog_name, '+
  834. ''''' as schema_name, '+
  835. 'relname as table_name, '+
  836. '0 as table_type '+
  837. 'from '+
  838. 'pg_class '+
  839. 'where '+
  840. 'relkind=''r''' +
  841. 'order by relname';
  842. stColumns : s := 'select '+
  843. 'a.attnum as recno, '+
  844. ''''' as catalog_name, '+
  845. ''''' as schema_name, '+
  846. 'c.relname as table_name, '+
  847. 'a.attname as column_name, '+
  848. '0 as column_position, '+
  849. '0 as column_type, '+
  850. '0 as column_datatype, '+
  851. ''''' as column_typename, '+
  852. '0 as column_subtype, '+
  853. '0 as column_precision, '+
  854. '0 as column_scale, '+
  855. 'a.atttypmod as column_length, '+
  856. 'not a.attnotnull as column_nullable '+
  857. 'from '+
  858. ' pg_class c, pg_attribute a '+
  859. 'WHERE '+
  860. // This can lead to problems when case-sensitive tablenames are used.
  861. '(c.oid=a.attrelid) and (a.attnum>0) and (not a.attisdropped) and (upper(c.relname)=''' + Uppercase(SchemaObjectName) + ''') ' +
  862. 'order by a.attname';
  863. else
  864. DatabaseError(SMetadataUnavailable)
  865. end; {case}
  866. result := s;
  867. end;
  868. procedure TPQConnection.LoadBlobIntoBuffer(FieldDef: TFieldDef;
  869. ABlobBuf: PBufBlobField; cursor: TSQLCursor; ATransaction: TSQLTransaction);
  870. var
  871. x : integer;
  872. li : Longint;
  873. begin
  874. with cursor as TPQCursor do
  875. begin
  876. x := FieldBinding[FieldDef.FieldNo-1];
  877. li := pqgetlength(res,curtuple,x);
  878. ReAllocMem(ABlobBuf^.BlobBuffer^.Buffer,li);
  879. Move(pqgetvalue(res,CurTuple,x)^, ABlobBuf^.BlobBuffer^.Buffer^, li);
  880. ABlobBuf^.BlobBuffer^.Size := li;
  881. end;
  882. end;
  883. function TPQConnection.RowsAffected(cursor: TSQLCursor): TRowsCount;
  884. begin
  885. if assigned(cursor) and assigned((cursor as TPQCursor).res) then
  886. Result := StrToIntDef(PQcmdTuples((cursor as TPQCursor).res),-1)
  887. else
  888. Result := -1;
  889. end;
  890. { TPQConnectionDef }
  891. class function TPQConnectionDef.TypeName: String;
  892. begin
  893. Result:='PostGreSQL';
  894. end;
  895. class function TPQConnectionDef.ConnectionClass: TSQLConnectionClass;
  896. begin
  897. Result:=TPQConnection;
  898. end;
  899. class function TPQConnectionDef.Description: String;
  900. begin
  901. Result:='Connect to a PostGreSQL database directly via the client library';
  902. end;
  903. initialization
  904. RegisterConnection(TPQConnectionDef);
  905. finalization
  906. UnRegisterConnection(TPQConnectionDef);
  907. end.