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