pqconnection.pp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. unit pqconnection;
  2. {$mode objfpc}{$H+}
  3. {$Define LinkDynamically}
  4. interface
  5. uses
  6. Classes, SysUtils, sqldb, db, dbconst,
  7. {$IfDef LinkDynamically}
  8. postgres3dyn;
  9. {$Else}
  10. postgres3;
  11. {$EndIf}
  12. type
  13. TPQTrans = Class(TSQLHandle)
  14. protected
  15. TransactionHandle : PPGConn;
  16. end;
  17. TPQCursor = Class(TSQLHandle)
  18. protected
  19. Statement : string;
  20. tr : Pointer;
  21. nFields : integer;
  22. res : PPGresult;
  23. BaseRes : PPGresult;
  24. Nr : string;
  25. end;
  26. TPQConnection = class (TSQLConnection)
  27. private
  28. FCursorCount : word;
  29. FConnectString : string;
  30. FSQLDatabaseHandle : pointer;
  31. function TranslateFldType(Type_Oid : integer) : TFieldType;
  32. protected
  33. procedure DoInternalConnect; override;
  34. procedure DoInternalDisconnect; override;
  35. function GetHandle : pointer; override;
  36. Function AllocateCursorHandle : TSQLHandle; override;
  37. Function AllocateTransactionHandle : TSQLHandle; override;
  38. procedure FreeStatement(cursor : TSQLHandle); override;
  39. procedure PrepareStatement(cursor: TSQLHandle;ATransaction : TSQLTransaction;buf : string); override;
  40. procedure FreeFldBuffers(cursor : TSQLHandle); override;
  41. procedure Execute(cursor: TSQLHandle;atransaction:tSQLtransaction); override;
  42. procedure AddFieldDefs(cursor: TSQLHandle; FieldDefs : TfieldDefs); override;
  43. function Fetch(cursor : TSQLHandle) : boolean; override;
  44. function LoadField(cursor : TSQLHandle;FieldDef : TfieldDef;buffer : pointer) : boolean; override;
  45. function GetTransactionHandle(trans : TSQLHandle): pointer; override;
  46. function RollBack(trans : TSQLHandle) : boolean; override;
  47. function Commit(trans : TSQLHandle) : boolean; override;
  48. procedure CommitRetaining(trans : TSQLHandle); override;
  49. function StartdbTransaction(trans : TSQLHandle) : boolean; override;
  50. procedure RollBackRetaining(trans : TSQLHandle); override;
  51. procedure UpdateIndexDefs(var IndexDefs : TIndexDefs;TableName : string); override;
  52. published
  53. property DatabaseName;
  54. property KeepConnection;
  55. property LoginPrompt;
  56. property Params;
  57. property OnLogin;
  58. end;
  59. implementation
  60. ResourceString
  61. SErrRollbackFailed = 'Rollback transaction failed';
  62. SErrCommitFailed = 'Commit transaction failed';
  63. SErrConnectionFailed = 'Connection to database failed';
  64. SErrTransactionFailed = 'Start of transacion failed';
  65. SErrClearSelection = 'Clear of selection failed';
  66. SErrExecuteFailed = 'Execution of query failed';
  67. SErrFieldDefsFailed = 'Can not extract field information from query';
  68. SErrFetchFailed = 'Fetch of data failed';
  69. SErrNoDatabaseName = 'Database connect string (DatabaseName) not filled in!';
  70. const Oid_Bool = 16;
  71. Oid_Text = 25;
  72. Oid_Name = 19;
  73. Oid_Int8 = 20;
  74. Oid_int2 = 21;
  75. Oid_Int4 = 23;
  76. Oid_Float4 = 700;
  77. Oid_Float8 = 701;
  78. Oid_bpchar = 1042;
  79. Oid_varchar = 1043;
  80. Oid_timestamp = 1114;
  81. oid_date = 1082;
  82. oid_time = 1083;
  83. oid_numeric = 1700;
  84. function TPQConnection.GetTransactionHandle(trans : TSQLHandle): pointer;
  85. begin
  86. Result := (trans as TPQtrans).TransactionHandle;
  87. end;
  88. function TPQConnection.RollBack(trans : TSQLHandle) : boolean;
  89. var
  90. res : PPGresult;
  91. tr : TPQTrans;
  92. begin
  93. result := false;
  94. tr := trans as TPQTrans;
  95. res := PQexec(tr.TransactionHandle, 'ROLLBACK');
  96. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  97. begin
  98. PQclear(res);
  99. result := false;
  100. DatabaseError(SErrRollbackFailed + ' (PostgreSQL: ' + PQerrorMessage(tr.transactionhandle) + ')',self);
  101. end
  102. else
  103. begin
  104. PQclear(res);
  105. PQFinish(tr.TransactionHandle);
  106. result := true;
  107. end;
  108. end;
  109. function TPQConnection.Commit(trans : TSQLHandle) : boolean;
  110. var
  111. res : PPGresult;
  112. tr : TPQTrans;
  113. begin
  114. result := false;
  115. tr := trans as TPQTrans;
  116. res := PQexec(tr.TransactionHandle, 'COMMIT');
  117. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  118. begin
  119. PQclear(res);
  120. result := false;
  121. DatabaseError(SErrCommitFailed + ' (PostgreSQL: ' + PQerrorMessage(tr.transactionhandle) + ')',self);
  122. end
  123. else
  124. begin
  125. PQclear(res);
  126. PQFinish(tr.TransactionHandle);
  127. result := true;
  128. end;
  129. end;
  130. function TPQConnection.StartdbTransaction(trans : TSQLHandle) : boolean;
  131. var
  132. res : PPGresult;
  133. tr : TPQTrans;
  134. msg : string;
  135. begin
  136. result := false;
  137. tr := trans as TPQTrans;
  138. tr.TransactionHandle := PQconnectdb(pchar(FConnectString));
  139. if (PQstatus(tr.TransactionHandle) = CONNECTION_BAD) then
  140. begin
  141. result := false;
  142. PQFinish(tr.TransactionHandle);
  143. DatabaseError(SErrConnectionFailed + ' (PostgreSQL: ' + PQerrorMessage(tr.transactionhandle) + ')',self);
  144. end
  145. else
  146. begin
  147. res := PQexec(tr.TransactionHandle, 'BEGIN');
  148. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  149. begin
  150. result := false;
  151. PQclear(res);
  152. msg := PQerrorMessage(tr.transactionhandle);
  153. PQFinish(tr.TransactionHandle);
  154. DatabaseError(sErrTransactionFailed + ' (PostgreSQL: ' + msg + ')',self);
  155. end
  156. else
  157. begin
  158. PQclear(res);
  159. result := true;
  160. end;
  161. end;
  162. end;
  163. procedure TPQConnection.RollBackRetaining(trans : TSQLHandle);
  164. var
  165. res : PPGresult;
  166. tr : TPQTrans;
  167. msg : string;
  168. begin
  169. tr := trans as TPQTrans;
  170. res := PQexec(tr.TransactionHandle, 'ROLLBACK');
  171. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  172. begin
  173. PQclear(res);
  174. DatabaseError(SErrRollbackFailed + ' (PostgreSQL: ' + PQerrorMessage(tr.transactionhandle) + ')',self);
  175. end
  176. else
  177. begin
  178. PQclear(res);
  179. res := PQexec(tr.TransactionHandle, 'BEGIN');
  180. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  181. begin
  182. PQclear(res);
  183. msg := PQerrorMessage(tr.transactionhandle);
  184. PQFinish(tr.TransactionHandle);
  185. DatabaseError(sErrTransactionFailed + ' (PostgreSQL: ' + msg + ')',self);
  186. end
  187. else
  188. PQclear(res);
  189. end;
  190. end;
  191. procedure TPQConnection.CommitRetaining(trans : TSQLHandle);
  192. var
  193. res : PPGresult;
  194. tr : TPQTrans;
  195. msg : string;
  196. begin
  197. tr := trans as TPQTrans;
  198. res := PQexec(tr.TransactionHandle, 'COMMIT');
  199. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  200. begin
  201. PQclear(res);
  202. DatabaseError(SErrCommitFailed + ' (PostgreSQL: ' + PQerrorMessage(tr.transactionhandle) + ')',self);
  203. end
  204. else
  205. begin
  206. PQclear(res);
  207. res := PQexec(tr.TransactionHandle, 'BEGIN');
  208. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  209. begin
  210. PQclear(res);
  211. msg := PQerrorMessage(tr.transactionhandle);
  212. PQFinish(tr.TransactionHandle);
  213. DatabaseError(sErrTransactionFailed + ' (PostgreSQL: ' + msg + ')',self);
  214. end
  215. else
  216. PQclear(res);
  217. end;
  218. end;
  219. procedure TPQConnection.DoInternalConnect;
  220. var msg : string;
  221. begin
  222. {$IfDef LinkDynamically}
  223. InitialisePostgres3;
  224. {$EndIf}
  225. inherited dointernalconnect;
  226. if (DatabaseName = '') then
  227. DatabaseError(SErrNoDatabaseName,self);
  228. FConnectString := '';
  229. if (UserName <> '') then FConnectString := FConnectString + ' user=''' + UserName + '''';
  230. if (Password <> '') then FConnectString := FConnectString + ' password=''' + Password + '''';
  231. if (HostName <> '') then FConnectString := FConnectString + ' host=''' + HostName + '''';
  232. if (DatabaseName <> '') then FConnectString := FConnectString + ' dbname=''' + DatabaseName + '''';
  233. FSQLDatabaseHandle := PQconnectdb(pchar(FConnectString));
  234. if (PQstatus(FSQLDatabaseHandle) = CONNECTION_BAD) then
  235. begin
  236. msg := PQerrorMessage(FSQLDatabaseHandle);
  237. dointernaldisconnect;
  238. DatabaseError(sErrConnectionFailed + ' (PostgreSQL: ' + msg + ')',self);
  239. end;
  240. end;
  241. procedure TPQConnection.DoInternalDisconnect;
  242. begin
  243. PQfinish(FSQLDatabaseHandle);
  244. {$IfDef LinkDynamically}
  245. ReleasePostgres3;
  246. {$EndIf}
  247. end;
  248. function TPQConnection.TranslateFldType(Type_Oid : integer) : TFieldType;
  249. begin
  250. case Type_Oid of
  251. Oid_varchar,Oid_bpchar,
  252. Oid_name : Result := ftstring;
  253. Oid_text : REsult := ftmemo;
  254. Oid_int8 : Result := ftLargeInt;
  255. Oid_int4 : Result := ftInteger;
  256. Oid_int2 : Result := ftSmallInt;
  257. Oid_Float4 : Result := ftFloat;
  258. Oid_Float8 : Result := ftFloat;
  259. Oid_TimeStamp : Result := ftDateTime;
  260. Oid_Date : Result := ftDate;
  261. Oid_Time : Result := ftTime;
  262. Oid_Bool : Result := ftBoolean;
  263. Oid_Numeric : Result := ftBCD;
  264. end;
  265. end;
  266. Function TPQConnection.AllocateCursorHandle : TSQLHandle;
  267. begin
  268. result := TPQCursor.create;
  269. end;
  270. Function TPQConnection.AllocateTransactionHandle : TSQLHandle;
  271. begin
  272. result := TPQTrans.create;
  273. end;
  274. procedure TPQConnection.PrepareStatement(cursor: TSQLHandle;ATransaction : TSQLTransaction;buf : string);
  275. begin
  276. with (cursor as TPQCursor) do
  277. begin
  278. (cursor as TPQCursor).statement := buf;
  279. if StatementType = stselect then
  280. begin
  281. nr := inttostr(FCursorcount);
  282. statement := 'DECLARE slctst' + name + nr +' BINARY CURSOR FOR ' + statement;
  283. inc(FCursorcount);
  284. end;
  285. end;
  286. end;
  287. procedure TPQConnection.FreeStatement(cursor : TSQLHandle);
  288. begin
  289. with cursor as TPQCursor do
  290. if (PQresultStatus(res) <> PGRES_FATAL_ERROR) then //Don't try to do anything if the transaction has already encountered an error.
  291. begin
  292. if StatementType = stselect then
  293. begin
  294. Res := pqexec(tr,pchar('CLOSE slctst' + name + nr));
  295. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  296. begin
  297. pqclear(res);
  298. DatabaseError(SErrClearSelection + ' (PostgreSQL: ' + PQerrorMessage(tr) + ')',self)
  299. end
  300. end;
  301. pqclear(baseres);
  302. pqclear(res);
  303. end;
  304. end;
  305. procedure TPQConnection.FreeFldBuffers(cursor : TSQLHandle);
  306. begin
  307. // Do nothing
  308. end;
  309. procedure TPQConnection.Execute(cursor: TSQLHandle;atransaction:tSQLtransaction);
  310. var st : string;
  311. begin
  312. with cursor as TPQCursor do
  313. begin
  314. tr := aTransaction.Handle;
  315. // res := pqexecParams(tr,pchar(statement),0,nil,nil,nil,nil,1);
  316. st := statement;
  317. res := pqexec(tr,pchar(st));
  318. if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
  319. begin
  320. pqclear(res);
  321. DatabaseError(SErrExecuteFailed + ' (PostgreSQL: ' + PQerrorMessage(tr) + ')',self);
  322. end;
  323. end;
  324. end;
  325. procedure TPQConnection.AddFieldDefs(cursor: TSQLHandle; FieldDefs : TfieldDefs);
  326. var
  327. i : integer;
  328. size : integer;
  329. st : string;
  330. fieldtype : tfieldtype;
  331. begin
  332. with cursor as TPQCursor do
  333. begin
  334. // BaseRes := pqexecParams(tr,'FETCH 0 IN selectst' + pchar(name) ,0,nil,nil,nil,nil,1);
  335. st := pchar('FETCH 0 IN slctst' + name+nr);
  336. BaseRes := pqexec(tr,pchar(st));
  337. if (PQresultStatus(BaseRes) <> PGRES_TUPLES_OK) then
  338. begin
  339. pqclear(BaseRes);
  340. DatabaseError(SErrFieldDefsFailed + ' (PostgreSQL: ' + PQerrorMessage(tr) + ')',self)
  341. end;
  342. nFields := PQnfields(BaseRes);
  343. for i := 0 to nFields-1 do
  344. begin
  345. size := PQfsize(BaseRes, i);
  346. fieldtype := TranslateFldType(PQftype(BaseRes, i));
  347. if (fieldtype = ftstring) and (size = -1) then
  348. size := pqfmod(baseres,i)-3;
  349. if fieldtype = ftdate then
  350. size := sizeof(double);
  351. TFieldDef.Create(FieldDefs, PQfname(BaseRes, i), fieldtype,size, False, (i + 1));
  352. end;
  353. end;
  354. end;
  355. function TPQConnection.GetHandle: pointer;
  356. begin
  357. Result := FSQLDatabaseHandle;
  358. end;
  359. function TPQConnection.Fetch(cursor : TSQLHandle) : boolean;
  360. var st : string;
  361. begin
  362. with cursor as TPQCursor do
  363. begin
  364. st := pchar('FETCH NEXT IN slctst' + name+nr);
  365. Res := pqexec(tr,pchar(st));
  366. if (PQresultStatus(res) <> PGRES_TUPLES_OK) then
  367. begin
  368. pqclear(Res);
  369. DatabaseError(SErrfetchFailed + ' (PostgreSQL: ' + PQerrorMessage(tr) + ')',self)
  370. end;
  371. Result := (PQntuples(res)<>0);
  372. end;
  373. end;
  374. function TPQConnection.LoadField(cursor : TSQLHandle;FieldDef : TfieldDef;buffer : pointer) : boolean;
  375. var
  376. x,i : integer;
  377. li : Longint;
  378. CurrBuff : pchar;
  379. tel : byte;
  380. dbl : pdouble;
  381. begin
  382. with cursor as TPQCursor do
  383. begin
  384. for x := 0 to PQnfields(res)-1 do
  385. if PQfname(Res, x) = FieldDef.Name then break;
  386. if PQfname(Res, x) <> FieldDef.Name then
  387. DatabaseErrorFmt(SFieldNotFound,[FieldDef.Name],self);
  388. if pqgetisnull(res,0,x)=1 then
  389. result := false
  390. else
  391. begin
  392. i := PQfsize(res, x);
  393. CurrBuff := pqgetvalue(res,0,x);
  394. case FieldDef.DataType of
  395. ftInteger, ftSmallint, ftLargeInt,ftfloat :
  396. begin
  397. for tel := 1 to i do // postgres returns big-endian numbers
  398. pchar(Buffer)[tel-1] := CurrBuff[i-tel];
  399. end;
  400. ftString :
  401. begin
  402. li := pqgetlength(res,0,x);
  403. Move(CurrBuff^, Buffer^, li);
  404. pchar(Buffer + li)^ := #0;
  405. i := pqfmod(res,x)-3;
  406. end;
  407. ftdate :
  408. begin
  409. li := 0;
  410. for tel := 1 to i do // postgres returns big-endian numbers
  411. pchar(@li)[tel-1] := CurrBuff[i-tel];
  412. // double(buffer^) := x + 36526; This doesn't work, please tell me what is wrong with it?
  413. dbl := pointer(buffer);
  414. dbl^ := li + 36526;
  415. i := sizeof(double);
  416. end;
  417. ftDateTime, fttime :
  418. begin
  419. dbl := pointer(buffer);
  420. dbl^ := 0;
  421. for tel := 1 to i do // postgres returns big-endian numbers
  422. pchar(Buffer)[tel-1] := CurrBuff[i-tel];
  423. dbl^ := (dbl^+3.1558464E+009)/86400; // postgres counts seconds elapsed since 1-1-2000
  424. end;
  425. ftBCD:
  426. begin
  427. // not implemented
  428. end;
  429. ftBoolean:
  430. pchar(buffer)[0] := CurrBuff[0]
  431. end;
  432. result := true;
  433. end;
  434. end;
  435. end;
  436. procedure TPQConnection.UpdateIndexDefs(var IndexDefs : TIndexDefs;TableName : string);
  437. var qry : TSQLQuery;
  438. begin
  439. if not assigned(Transaction) then
  440. DatabaseError(SErrConnTransactionnSet);
  441. qry := tsqlquery.Create(nil);
  442. qry.transaction := Transaction;
  443. qry.database := Self;
  444. with qry do
  445. begin
  446. ReadOnly := True;
  447. sql.clear;
  448. sql.add('select '+
  449. 'ic.relname as indexname, '+
  450. 'tc.relname as tablename, '+
  451. 'ia.attname, '+
  452. 'i.indisprimary, '+
  453. 'i.indisunique '+
  454. 'from '+
  455. 'pg_attribute ta, '+
  456. 'pg_attribute ia, '+
  457. 'pg_class tc, '+
  458. 'pg_class ic, '+
  459. 'pg_index i '+
  460. 'where '+
  461. '(i.indrelid = tc.oid) and '+
  462. '(ta.attrelid = tc.oid) and '+
  463. '(ia.attrelid = i.indexrelid) and '+
  464. '(ic.oid = i.indexrelid) and '+
  465. '(ta.attnum = i.indkey[ia.attnum-1]) and '+
  466. '(upper(tc.relname)=''' + UpperCase(TableName) +''') '+
  467. 'order by '+
  468. 'ic.relname;');
  469. open;
  470. end;
  471. while not qry.eof do with IndexDefs.AddIndexDef do
  472. begin
  473. Name := trim(qry.fields[0].asstring);
  474. Fields := trim(qry.Fields[2].asstring);
  475. If qry.fields[3].asboolean then options := options + [ixPrimary];
  476. If qry.fields[4].asboolean then options := options + [ixUnique];
  477. qry.next;
  478. while (name = qry.fields[0].asstring) and (not qry.eof) do
  479. begin
  480. Fields := Fields + ';' + trim(qry.Fields[2].asstring);
  481. qry.next;
  482. end;
  483. end;
  484. qry.close;
  485. qry.free;
  486. end;
  487. end.