Selaa lähdekoodia

Merged revisions 3119,3288,3308-3309,3362,3364,3381,3415,3458,3463,3469,3536,3548-3549,3657,3670,3672,3680,3683,3688 via svnmerge from
svn+ssh://[email protected]/FPC/svn/fpc/trunk

........
r3119 | michael | 2006-04-02 10:23:42 +0200 (Sun, 02 Apr 2006) | 1 line

+ Fix for applying null parameters
........
r3288 | joost | 2006-04-20 00:24:04 +0200 (Thu, 20 Apr 2006) | 8 lines

- Fix for TParam.SetDataType
- Implemented TSQLQuery.UpdateSQL .DeleteSQL and .InsertSQL
Added example to fedittable.pp
- Fix for TSQLQuery.ExecSQL if no database is assigned
- TSQLQuery.ApplyRecUpdate now uses parameters
- Fixed handling of ftFixedChar parameters with IBConnection
- Clarified IBConnection parameters-error message

........
r3308 | joost | 2006-04-21 13:30:54 +0200 (Fri, 21 Apr 2006) | 1 line

+ fixed a resource link
........
r3309 | joost | 2006-04-21 13:59:46 +0200 (Fri, 21 Apr 2006) | 1 line

+ fixed bug #5011, thanks to tip from Doug Nettleton
........
r3362 | joost | 2006-04-29 18:25:17 +0200 (Sat, 29 Apr 2006) | 2 lines

+ TDataset.ActiveBuffer now returns nil if the dataset is closed
+ TBufDataset.RecNo now returns -1 if the dataset is closed (bug 5061)
........
r3364 | joost | 2006-04-29 19:03:28 +0200 (Sat, 29 Apr 2006) | 1 line

+ Fixed TBufDataset.First (bug 5068)
........
r3381 | joost | 2006-04-30 13:17:23 +0200 (Sun, 30 Apr 2006) | 1 line

+ Fixed bug #5070
........
r3415 | joost | 2006-05-03 15:03:18 +0200 (Wed, 03 May 2006) | 1 line

+ RecNo should be -1 if the recordset is empty
........
r3458 | michael | 2006-05-08 22:22:20 +0200 (Mon, 08 May 2006) | 1 line

+ Patch from Bram Kuijvenhoven to implement blob field and more verbose errors
........
r3463 | joost | 2006-05-09 22:01:59 +0200 (Tue, 09 May 2006) | 12 lines

+ if TBufDataset.PacketRecords is set to -1, all records are fetched
+ TBufDataset.RecNo now always returns 0 for new records
+ TBufDataset.Recordcount does not count a new record anymore
+ EOF and BOF must be true when a dataset is created
+ Close does not call CheckBrowseMode anymore
+ Add a check in MoveBy for distance=0
+ MoveBy calls DoBeforeScroll before it does anything else
+ Send the right dataevent in MoveBy (bug 5048)
+ Check for an edit state in Post
+ Fixed NotInEditState error message
+ added the deConnectChange,deReconcileError,deDisabledStateChange TDataEvents
+ Do not send a deFieldListChange event when opening the dataset
........
r3469 | michael | 2006-05-10 11:05:15 +0200 (Wed, 10 May 2006) | 1 line

+ Patch from Bram Kuijvenhoven: optimised storage of zero-length BLOBS
........
r3536 | joost | 2006-05-14 23:14:31 +0200 (Sun, 14 May 2006) | 1 line

+ Removed the usage of a cursor
........
r3548 | joost | 2006-05-16 17:15:47 +0200 (Tue, 16 May 2006) | 4 lines

+ implemented TStringField.FixedChar
+ Enabled check for FixedChar in TParam.AssignFieldValue
+ added test for the above
+ added test for TDataset.Close
........
r3549 | joost | 2006-05-16 20:27:41 +0200 (Tue, 16 May 2006) | 1 line

+ Added check to TParam.SetDataType for empty variants
........
r3657 | joost | 2006-05-24 18:57:19 +0200 (Wed, 24 May 2006) | 1 line

+ fixed TBufDataset.SetRecNo (bug 6919) and added a test for it
........
r3670 | joost | 2006-05-25 13:32:32 +0200 (Thu, 25 May 2006) | 1 line

+ added TBufDataset.ChangeCount (bug 6947)
........
r3672 | michael | 2006-05-25 13:57:07 +0200 (Thu, 25 May 2006) | 1 line

+ Added some missing methods
........
r3680 | joost | 2006-05-26 13:09:26 +0200 (Fri, 26 May 2006) | 2 lines

+ Fixed problems with DateTimes before 1899-12-29 (issue #6925)
+ fix for the datasize of strings
........
r3683 | joost | 2006-05-26 16:50:19 +0200 (Fri, 26 May 2006) | 1 line

+ implemented TBufDataset.UpdateStatus (issue #6944)
........
r3688 | joost | 2006-05-27 00:00:42 +0200 (Sat, 27 May 2006) | 2 lines

+ Added support for empty parameters to TPQConnection
+ Added errormessage for unsupported parameters
........

git-svn-id: branches/fixes_2_0@3768 -

joost 19 vuotta sitten
vanhempi
commit
d3b22205fb

+ 50 - 16
fcl/db/bufdataset.inc

@@ -27,7 +27,7 @@ end;
 
 procedure TBufDataset.SetPacketRecords(aValue : integer);
 begin
-  if aValue > 0 then FPacketRecords := aValue
+  if (aValue = -1) or (aValue > 0) then FPacketRecords := aValue
     else DatabaseError(SInvPacketRecordsValue);
 end;
 
@@ -68,6 +68,7 @@ begin
   FLastRecBuf := FFirstRecBuf;
   FCurrentRecBuf := FLastRecBuf;
 
+  FAllPacketsFetched := False;
   FOpen:=True;
 end;
 
@@ -90,13 +91,13 @@ end;
 
 procedure TBufDataset.InternalFirst;
 begin
-  FCurrentRecBuf := FFirstRecBuf;
+  FCurrentRecBuf := nil;
 end;
 
 procedure TBufDataset.InternalLast;
 begin
   repeat
-  until getnextpacket < FPacketRecords;
+  until (getnextpacket < FPacketRecords) or (FPacketRecords = -1);
   if FLastRecBuf <> FFirstRecBuf then
     FCurrentRecBuf := FLastRecBuf;
 end;
@@ -138,6 +139,7 @@ begin
         begin
         if getnextpacket = 0 then result := grEOF;
         end
+      else if FCurrentRecBuf = nil then FCurrentRecBuf := FFirstRecBuf
       else if (PBufRecLinkItem(FCurrentRecBuf)^.next = FLastRecBuf) then
         begin
         if getnextpacket > 0 then
@@ -224,9 +226,14 @@ var i : integer;
     pb : pchar;
     
 begin
+  if FAllPacketsFetched then
+    begin
+    result := 0;
+    exit;
+    end;
   i := 0;
   pb := pchar(pointer(FLastRecBuf)+sizeof(TBufRecLinkItem));
-  while (i < FPacketRecords) and (loadbuffer(pb) = grOk) do
+  while ((i < FPacketRecords) or (FPacketRecords = -1)) and (loadbuffer(pb) = grOk) do
     begin
     FLastRecBuf^.next := pointer(IntAllocRecordBuffer);
     FLastRecBuf^.next^.prior := FLastRecBuf;
@@ -267,6 +274,7 @@ begin
   if not Fetch then
     begin
     Result := grEOF;
+    FAllPacketsFetched := True;
     Exit;
     end;
 
@@ -594,6 +602,13 @@ begin
   result := FRecordSize;
 end;
 
+function TBufDataset.GetChangeCount: integer;
+
+begin
+  result := length(FUpdateBuffer);
+end;
+
+
 procedure TBufDataset.InternalInitRecord(Buffer: PChar);
 
 begin
@@ -608,9 +623,10 @@ var recnr        : integer;
     TmpRecBuffer : PBufRecLinkItem;
 
 begin
+  checkbrowsemode;
   if value > RecordCount then
     begin
-    repeat until (getnextpacket < FPacketRecords) or (value <= RecordCount);
+    repeat until (getnextpacket < FPacketRecords) or (value <= RecordCount) or (FPacketRecords = -1);
     if value > RecordCount then
       begin
       DatabaseError(SNoSuchRecord,self);
@@ -620,7 +636,7 @@ begin
   TmpRecBuffer := FFirstRecBuf;
   for recnr := 1 to value-1 do
     TmpRecBuffer := TmpRecBuffer^.next;
-  GotoBookmark(TmpRecBuffer);
+  GotoBookmark(@TmpRecBuffer);
 end;
 
 function TBufDataset.GetRecNo: Longint;
@@ -628,17 +644,24 @@ function TBufDataset.GetRecNo: Longint;
 Var SearchRecBuffer : PBufRecLinkItem;
     TmpRecBuffer    : PBufRecLinkItem;
     recnr           : integer;
+    abuf            : PChar;
 
 begin
-  GetBookmarkData(ActiveBuffer,@SearchRecBuffer);
-  TmpRecBuffer := FFirstRecBuf;
-  recnr := 1;
-  while TmpRecBuffer <> SearchRecBuffer do
+  abuf := ActiveBuffer;
+  // If abuf isn't assigned, the recordset probably isn't opened.
+  if assigned(abuf) and (FBRecordCount>0) and (state <> dsInsert) then
     begin
-    inc(recnr);
-    TmpRecBuffer := TmpRecBuffer^.next;
-    end;
-  result := recnr;
+    GetBookmarkData(abuf,@SearchRecBuffer);
+    TmpRecBuffer := FFirstRecBuf;
+    recnr := 1;
+    while TmpRecBuffer <> SearchRecBuffer do
+      begin
+      inc(recnr);
+      TmpRecBuffer := TmpRecBuffer^.next;
+      end;
+    result := recnr;
+    end
+  else result := 0;
 end;
 
 function TBufDataset.IsCursorOpen: Boolean;
@@ -650,8 +673,19 @@ end;
 Function TBufDataset.GetRecordCount: Longint;
 
 begin
-  if state <> dsInsert then Result := FBRecordCount
-    else Result := FBRecordCount+1;
+  Result := FBRecordCount;
+end;
+
+Function TBufDataSet.UpdateStatus: TUpdateStatus;
+
+begin
+  Result:=usUnmodified;
+  if GetRecordUpdateBuffer then
+    case FUpdateBuffer[FCurrentUpdateBuffer].UpdateKind of
+      ukModify : Result := usModified;
+      ukInsert : Result := usInserted;
+      ukDelete : Result := usDeleted;
+    end;
 end;
 
 Function TBufDataset.Locate(const KeyFields: string; const KeyValues: Variant; options: TLocateOptions) : boolean;

+ 46 - 35
fcl/db/dataset.inc

@@ -27,6 +27,15 @@ begin
   FFieldDefs:=TFieldDefs.Create(Self);
   FFieldList:=TFields.Create(Self);
   FDataSources:=TList.Create;
+  
+// FBuffer must be allocated on create, to make Activebuffer return nil
+  ReAllocMem(FBuffers,SizeOf(PChar));
+//  pointer(FBuffers^) := nil;
+  FBuffers[0] := nil;
+  FActiveRecord := 0;
+  FBufferCount := 0;
+  FEOF := True;
+  FBOF := True;
 end;
 
 
@@ -46,12 +55,9 @@ begin
       TDatasource(Items[Count - 1]).DataSet:=Nil;
     Free;
     end;
-  if Assigned(FBuffers) then
-    begin
-    for i := 0 to FBufferCount do
-      FreeRecordBuffer(FBuffers[i]);
-    FreeMem(FBuffers);
-    end;
+  for i := 0 to FBufferCount do
+    FreeRecordBuffer(FBuffers[i]);
+  FreeMem(FBuffers);
   Inherited Destroy;
 end;
 
@@ -392,16 +398,14 @@ begin
     DoAfterOpen;
     DoAfterScroll;
   except
-    DoInternalClose(false);
+    DoInternalClose;
     raise;
   end;
 end;
 
-Procedure TDataset.DoInternalClose(DoCheck : Boolean);
+Procedure TDataset.DoInternalClose;
 
 begin
-  if DoCheck then
-    CheckBrowsemode;
   DoBeforeScroll;
   DoBeforeClose;
   FreeFieldBuffers;
@@ -818,7 +822,7 @@ begin
       DoInternalOpen;
     end
   else if not value and (Fstate <> dsinactive) then
-    DoInternalClose(True);
+    DoInternalClose;
 end;
 
 procedure TDataset.Loaded;
@@ -922,7 +926,7 @@ begin
 {$endif}
     except
       I:=FBufferCount;
-      While (I<(Value+1)) and (FBuffers[i]<>Nil) do
+      While (I<(Value+1)) do
         begin
         FreeRecordBuffer(FBuffers[i]);
         Inc(i);
@@ -946,7 +950,14 @@ begin
       begin
       For I:=Value+1 to FBufferCount do
         FreeRecordBuffer(FBuffers[i]);
-      ReAllocMem(FBuffers,(Value+1)*SizeOf(Pchar));
+      // FBuffer must stay allocated, to make sure that Activebuffer returns nil
+      if Value = -1 then
+        begin
+        ReAllocMem(FBuffers,SizeOf(Pchar));
+        FBuffers[0] := nil;
+        end
+      else
+        ReAllocMem(FBuffers,(Value+1)*SizeOf(Pchar));
       end;
     if FRecordcount > Value then FRecordcount := Value;
     end;
@@ -1095,14 +1106,12 @@ end;
 
 Function TDataset.ControlsDisabled: Boolean;
 
-
 begin
   Result := (FDisableControlsCount > 0);
 end;
 
 Function TDataset.ActiveBuffer: PChar;
 
-
 begin
 {$ifdef dsdebug}
   Writeln ('Active buffer requested. Returning:',ActiveRecord);
@@ -1266,7 +1275,7 @@ end;
 Procedure TDataset.DoInsertAppend(DoAppend : Boolean);
 
 
-  procedure DoInsert;
+  procedure DoInsert(DoAppend : Boolean);
 
   Var BookBeforeInsert : TBookmarkStr;
       TempBuf : pointer;
@@ -1277,18 +1286,22 @@ Procedure TDataset.DoInsertAppend(DoAppend : Boolean);
   If FRecordcount > 0 then
     BookBeforeInsert:=Bookmark;
 
-  if FActiveRecord < FRecordCount-1 then
+  if not DoAppend then
     begin
-    TempBuf := FBuffers[FBuffercount];
-    move(FBuffers[FActiveRecord],FBuffers[FActiveRecord+1],(Fbuffercount-FActiveRecord)*sizeof(FBuffers[0]));
-    FBuffers[FActiveRecord]:=TempBuf;
+    if FRecordCount > 0 then
+      begin
+      TempBuf := FBuffers[FBuffercount];
+      move(FBuffers[FActiveRecord],FBuffers[FActiveRecord+1],(Fbuffercount-FActiveRecord)*sizeof(FBuffers[0]));
+      FBuffers[FActiveRecord]:=TempBuf;
+      end;
     end
   else if FRecordcount=FBuffercount then
     shiftbuffersbackward
-  else begin
+  else
+    begin
     if FRecordCount>0 then
-    inc(FActiveRecord);
-  end;
+      inc(FActiveRecord);
+    end;
 
   // Active buffer is now edit buffer. Initialize.
   InitRecord(FBuffers[FActiveRecord]);
@@ -1330,8 +1343,7 @@ begin
 {$ifdef dsdebug}
     Writeln ('going to insert mode');
 {$endif}
-
-    DoInsert;
+    DoInsert(false);
     end
   else
     begin
@@ -1343,7 +1355,7 @@ begin
     GetPriorRecords;
     if FRecordCount>0 then
       FActiveRecord:=FRecordCount-1;
-    DoInsert;
+    DoInsert(True);
     SetBookmarkFlag(ActiveBuffer,bfEOF);
     FBOF :=False;
     FEOF := true;
@@ -1685,10 +1697,11 @@ Var
 begin
   CheckBrowseMode;
   Result:=0; TheResult:=0;
-  If ((Distance>0) and FEOF) or
+  DoBeforeScroll;
+  If (Distance = 0) or
+     ((Distance>0) and FEOF) or
      ((Distance<0) and FBOF) then
     exit;
-  DoBeforeScroll;
   Try
     Scrolled := 0;
     If Distance>0 then
@@ -1699,11 +1712,7 @@ begin
 {$ifdef dsdebug}
     WriteLn('ActiveRecord=', FActiveRecord,' FEOF=',FEOF,' FBOF=',FBOF);
 {$Endif}
-//    If FRecordCount<>PrevRecordCount then
-    if Scrolled = 0 then
-      DataEvent(deDatasetChange,0)
-    else
-      DataEvent(deDatasetScroll,Scrolled);
+    DataEvent(deDatasetScroll,Scrolled);
     DoAfterScroll;
     Result:=TheResult;
   end;
@@ -1760,7 +1769,9 @@ begin
     writeln ('Post: Browse mode set');
 {$endif}
     DoAfterPost;
-    end;
+    end
+  else
+    DatabaseErrorFmt(SNotInEditState, [Name], Self);
 end;
 
 Procedure TDataset.Prior;
@@ -1914,7 +1925,7 @@ Procedure TDataset.UpdateRecord;
 
 begin
   if not (State in dsEditModes) then
-    DatabaseError(SNotInEditState, Self);
+    DatabaseErrorFmt(SNotInEditState, [Name], Self);
   DataEvent(deUpdateRecord, 0);
 end;
 

+ 28 - 2
fcl/db/db.pp

@@ -51,7 +51,7 @@ type
   TDataEvent = (deFieldChange, deRecordChange, deDataSetChange,
     deDataSetScroll, deLayoutChange, deUpdateRecord, deUpdateState,
     deCheckBrowseMode, dePropertyChange, deFieldListChange, deFocusControl,
-    deParentScroll);
+    deParentScroll,deConnectChange,deReconcileError,deDisabledStateChange);
 
   TUpdateStatus = (usUnmodified, usModified, usInserted, usDeleted);
   TUpdateStatusSet = SET OF TUpdateStatus;
@@ -386,6 +386,8 @@ type
 { TStringField }
 
   TStringField = class(TField)
+  private
+    FFixedChar : boolean;
   protected
     class procedure CheckTypeSize(AValue: Longint); override;
     function GetAsBoolean: Boolean; override;
@@ -406,6 +408,7 @@ type
     procedure SetVarValue(const AValue: Variant); override;
   public
     constructor Create(AOwner: TComponent); override;
+    property FixedChar : Boolean read FFixedChar write FFixedChar;
   published
     property Size default 20;
   end;
@@ -965,7 +968,7 @@ type
     FState : TDataSetState;
     Procedure DoInsertAppend(DoAppend : Boolean);
     Procedure DoInternalOpen;
-    Procedure DoInternalClose(DoCheck : Boolean);
+    Procedure DoInternalClose;
     Function  GetBuffer (Index : longint) : Pchar;
     Function  GetField (Index : Longint) : TField;
     Procedure RegisterDataSource(ADatasource : TDataSource);
@@ -1509,6 +1512,8 @@ type
     FCurrentUpdateBuffer : integer;
 
     FFieldBufPositions : array of longint;
+    
+    FAllPacketsFetched : boolean;
     procedure CalcRecordSize;
     function LoadBuffer(Buffer : PChar): TGetResult;
     function GetFieldSize(FieldDef : TFieldDef) : longint;
@@ -1518,6 +1523,7 @@ type
   protected
     procedure SetRecNo(Value: Longint); override;
     function  GetRecNo: Longint; override;
+    function GetChangeCount: integer; virtual;
     function  AllocRecordBuffer: PChar; override;
     procedure FreeRecordBuffer(var Buffer: PChar); override;
     procedure InternalInitRecord(Buffer: PChar); override;
@@ -1555,6 +1561,8 @@ type
     procedure CancelUpdates; virtual;
     destructor Destroy; override;
     function Locate(const keyfields: string; const keyvalues: Variant; options: TLocateOptions) : boolean; override;
+    function UpdateStatus: TUpdateStatus; override;
+    property ChangeCount : Integer read GetChangeCount;
   published
     property PacketRecords : Integer read FPacketRecords write FPacketRecords default 10;
   end;
@@ -1838,6 +1846,9 @@ Function ExtractFieldName(Const Fields: String; var Pos: Integer): String;
 Function DateTimeRecToDateTime(DT: TFieldType; Data: TDateTimeRec): TDateTime;
 Function DateTimeToDateTimeRec(DT: TFieldType; Data: TDateTime): TDateTimeRec;
 
+procedure DisposeMem(var Buffer; Size: Integer);
+function BuffersEqual(Buf1, Buf2: Pointer; Size: Integer): Boolean;
+
 implementation
 
 uses dbconst,typinfo;
@@ -2123,6 +2134,21 @@ begin
   if i >= 0 then Result := PLookupListRec(FList.Items[I])^.Value;
 end;
 
+procedure DisposeMem(var Buffer; Size: Integer);
+begin
+  if Pointer(Buffer) <> nil then
+    begin
+    FreeMem(Pointer(Buffer), Size);
+    Pointer(Buffer) := nil;
+    end;
+end;
+
+function BuffersEqual(Buf1, Buf2: Pointer; Size: Integer): Boolean; 
+
+begin
+  Result:=CompareByte(Buf1,Buf2,Size)=0
+end;
+
 {$i dataset.inc}
 {$i fields.inc}
 {$i datasource.inc}

+ 1 - 0
fcl/db/dbconst.pp

@@ -69,6 +69,7 @@ Const
   SDeletedRecord           = 'The record is deleted.';
   SIndexNotFound           = 'Index ''%s'' not found';
   SParameterCountIncorrect = 'The number of parameters is incorrect.';
+  SUnsupportedParameter    = 'Parameters of the type ''%s'' are not (yet) supported.';
   SFieldValueError         = 'Invalid value for field ''%s''';
   SInvalidCalcType         = 'Field ''%s'' cannot be a calculated or lookup field';
   SDuplicateName           = 'Duplicate name ''%s'' in %s';

+ 10 - 8
fcl/db/dsparams.inc

@@ -633,11 +633,14 @@ begin
   If (VT=varError) then
     clear
   else
-    Try
-      FValue:=VarAsType(AValue,VT)
-    except
-      Clear;
-    end;
+    if not VarIsEmpty(FValue) then
+      begin
+      Try
+        FValue:=VarAsType(FValue,VT)
+      except
+        Clear;
+      end { try }
+      end;
 end;
 
 Procedure TParam.SetText(const AValue: string);
@@ -764,8 +767,7 @@ Procedure TParam.AssignFieldValue(Field: TField; const AValue: Variant);
 begin
   If Assigned(Field) then
     begin
-    // Need TField.FixedChar property.
-    if (Field.DataType = ftString) {and TStringField(Field).FixedChar} then
+    if (Field.DataType = ftString) and TStringField(Field).FixedChar then
       DataType:=ftFixedChar
     else if (Field.DataType = ftMemo) and (Field.Size > 255) then
       DataType:=ftString
@@ -972,7 +974,7 @@ begin
      if CopyBound or (not P.Bound) then
        begin
        F:=ADataset.FieldByName(P.Name);
-       P.AssignFromField(F);   
+       P.AssignField(F);
        If Not CopyBound then
          P.Bound:=False;
        end;

+ 2 - 1
fcl/db/fields.inc

@@ -960,6 +960,7 @@ constructor TStringField.Create(AOwner: TComponent);
 begin
   Inherited Create(AOwner);
   SetDataType(ftString);
+  FFixedChar := False;
   Size:=20;
 end;
 
@@ -2399,7 +2400,7 @@ end;
 Procedure Tfields.Changed;
 
 begin
-  if (FDataSet <> nil) and not (csDestroying in FDataSet.ComponentState) then
+  if (FDataSet <> nil) and not (csDestroying in FDataSet.ComponentState) and FDataset.Active then
     FDataSet.DataEvent(deFieldListChange, 0);
   If Assigned(FOnChange) then
     FOnChange(Self);

+ 10 - 1
fcl/db/sqldb/examples/fedittable.pp

@@ -35,8 +35,17 @@ begin
     
     SQL.Add('select * from FPDEV');
 
+// With these lines commented out, TSQLQuery creates the update, delete and insert
+// queries itself.
+// For more complex queries, though, it could be nessecary to provide these queries
+// here
+
+//    UpdateSQL.add('update fpdev set name=:name, birthdate=:birthdate where id=:OLD_id');
+//    DeleteSQL.add('delete from fpdev where id=:OLD_id');
+//    InsertSQL.add('insert into fpdev(id,name,email,birthdate) values (:id,:name,:email,:birthdate)');}
+
     open;
-    
+
     Edit;
     FieldByName('name').AsString := FPdevNames[1];
     FieldByName('birthdate').AsDateTime := FPdevBirthDates[1];

+ 6 - 5
fcl/db/sqldb/interbase/ibconnection.pp

@@ -609,7 +609,10 @@ begin
     begin
     ParNr := ParamBinding[SQLVarNr];
     if AParams[ParNr].IsNull then
-      in_sqlda^.SQLvar[SQLVarNr].SQLInd^ := -1
+      begin
+      If Assigned(in_sqlda^.SQLvar[SQLVarNr].SQLInd) then
+        in_sqlda^.SQLvar[SQLVarNr].SQLInd^ := -1;
+      end
     else
       begin
       if assigned(in_sqlda^.SQLvar[SQLVarNr].SQLInd) then in_sqlda^.SQLvar[SQLVarNr].SQLInd^ := 0;
@@ -622,7 +625,7 @@ begin
           Move(i, in_sqlda^.SQLvar[SQLVarNr].SQLData^, in_SQLDA^.SQLVar[SQLVarNr].SQLLen);
           {$R+}
           end;
-        ftString  :
+        ftString,ftFixedChar  :
           begin
           {$R-}
           s := AParams[ParNr].AsString;
@@ -653,9 +656,7 @@ begin
           {$R+}
           end;
       else
-        begin
-        DatabaseError('This kind of parameter in not (yet) supported.',self);
-        end;
+        DatabaseErrorFmt(SUnsupportedParameter,[Fieldtypenames[AParams[ParNr].DataType]],self);
       end {case}
       end;
     end;

+ 12 - 7
fcl/db/sqldb/mysql/mysqlconn.inc

@@ -289,10 +289,6 @@ begin
   C:=Cursor as TCursorName;
   if c.FStatementType=stSelect then
     c.FNeedData:=False;
-  If (C.FRes<>Nil) then
-    begin
-    C.FRes:=Nil;
-    end;
   if (c.FQMySQL <> Nil) then
     begin
     mysql_close(c.FQMySQL);
@@ -487,9 +483,9 @@ begin
   EY := StrToInt(Copy(S, 1, 4));
   EM := StrToInt(Copy(S, 6, 2));
   ED := StrToInt(Copy(S, 9, 2));
-  EH := StrToInt(Copy(S, 11, 2));
-  EN := StrToInt(Copy(S, 14, 2));
-  ES := StrToInt(Copy(S, 17, 2));
+  EH := StrToInt(Copy(S, 12, 2));
+  EN := StrToInt(Copy(S, 15, 2));
+  ES := StrToInt(Copy(S, 18, 2));
   if (EY = 0) or (EM = 0) or (ED = 0) then
     Result := 0
   else
@@ -516,12 +512,21 @@ var
   EH, EN, ES: Word;
 
 begin
+{$IFNDEF mysql40}
+  EY := StrToInt(Copy(S, 1, 4));
+  EM := StrToInt(Copy(S, 6, 2));
+  ED := StrToInt(Copy(S, 9, 2));
+  EH := StrToInt(Copy(S, 12, 2));
+  EN := StrToInt(Copy(S, 15, 2));
+  ES := StrToInt(Copy(S, 18, 2));
+{$ELSE}
   EY := StrToInt(Copy(S, 1, 4));
   EM := StrToInt(Copy(S, 5, 2));
   ED := StrToInt(Copy(S, 7, 2));
   EH := StrToInt(Copy(S, 9, 2));
   EN := StrToInt(Copy(S, 11, 2));
   ES := StrToInt(Copy(S, 13, 2));
+{$ENDIF}
   if (EY = 0) or (EM = 0) or (ED = 0) then
     Result := 0
   else

+ 129 - 16
fcl/db/sqldb/odbc/odbcconn.pas

@@ -31,6 +31,7 @@ type
     FQuery:string;        // last prepared query, with :ParamName converted to ?
     FParamIndex:TParamBinding; // maps the i-th parameter in the query to the TParams passed to PrepareStatement
     FParamBuf:array of pointer; // buffers that can be used to bind the i-th parameter in the query
+    FBlobStreams:TList;   // list of Blob TMemoryStreams stored in field buffers (we need this currently as we can't hook into the freeing of TBufDataset buffers)
   public
     constructor Create(Connection:TODBCConnection);
     destructor Destroy; override;
@@ -101,8 +102,8 @@ type
   public
     property Environment:TODBCEnvironment read FEnvironment;
   published
-    property Driver:string read FDriver write FDriver;                         // will be passed as DRIVER connection parameter
-    property FileDSN:string read FFileDSN write FFileDSN;                      // will be passed as FILEDSN parameter
+    property Driver:string read FDriver write FDriver;    // will be passed as DRIVER connection parameter
+    property FileDSN:string read FFileDSN write FFileDSN; // will be passed as FILEDSN parameter
     // Redeclare properties from TSQLConnection
     property Password;     // will be passed as PWD connection parameter
     property Transaction;
@@ -126,7 +127,7 @@ type
 implementation
 
 uses
-  Math; // for the Min proc
+  Math, DBConst;
 
 const
   DefaultEnvironment:TODBCEnvironment = nil;
@@ -351,7 +352,8 @@ var
   OutConnectionString:string;
   ActualLength:SQLSMALLINT;
 begin
-  inherited DoInternalConnect;
+  // Do not call the inherited method as it checks for a non-empty DatabaseName, and we don't even use DatabaseName!
+  // inherited DoInternalConnect;
 
   // make sure we have an environment
   if not Assigned(FEnvironment) then
@@ -382,7 +384,7 @@ begin
     SQL_HANDLE_DBC,FDBCHandle,Format('Could not connect with connection string "%s".',[ConnectionString])
   );
 
-// commented out as the OutConenctionString is not used further at the moment
+// commented out as the OutConnectionString is not used further at the moment
 //  if ActualLength<BufferLength-1 then
 //    SetLength(OutConnectionString,ActualLength); // fix completed connection string length
 
@@ -520,6 +522,8 @@ begin
 end;
 
 function TODBCConnection.LoadField(cursor: TSQLCursor; FieldDef: TFieldDef; buffer: pointer): boolean;
+const
+  DEFAULT_BLOB_BUFFER_SIZE = 1024;
 var
   ODBCCursor:TODBCCursor;
   StrLenOrInd:SQLINTEGER;
@@ -527,6 +531,9 @@ var
   ODBCTimeStruct:SQL_TIME_STRUCT;
   ODBCTimeStampStruct:SQL_TIMESTAMP_STRUCT;
   DateTime:TDateTime;
+  BlobBuffer:pointer;
+  BlobBufferSize,BytesRead:SQLINTEGER;
+  BlobMemoryStream:TMemoryStream;
   Res:SQLRETURN;
 begin
   ODBCCursor:=cursor as TODBCCursor;
@@ -578,27 +585,91 @@ begin
       Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, buffer, FieldDef.Size, @StrLenOrInd);
     ftVarBytes:           // mapped to TVarBytesField
       Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, buffer, FieldDef.Size, @StrLenOrInd);
+    ftBlob, ftMemo:       // BLOBs
+    begin
+      // Try to discover BLOB data length
+      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, buffer, 0, @StrLenOrInd);
+      ODBCCheckResult(Res, SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, Format('Could not get field data for field ''%s'' (index %d).',[FieldDef.Name, FieldDef.Index+1]));
+      // Read the data if not NULL
+      if StrLenOrInd<>SQL_NULL_DATA then
+      begin
+        // Determine size of buffer to use
+        if StrLenOrInd<>SQL_NO_TOTAL then
+          BlobBufferSize:=StrLenOrInd
+        else
+          BlobBufferSize:=DEFAULT_BLOB_BUFFER_SIZE;
+        try
+          // init BlobBuffer and BlobMemoryStream to nil pointers
+          BlobBuffer:=nil;
+          BlobMemoryStream:=nil;
+          if BlobBufferSize>0 then // Note: zero-length BLOB is represented as nil pointer in the field buffer to save memory usage
+          begin
+            // Allocate the buffer and memorystream
+            BlobBuffer:=GetMem(BlobBufferSize);
+            BlobMemoryStream:=TMemoryStream.Create;
+            // Retrieve data in parts (or effectively in one part if StrLenOrInd<>SQL_NO_TOTAL above)
+            repeat
+              Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, BlobBuffer, BlobBufferSize, @StrLenOrInd);
+              ODBCCheckResult(Res, SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, Format('Could not get field data for field ''%s'' (index %d).',[FieldDef.Name, FieldDef.Index+1]));
+              // Append data in buffer to memorystream
+              if (StrLenOrInd=SQL_NO_TOTAL) or (StrLenOrInd>BlobBufferSize) then
+                BytesRead:=BlobBufferSize
+              else
+                BytesRead:=StrLenOrInd;
+              BlobMemoryStream.Write(BlobBuffer^, BytesRead);
+            until Res=SQL_SUCCESS;
+          end;
+          // Store memorystream pointer in Field buffer and in the cursor's FBlobStreams list
+          TObject(buffer^):=BlobMemoryStream;
+          if BlobMemoryStream<>nil then
+            ODBCCursor.FBlobStreams.Add(BlobMemoryStream);
+          // Set BlobMemoryStream to nil, so it won't get freed in the finally block below
+          BlobMemoryStream:=nil;
+        finally
+          BlobMemoryStream.Free;
+          if BlobBuffer<>nil then
+            Freemem(BlobBuffer,BlobBufferSize);
+        end;
+      end;
+    end;
     // TODO: Loading of other field types
   else
     raise EODBCException.CreateFmt('Tried to load field of unsupported field type %s',[Fieldtypenames[FieldDef.DataType]]);
   end;
-  ODBCCheckResult(Res,SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, Format('Could not get field data for field ''%s'' (index %d).',[FieldDef.Name, FieldDef.Index+1]));
+  ODBCCheckResult(Res, SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, Format('Could not get field data for field ''%s'' (index %d).',[FieldDef.Name, FieldDef.Index+1]));
   Result:=StrLenOrInd<>SQL_NULL_DATA; // Result indicates whether the value is non-null
 
 //  writeln(Format('Field.Size: %d; StrLenOrInd: %d',[FieldDef.Size, StrLenOrInd]));
 end;
 
 function TODBCConnection.CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream;
+var
+  ODBCCursor: TODBCCursor;
+  BlobMemoryStream, BlobMemoryStreamCopy: TMemoryStream;
 begin
-  // TODO: implement TODBCConnection.CreateBlobStream
-  Result:=nil;
+  if (Mode=bmRead) and not Field.IsNull then
+  begin
+    Field.GetData(@BlobMemoryStream);
+    BlobMemoryStreamCopy:=TMemoryStream.Create;
+    if BlobMemoryStream<>nil then
+      BlobMemoryStreamCopy.LoadFromStream(BlobMemoryStream);
+    Result:=BlobMemoryStreamCopy;
+  end
+  else
+    Result:=nil;
 end;
 
 procedure TODBCConnection.FreeFldBuffers(cursor: TSQLCursor);
 var
   ODBCCursor:TODBCCursor;
+  i: integer;
 begin
   ODBCCursor:=cursor as TODBCCursor;
+  
+  // Free TMemoryStreams in cursor.FBlobStreams and clear it
+  for i:=0 to ODBCCursor.FBlobStreams.Count-1 do
+    TObject(ODBCCursor.FBlobStreams[i]).Free;
+  ODBCCursor.FBlobStreams.Clear;
 
   ODBCCheckResult(
     SQLFreeStmt(ODBCCursor.FSTMTHandle, SQL_CLOSE),
@@ -608,14 +679,15 @@ end;
 
 procedure TODBCConnection.AddFieldDefs(cursor: TSQLCursor; FieldDefs: TFieldDefs);
 const
-  ColNameDefaultLength = 40; // should be > 0, because an ansistring of length 0 is a nil pointer instead of a pointer to a #0
+  ColNameDefaultLength  = 40; // should be > 0, because an ansistring of length 0 is a nil pointer instead of a pointer to a #0
+  TypeNameDefaultLength = 80; // idem
 var
   ODBCCursor:TODBCCursor;
   ColumnCount:SQLSMALLINT;
   i:integer;
-  ColNameLength,DataType,DecimalDigits,Nullable:SQLSMALLINT;
+  ColNameLength,TypeNameLength,DataType,DecimalDigits,Nullable:SQLSMALLINT;
   ColumnSize:SQLUINTEGER;
-  ColName:string;
+  ColName,TypeName:string;
   FieldType:TFieldType;
   FieldSize:word;
 begin
@@ -668,10 +740,10 @@ begin
     case DataType of
       SQL_CHAR:          begin FieldType:=ftFixedChar;  FieldSize:=ColumnSize+1; end;
       SQL_VARCHAR:       begin FieldType:=ftString;     FieldSize:=ColumnSize+1; end;
-      SQL_LONGVARCHAR:   begin FieldType:=ftString;     FieldSize:=ColumnSize+1; end; // no fixed maximum length; make ftMemo when blobs are supported
+      SQL_LONGVARCHAR:   begin FieldType:=ftMemo;       FieldSize:=sizeof(pointer); end; // is a blob
       SQL_WCHAR:         begin FieldType:=ftWideString; FieldSize:=ColumnSize+1; end;
       SQL_WVARCHAR:      begin FieldType:=ftWideString; FieldSize:=ColumnSize+1; end;
-      SQL_WLONGVARCHAR:  begin FieldType:=ftWideString; FieldSize:=ColumnSize+1; end; // no fixed maximum length; make ftMemo when blobs are supported
+      SQL_WLONGVARCHAR:  begin FieldType:=ftMemo;       FieldSize:=sizeof(pointer); end; // is a blob
       SQL_DECIMAL:       begin FieldType:=ftFloat;      FieldSize:=0; end;
       SQL_NUMERIC:       begin FieldType:=ftFloat;      FieldSize:=0; end;
       SQL_SMALLINT:      begin FieldType:=ftSmallint;   FieldSize:=0; end;
@@ -684,12 +756,12 @@ begin
       SQL_BIGINT:        begin FieldType:=ftLargeint;   FieldSize:=0; end;
       SQL_BINARY:        begin FieldType:=ftBytes;      FieldSize:=ColumnSize; end;
       SQL_VARBINARY:     begin FieldType:=ftVarBytes;   FieldSize:=ColumnSize; end;
-      SQL_LONGVARBINARY: begin FieldType:=ftBlob;       FieldSize:=ColumnSize; end;
+      SQL_LONGVARBINARY: begin FieldType:=ftBlob;       FieldSize:=sizeof(pointer); end; // is a blob
       SQL_TYPE_DATE:     begin FieldType:=ftDate;       FieldSize:=0; end;
       SQL_TYPE_TIME:     begin FieldType:=ftTime;       FieldSize:=0; end;
       SQL_TYPE_TIMESTAMP:begin FieldType:=ftDateTime;   FieldSize:=0; end;
 {      SQL_TYPE_UTCDATETIME:FieldType:=ftUnknown;}
-{      SQL_TYPE_UTCTIME:   FieldType:=ftUnknown; }
+{      SQL_TYPE_UTCTIME:    FieldType:=ftUnknown;}
 {      SQL_INTERVAL_MONTH:           FieldType:=ftUnknown;}
 {      SQL_INTERVAL_YEAR:            FieldType:=ftUnknown;}
 {      SQL_INTERVAL_YEAR_TO_MONTH:   FieldType:=ftUnknown;}
@@ -707,12 +779,48 @@ begin
     else
       begin FieldType:=ftUnknown; FieldSize:=ColumnSize; end
     end;
-    
+
     if (FieldType in [ftString,ftFixedChar]) and // field types mapped to TStringField
        (FieldSize >= dsMaxStringSize) then
     begin
       FieldSize:=dsMaxStringSize-1;
     end;
+    
+    if FieldType=ftUnknown then // if unknown field type encountered, try finding more specific information about the ODBC SQL DataType
+    begin
+      SetLength(TypeName,TypeNameDefaultLength); // also garantuees uniqueness
+      
+      ODBCCheckResult(
+        SQLColAttribute(ODBCCursor.FSTMTHandle,  // statement handle
+                        i,                       // column number
+                        SQL_DESC_TYPE_NAME,      // FieldIdentifier indicating the datasource dependent data type name (useful for diagnostics)
+                        @(TypeName[1]),          // default buffer
+                        TypeNameDefaultLength+1, // and its length; we include the #0 terminating any ansistring of Length > 0 in the buffer
+                        @TypeNameLength,         // actual type name length
+                        nil                      // no need for a pointer to return a numeric attribute at
+        ),
+        SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, Format('Could not get datasource dependent type name for column %s.',[ColName])
+      );
+      // truncate buffer or make buffer long enough for entire column name (note: the call is the same for both cases!)
+      SetLength(TypeName,TypeNameLength);
+      // check whether entire column name was returned
+      if TypeNameLength>TypeNameDefaultLength then
+      begin
+        // request column name with buffer that is long enough
+        ODBCCheckResult(
+          SQLColAttribute(ODBCCursor.FSTMTHandle, // statement handle
+                        i,                        // column number
+                        SQL_DESC_TYPE_NAME,       // FieldIdentifier indicating the datasource dependent data type name (useful for diagnostics)
+                        @(TypeName[1]),           // buffer
+                        TypeNameLength+1,         // buffer size
+                        @TypeNameLength,          // actual length
+                        nil),                     // no need for a pointer to return a numeric attribute at
+          SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, Format('Could not get datasource dependent type name for column %s.',[ColName])
+        );
+      end;
+
+      DatabaseErrorFmt('Column %s has an unknown or unsupported column type. Datasource dependent type name: %s. ODBC SQL data type code: %d.', [ColName, TypeName, DataType]);
+    end;
 
     // add FieldDef
     TFieldDef.Create(FieldDefs, ColName, FieldType, FieldSize, False, i);
@@ -773,6 +881,9 @@ begin
     SQLAllocHandle(SQL_HANDLE_STMT, Connection.FDBCHandle, FSTMTHandle),
     SQL_HANDLE_DBC, Connection.FDBCHandle, 'Could not allocate ODBC Statement handle.'
   );
+  
+  // allocate FBlobStreams
+  FBlobStreams:=TList.Create;
 end;
 
 destructor TODBCCursor.Destroy;
@@ -780,6 +891,8 @@ var
   Res:SQLRETURN;
 begin
   inherited Destroy;
+  
+  FBlobStreams.Free;
 
   if FSTMTHandle<>SQL_INVALID_HANDLE then
   begin

+ 34 - 61
fcl/db/sqldb/postgres/pqconnection.pp

@@ -24,8 +24,8 @@ type
     protected
     Statement : string;
     tr        : Pointer;
-    nFields   : integer;
     res       : PPGresult;
+    CurTuple  : integer;
     Nr        : string;
   end;
 
@@ -392,9 +392,7 @@ begin
     inc(FCursorCount);
     // Prior to v8 there is no support for cursors and parameters.
     // So that's not supported.
-    if FStatementType = stselect then
-      statement := 'DECLARE slctst' + name + nr +' BINARY CURSOR FOR ' + buf
-    else if FStatementType in [stInsert,stUpdate,stDelete] then
+    if FStatementType in [stInsert,stUpdate,stDelete, stSelect] then
       begin
       tr := aTransaction.Handle;
       // Only available for pq 8.0, so don't use it...
@@ -403,8 +401,9 @@ begin
       if Assigned(AParams) and (AParams.count > 0) then
         begin
         s := s + '(';
-        for i := 0 to AParams.count-1 do
-          s := s + TypeStrings[AParams[i].DataType] + ',';
+        for i := 0 to AParams.count-1 do if TypeStrings[AParams[i].DataType] <> 'Unknown' then
+          s := s + TypeStrings[AParams[i].DataType] + ','
+        else DatabaseErrorFmt(SUnsupportedParameter,[Fieldtypenames[AParams[i].DataType]],self);
         s[length(s)] := ')';
         buf := AParams.ParseSQL(buf,false,psPostgreSQL);
         end;
@@ -441,20 +440,7 @@ end;
 procedure TPQConnection.FreeFldBuffers(cursor : TSQLCursor);
 
 begin
-  with cursor as TPQCursor do
-   if (PQresultStatus(res) <> PGRES_FATAL_ERROR) then //Don't try to do anything if the transaction has already encountered an error.
-    begin
-    if FStatementType = stselect then
-      begin
-      Res := pqexec(tr,pchar('CLOSE slctst' + name + nr));
-      if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
-        begin
-        pqclear(res);
-        DatabaseError(SErrClearSelection + ' (PostgreSQL: ' + PQerrorMessage(tr) + ')',self)
-        end
-      end;
-    pqclear(res);
-    end;
+// Do nothing
 end;
 
 procedure TPQConnection.Execute(cursor: TSQLCursor;atransaction:tSQLtransaction;AParams : TParams);
@@ -466,12 +452,12 @@ var ar  : array of pointer;
 begin
   with cursor as TPQCursor do
     begin
-    if FStatementType in [stInsert,stUpdate,stDelete] then
+    if FStatementType in [stInsert,stUpdate,stDelete,stSelect] then
       begin
       if Assigned(AParams) and (AParams.count > 0) then
         begin
         setlength(ar,Aparams.count);
-        for i := 0 to AParams.count -1 do
+        for i := 0 to AParams.count -1 do if not AParams[i].IsNull then
           begin
           case AParams[i].DataType of
             ftdatetime : s := formatdatetime('YYYY-MM-DD',AParams[i].AsDateTime);
@@ -480,18 +466,15 @@ begin
           end; {case}
           GetMem(ar[i],length(s)+1);
           StrMove(PChar(ar[i]),Pchar(s),Length(S)+1);
-          end;
+          end
+        else
+          FreeAndNil(ar[i]);
         res := PQexecPrepared(tr,pchar('prepst'+nr),Aparams.count,@Ar[0],nil,nil,0);
         for i := 0 to AParams.count -1 do
           FreeMem(ar[i]);
         end
       else
-        res := PQexecPrepared(tr,pchar('prepst'+nr),0,nil,nil,nil,0);
-      if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
-        begin
-        pqclear(res);
-        DatabaseError(SErrExecuteFailed + ' (PostgreSQL: ' + PQerrorMessage(tr) + ')',self);
-        end;
+        res := PQexecPrepared(tr,pchar('prepst'+nr),0,nil,nil,nil,1);
       end
     else
       begin
@@ -502,11 +485,11 @@ begin
       if assigned(AParams) then for i := 0 to AParams.count-1 do
         s := stringreplace(s,':'+AParams[i].Name,AParams[i].asstring,[rfReplaceAll,rfIgnoreCase]);
       res := pqexec(tr,pchar(s));
-      if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
-        begin
-        pqclear(res);
-        DatabaseError(SErrExecuteFailed + ' (PostgreSQL: ' + PQerrorMessage(tr) + ')',self);
-        end;
+      end;
+    if not (PQresultStatus(res) in [PGRES_COMMAND_OK,PGRES_TUPLES_OK]) then
+      begin
+      pqclear(res);
+      DatabaseError(SErrExecuteFailed + ' (PostgreSQL: ' + PQerrorMessage(tr) + ')',self);
       end;
     end;
 end;
@@ -518,36 +501,28 @@ var
   size      : integer;
   st        : string;
   fieldtype : tfieldtype;
-  BaseRes   : PPGresult;
+  nFields   : integer;
 
 begin
   with cursor as TPQCursor do
     begin
-//    BaseRes := pqexecParams(tr,'FETCH 0 IN selectst' + pchar(name) ,0,nil,nil,nil,nil,1);
-    st := pchar('FETCH 0 IN slctst' + name+nr);
-    BaseRes := pqexec(tr,pchar(st));
-    if (PQresultStatus(BaseRes) <> PGRES_TUPLES_OK) then
-      begin
-      pqclear(BaseRes);
-      DatabaseError(SErrFieldDefsFailed + ' (PostgreSQL: ' + PQerrorMessage(tr) + ')',self)
-      end;
-    nFields := PQnfields(BaseRes);
+    nFields := PQnfields(Res);
     for i := 0 to nFields-1 do
       begin
-      size := PQfsize(BaseRes, i);
-      fieldtype := TranslateFldType(PQftype(BaseRes, i));
+      size := PQfsize(Res, i);
+      fieldtype := TranslateFldType(PQftype(Res, i));
 
       if (fieldtype = ftstring) and (size = -1) then
         begin
-        size := pqfmod(baseres,i)-3;
-        if size = -4 then size := dsMaxStringSize;
+        size := pqfmod(res,i)-4;
+        if size = -5 then size := dsMaxStringSize;
         end;
       if fieldtype = ftdate  then
         size := sizeof(double);
 
-      TFieldDef.Create(FieldDefs, PQfname(BaseRes, i), fieldtype,size, False, (i + 1));
+      TFieldDef.Create(FieldDefs, PQfname(Res, i), fieldtype,size, False, (i + 1));
       end;
-    pqclear(baseres);
+    CurTuple := -1;
     end;
 end;
 
@@ -563,14 +538,8 @@ var st : string;
 begin
   with cursor as TPQCursor do
     begin
-    st := pchar('FETCH NEXT IN slctst' + name+nr);
-    Res := pqexec(tr,pchar(st));
-    if (PQresultStatus(res) <> PGRES_TUPLES_OK) then
-      begin
-      pqclear(Res);
-      DatabaseError(SErrfetchFailed + ' (PostgreSQL: ' + PQerrorMessage(tr) + ')',self)
-      end;
-    Result := (PQntuples(res)<>0);
+    inc(CurTuple);
+    Result := (PQntuples(res)>CurTuple);
     end;
 end;
 
@@ -593,12 +562,12 @@ begin
     if PQfname(Res, x) <> FieldDef.Name then
       DatabaseErrorFmt(SFieldNotFound,[FieldDef.Name],self);
 
-    if pqgetisnull(res,0,x)=1 then
+    if pqgetisnull(res,CurTuple,x)=1 then
       result := false
     else
       begin
       i := PQfsize(res, x);
-      CurrBuff := pqgetvalue(res,0,x);
+      CurrBuff := pqgetvalue(res,CurTuple,x);
 
       case FieldDef.DataType of
         ftInteger, ftSmallint, ftLargeInt,ftfloat :
@@ -608,7 +577,7 @@ begin
           end;
         ftString  :
           begin
-          li := pqgetlength(res,0,x);
+          li := pqgetlength(res,curtuple,x);
           Move(CurrBuff^, Buffer^, li);
           pchar(Buffer + li)^ := #0;
           i := pqfmod(res,x)-3;
@@ -631,6 +600,10 @@ begin
             pchar(Buffer)[tel-1] := CurrBuff[i-tel];
 
           dbl^ := (dbl^+3.1558464E+009)/86400;  // postgres counts seconds elapsed since 1-1-2000
+          // Now convert the mathematically-correct datetime to the
+          // illogical windows/delphi/fpc TDateTime:
+          if (dbl^ <= 0) and (frac(dbl^)<0) then
+            dbl^ := trunc(dbl^)-2-frac(dbl^);
           end;
         ftBCD:
           begin

+ 97 - 31
fcl/db/sqldb/sqldb.pp

@@ -164,6 +164,9 @@ type
     FUpdateable          : boolean;
     FTableName           : string;
     FSQL                 : TStringList;
+    FUpdateSQL,
+    FInsertSQL,
+    FDeleteSQL           : TStringList;
     FIsEOF               : boolean;
     FLoadingFieldDefs    : boolean;
     FIndexDefs           : TIndexDefs;
@@ -179,6 +182,10 @@ type
     FMasterLink          : TMasterParamsDatalink;
 //    FSchemaInfo          : TSchemaInfo;
 
+    FUpdateQry,
+    FDeleteQry,
+    FInsertQry           : TSQLQuery;
+
     procedure FreeFldBuffers;
     procedure InitUpdates(ASQL : string);
     function GetIndexDefs : TIndexDefs;
@@ -189,7 +196,7 @@ type
     procedure SetUsePrimaryKeyAsKey(AValue : Boolean);
     procedure SetUpdateMode(AValue : TUpdateMode);
     procedure OnChangeSQL(Sender : TObject);
-
+    procedure OnChangeModifySQL(Sender : TObject);
     procedure Execute;
     Procedure SQLParser(var ASQL : string);
     procedure ApplyFilter;
@@ -258,6 +265,9 @@ type
     property Transaction;
     property ReadOnly : Boolean read FReadOnly write SetReadOnly;
     property SQL : TStringlist read FSQL write FSQL;
+    property UpdateSQL : TStringlist read FUpdateSQL write FUpdateSQL;
+    property InsertSQL : TStringlist read FInsertSQL write FInsertSQL;
+    property DeleteSQL : TStringlist read FDeleteSQL write FDeleteSQL;
     property IndexDefs : TIndexDefs read GetIndexDefs;
     property Params : TParams read FParams write FParams;
     property UpdateMode : TUpdateMode read FUpdateMode write SetUpdateMode;
@@ -541,10 +551,7 @@ end;
 { TSQLQuery }
 procedure TSQLQuery.OnChangeSQL(Sender : TObject);
 
-var s         : string;
-    i         : integer;
-    p         : pchar;
-    ParamName : String;
+var ParamName : String;
 
 begin
   UnPrepare;
@@ -556,6 +563,12 @@ begin
     end;
 end;
 
+procedure TSQLQuery.OnChangeModifySQL(Sender : TObject);
+
+begin
+  CheckInactive;
+end;
+
 Procedure TSQLQuery.SetTransaction(Value : TDBTransaction);
 
 begin
@@ -738,6 +751,9 @@ begin
   if DefaultFields then
     DestroyFields;
   FIsEOF := False;
+  if assigned(FUpdateQry) then FreeAndNil(FUpdateQry);
+  if assigned(FInsertQry) then FreeAndNil(FInsertQry);
+  if assigned(FDeleteQry) then FreeAndNil(FDeleteQry);
 //  FRecordSize := 0;
   inherited internalclose;
 end;
@@ -874,6 +890,20 @@ end;
 
 procedure TSQLQuery.InternalOpen;
 
+  procedure InitialiseModifyQuery(var qry : TSQLQuery; aSQL: TSTringList);
+  
+  begin
+    qry := TSQLQuery.Create(nil);
+    with qry do
+      begin
+      ParseSQL := False;
+      DataBase := Self.DataBase;
+      Transaction := Self.Transaction;
+      SQL.Assign(aSQL);
+      end;
+  end;
+
+
 var tel         : integer;
     f           : TField;
     s           : string;
@@ -888,20 +918,26 @@ begin
         begin
         CreateFields;
 
-        if FUpdateable and FusePrimaryKeyAsKey then
+        if FUpdateable then
           begin
-          UpdateIndexDefs;
-          for tel := 0 to indexdefs.count-1 do {with indexdefs[tel] do}
+          if FusePrimaryKeyAsKey then
             begin
-            if ixPrimary in indexdefs[tel].options then
+            UpdateIndexDefs;
+            for tel := 0 to indexdefs.count-1 do {with indexdefs[tel] do}
               begin
-              // Todo: If there is more then one field in the key, that must be parsed
-                s := indexdefs[tel].fields;
-                F := Findfield(s);
-                if F <> nil then
-                  F.ProviderFlags := F.ProviderFlags + [pfInKey];
+              if ixPrimary in indexdefs[tel].options then
+                begin
+                // Todo: If there is more then one field in the key, that must be parsed
+                  s := indexdefs[tel].fields;
+                  F := Findfield(s);
+                  if F <> nil then
+                    F.ProviderFlags := F.ProviderFlags + [pfInKey];
+                end;
               end;
             end;
+          InitialiseModifyQuery(FDeleteQry,FDeleteSQL);
+          InitialiseModifyQuery(FUpdateQry,FUpdateSQL);
+          InitialiseModifyQuery(FInsertQry,FInsertSQL);
           end;
         end;
       end
@@ -922,7 +958,7 @@ begin
     Prepare;
     Execute;
   finally
-    if not IsPrepared then (database as TSQLConnection).UnPrepareStatement(Fcursor);
+    if (not IsPrepared) and (assigned(database)) then (database as TSQLConnection).UnPrepareStatement(Fcursor);
   end;
 end;
 
@@ -932,6 +968,14 @@ begin
   FParams := TParams.create(self);
   FSQL := TStringList.Create;
   FSQL.OnChange := @OnChangeSQL;
+
+  FUpdateSQL := TStringList.Create;
+  FUpdateSQL.OnChange := @OnChangeModifySQL;
+  FInsertSQL := TStringList.Create;
+  FInsertSQL.OnChange := @OnChangeModifySQL;
+  FDeleteSQL := TStringList.Create;
+  FDeleteSQL.OnChange := @OnChangeModifySQL;
+
   FIndexDefs := TIndexDefs.Create(Self);
   FReadOnly := false;
   FParseSQL := True;
@@ -949,6 +993,9 @@ begin
   FreeAndNil(FMasterLink);
   FreeAndNil(FParams);
   FreeAndNil(FSQL);
+  FreeAndNil(FInsertSQL);
+  FreeAndNil(FDeleteSQL);
+  FreeAndNil(FUpdateSQL);
   FreeAndNil(FIndexDefs);
   inherited Destroy;
 end;
@@ -1008,11 +1055,7 @@ var
     if (pfInKey in Fields[x].ProviderFlags) or
        ((FUpdateMode = upWhereAll) and (pfInWhere in Fields[x].ProviderFlags)) or
        ((FUpdateMode = UpWhereChanged) and (pfInWhere in Fields[x].ProviderFlags) and (fields[x].value <> fields[x].oldvalue)) then
-      begin
-      // This should be converted to something like GetAsSQLText, but better wait until variants (oldvalue) are working for strings
-      s := fields[x].oldvalue; // This directly int the line below raises a variant-error
-      sql_where := sql_where + '(' + fields[x].FieldName + '=' + s + ') and ';
-      end;
+      sql_where := sql_where + '(' + fields[x].FieldName + '= :OLD_' + fields[x].FieldName + ') and ';
   end;
 
   function ModifyRecQuery : string;
@@ -1029,10 +1072,7 @@ var
       UpdateWherePart(sql_where,x);
 
       if (pfInUpdate in Fields[x].ProviderFlags) then
-        if fields[x].IsNull then // check for null
-          sql_set := sql_set + fields[x].FieldName + '=' + (Database as TSQLConnection).GetAsSQLText(nil) + ','
-        else
-          sql_set := sql_set + fields[x].FieldName + '=' + (Database as TSQLConnection).GetAsSQLText(fields[x]) + ',';
+        sql_set := sql_set + fields[x].FieldName + '=:' + fields[x].FieldName + ',';
       end;
 
     setlength(sql_set,length(sql_set)-1);
@@ -1054,8 +1094,8 @@ var
       begin
       if not fields[x].IsNull then
         begin
-        sql_fields := sql_fields + fields[x].DisplayName + ',';
-        sql_values := sql_values + (Database as TSQLConnection).GetAsSQLText(fields[x]) + ',';
+        sql_fields := sql_fields + fields[x].FieldName + ',';
+        sql_values := sql_values + ':' + fields[x].FieldName + ',';
         end;
       end;
     setlength(sql_fields,length(sql_fields)-1);
@@ -1079,15 +1119,41 @@ var
     result := 'delete from ' + FTableName + ' where ' + sql_where;
   end;
 
+var qry : tsqlquery;
+    x   : integer;
+    Fld : TField;
+    
 begin
   Result := True;
     case UpdateKind of
-      ukModify : s := ModifyRecQuery;
-      ukInsert : s := InsertRecQuery;
-      ukDelete : s := DeleteRecQuery;
-    end; {case}
+      ukModify : begin
+                 qry := FUpdateQry;
+                 if trim(qry.sql.Text) = '' then qry.SQL.Add(ModifyRecQuery);
+                 end;
+      ukInsert : begin
+                 qry := FInsertQry;
+                 if trim(qry.sql.Text) = '' then qry.SQL.Add(InsertRecQuery);
+                 end;
+      ukDelete : begin
+                 qry := FDeleteQry;
+                 if trim(qry.sql.Text) = '' then qry.SQL.Add(DeleteRecQuery);
+                 end;
+    end;
   try
-    (Database as TSQLConnection).ExecuteDirect(s,Transaction as TSQLTransaction);
+  with qry do
+    begin
+    for x := 0 to Params.Count-1 do with params[x] do if leftstr(name,4)='OLD_' then
+      begin
+      Fld := self.FieldByName(copy(name,5,length(name)-4));
+      AssignFieldValue(Fld,Fld.OldValue);
+      end
+    else
+      begin
+      Fld := self.FieldByName(name);
+      AssignFieldValue(Fld,Fld.Value);
+      end;
+    execsql;
+    end;
   except
     on EDatabaseError do Result := False
   else

+ 32 - 4
packages/base/odbc/odbcsql.inc

@@ -964,10 +964,38 @@ const
 {$ifdef ODBCVER3}
   SQL_COLUMN_DRIVER_START        = 1000;
 {$endif} { ODBCVER >= 0x0300 }
-  SQL_DESC_AUTO_UNIQUE_VALUE     = SQL_COLUMN_AUTO_INCREMENT;
-  SQL_DESC_BASE_COLUMN_NAME        = 22;
-  SQL_DESC_BASE_TABLE_NAME         = 23;
-  SQL_DESC_TABLE_NAME              = SQL_COLUMN_TABLE_NAME;
+
+ { SQLColAttribute defines }
+{$ifdef ODBCVER3}
+  SQL_DESC_ARRAY_SIZE	     = 20;
+  SQL_DESC_ARRAY_STATUS_PTR  = 21;
+  SQL_DESC_AUTO_UNIQUE_VALUE = SQL_COLUMN_AUTO_INCREMENT;
+  SQL_DESC_BASE_COLUMN_NAME  = 22;
+  SQL_DESC_BASE_TABLE_NAME   = 23;
+  SQL_DESC_BIND_OFFSET_PTR   = 24;
+  SQL_DESC_BIND_TYPE         = 25;
+  SQL_DESC_CASE_SENSITIVE    = SQL_COLUMN_CASE_SENSITIVE;
+  SQL_DESC_CATALOG_NAME      = SQL_COLUMN_QUALIFIER_NAME;
+  SQL_DESC_CONCISE_TYPE      = SQL_COLUMN_TYPE;
+  SQL_DESC_DATETIME_INTERVAL_PRECISION = 26;
+  SQL_DESC_DISPLAY_SIZE      = SQL_COLUMN_DISPLAY_SIZE;
+  SQL_DESC_FIXED_PREC_SCALE  = SQL_COLUMN_MONEY;
+  SQL_DESC_LABEL             = SQL_COLUMN_LABEL;
+  SQL_DESC_LITERAL_PREFIX    = 27;
+  SQL_DESC_LITERAL_SUFFIX    = 28;
+  SQL_DESC_LOCAL_TYPE_NAME   = 29;
+  SQL_DESC_MAXIMUM_SCALE     = 30;
+  SQL_DESC_MINIMUM_SCALE     = 31;
+  SQL_DESC_NUM_PREC_RADIX    = 32;
+  SQL_DESC_PARAMETER_TYPE    = 33;
+  SQL_DESC_ROWS_PROCESSED_PTR = 34;
+  SQL_DESC_SCHEMA_NAME       = SQL_COLUMN_OWNER_NAME;
+  SQL_DESC_SEARCHABLE        = SQL_COLUMN_SEARCHABLE;
+  SQL_DESC_TYPE_NAME         = SQL_COLUMN_TYPE_NAME;
+  SQL_DESC_TABLE_NAME        = SQL_COLUMN_TABLE_NAME;
+  SQL_DESC_UNSIGNED          = SQL_COLUMN_UNSIGNED;
+  SQL_DESC_UPDATABLE         = SQL_COLUMN_UPDATABLE;
+{$endif}
 
 //* SQLEndTran() options */
   SQL_COMMIT    = 0;