Browse Source

--- Merging r19247 into '.':
U packages/fcl-db/src/sqlite/sqliteds.pas
U packages/fcl-db/src/sqlite/sqlite3ds.pas
--- Merging r19251 into '.':
U packages/fcl-db/src/sqldb/oracle/oracleconnection.pp
--- Merging r19267 into '.':
U packages/fcl-db/tests/testbufdatasetstreams.pas
A packages/fcl-db/tests/testwherenull.lpr
U packages/fcl-db/src/sqldb/sqldb.pp
--- Merging r19271 into '.':
G packages/fcl-db/src/sqldb/oracle/oracleconnection.pp

# revisions: 19247,19251,19267,19271
------------------------------------------------------------------------
r19247 | blikblum | 2011-09-26 20:32:07 +0200 (Mon, 26 Sep 2011) | 1 line
Changed paths:
M /trunk/packages/fcl-db/src/sqlite/sqlite3ds.pas
M /trunk/packages/fcl-db/src/sqlite/sqliteds.pas

* fix retrieving AutoInc value when index of autoinc field differs in Fields and in fielddefs
------------------------------------------------------------------------
------------------------------------------------------------------------
r19251 | marco | 2011-09-27 12:49:35 +0200 (Tue, 27 Sep 2011) | 2 lines
Changed paths:
M /trunk/packages/fcl-db/src/sqldb/oracle/oracleconnection.pp

* fix for mantis #18149, memoryleaks as result from change of stmtprepare to ..stmtprepare2

------------------------------------------------------------------------
------------------------------------------------------------------------
r19267 | marco | 2011-09-28 18:27:32 +0200 (Wed, 28 Sep 2011) | 2 lines
Changed paths:
M /trunk/packages/fcl-db/src/sqldb/sqldb.pp
M /trunk/packages/fcl-db/tests/testbufdatasetstreams.pas
A /trunk/packages/fcl-db/tests/testwherenull.lpr

* fixes Mantis #14730 dealing with oldvalue=null

------------------------------------------------------------------------
------------------------------------------------------------------------
r19271 | marco | 2011-09-28 21:26:00 +0200 (Wed, 28 Sep 2011) | 2 lines
Changed paths:
M /trunk/packages/fcl-db/src/sqldb/oracle/oracleconnection.pp

* "out" parameters for Oracle, Mantis #18133

------------------------------------------------------------------------

git-svn-id: branches/fixes_2_6@19295 -

marco 14 years ago
parent
commit
e2d6ceb882

+ 1 - 0
.gitattributes

@@ -1995,6 +1995,7 @@ packages/fcl-db/tests/testsqlfiles.lpr svneol=native#text/plain
 packages/fcl-db/tests/testsqlscanner.lpi svneol=native#text/plain
 packages/fcl-db/tests/testsqlscanner.lpi svneol=native#text/plain
 packages/fcl-db/tests/testsqlscanner.lpr svneol=native#text/plain
 packages/fcl-db/tests/testsqlscanner.lpr svneol=native#text/plain
 packages/fcl-db/tests/testsqlscript.pas svneol=native#text/plain
 packages/fcl-db/tests/testsqlscript.pas svneol=native#text/plain
+packages/fcl-db/tests/testwherenull.lpr svneol=native#text/plain
 packages/fcl-db/tests/toolsunit.pas svneol=native#text/plain
 packages/fcl-db/tests/toolsunit.pas svneol=native#text/plain
 packages/fcl-db/tests/xmlxsdexporttestcase1.pas svneol=native#text/plain
 packages/fcl-db/tests/xmlxsdexporttestcase1.pas svneol=native#text/plain
 packages/fcl-extra/Makefile svneol=native#text/plain
 packages/fcl-extra/Makefile svneol=native#text/plain

+ 126 - 36
packages/fcl-db/src/sqldb/oracle/oracleconnection.pp

@@ -1,4 +1,8 @@
-unit OracleConnection;
+unit oracleconnection;
+//
+// For usage of "returning" like clauses see mantis #18133
+//
+
 
 
 {$mode objfpc}{$H+}
 {$mode objfpc}{$H+}
 
 
@@ -36,6 +40,7 @@ type
   TOraFieldBuf = record
   TOraFieldBuf = record
     Buffer : pointer;
     Buffer : pointer;
     Ind    : sb2;
     Ind    : sb2;
+    Len    : ub4;
   end;
   end;
 
 
   TOracleCursor = Class(TSQLCursor)
   TOracleCursor = Class(TSQLCursor)
@@ -55,6 +60,7 @@ type
     FOciUserSession : POCISession;
     FOciUserSession : POCISession;
     FUserMem        : pointer;
     FUserMem        : pointer;
     procedure HandleError;
     procedure HandleError;
+    procedure GetParameters(cursor : TSQLCursor;AParams : TParams);
     procedure SetParameters(cursor : TSQLCursor;AParams : TParams);
     procedure SetParameters(cursor : TSQLCursor;AParams : TParams);
   protected
   protected
     // - Connect/disconnect
     // - Connect/disconnect
@@ -105,6 +111,33 @@ ResourceString
   SErrHandleAllocFailed = 'The allocation of the error handle failed.';
   SErrHandleAllocFailed = 'The allocation of the error handle failed.';
   SErrOracle = 'Oracle returned error %s:';
   SErrOracle = 'Oracle returned error %s:';
 
 
+//callback functions
+
+function cbf_no_data(ictxp:Pdvoid; bindp:POCIBind; iter:ub4; index:ub4; bufpp:PPdvoid;
+             alenp:Pub4; piecep:Pub1; indp:PPdvoid):sb4;cdecl;
+
+begin
+  bufpp^ := nil;
+  alenp^ := 0;
+  indp^ := nil;
+  piecep^ := OCI_ONE_PIECE;
+  result:=OCI_CONTINUE;
+end;
+
+
+function cbf_get_data(octxp:Pdvoid; bindp:POCIBind; iter:ub4; index:ub4; bufpp:PPdvoid;
+             alenp:PPub4; piecep:Pub1; indp:PPdvoid; rcodep:PPub2):sb4;cdecl;
+
+begin
+//only 1 row can be stored. No support for multiple rows. When multiple rows, only last is kept.
+  bufpp^:=TOraFieldBuf(octxp^).Buffer;
+  indp^ := @TOraFieldBuf(octxp^).Ind;
+  alenp^ := @TOraFieldBuf(octxp^).Len;
+  rcodep^:=nil;
+  piecep^ := OCI_ONE_PIECE;
+  result:=OCI_CONTINUE;
+end;
+
 procedure TOracleConnection.HandleError;
 procedure TOracleConnection.HandleError;
 
 
 var errcode : sb4;
 var errcode : sb4;
@@ -123,6 +156,51 @@ begin
   Raise E;
   Raise E;
 end;
 end;
 
 
+procedure TOracleConnection.GetParameters(cursor: TSQLCursor; AParams: TParams
+  );
+var SQLVarNr       : integer;
+    i              : integer;
+    f              : double;
+    year,month,day : word;
+    db             : array[0..4] of byte;
+    pb             : pbyte;
+    s              : string;
+
+begin
+  with cursor as TOracleCursor do for SQLVarNr := 0 to High(ParamBuffers) do
+    with AParams[SQLVarNr] do
+      if ParamType=ptOutput then
+      begin
+      if parambuffers[SQLVarNr].ind = -1 then
+        Value:=null;
+
+      case DataType of
+        ftInteger         : begin
+                            move(parambuffers[SQLVarNr].buffer^,i,sizeof(integer));
+                            asInteger := i;
+                            end;
+        ftFloat           : begin
+                            move(parambuffers[SQLVarNr].buffer^,f,sizeof(double));
+                            asFloat := f;
+                            end;
+        ftString          : begin
+                            SetLength(s,parambuffers[SQLVarNr].Len);
+                            move(parambuffers[SQLVarNr].buffer^,s[1],length(s)+1);
+                            asString:=s;
+                            end;
+        ftDate, ftDateTime: begin
+                            pb := parambuffers[SQLVarNr].buffer;
+                            year:=(pb[0]-100)*100+pb[1]-100;
+                            month:=pb[2];
+                            day:=pb[3];
+                            asDateTime:=EncodeDate(year,month,day);
+                            end;
+      end;
+
+      end;
+
+end;
+
 procedure TOracleConnection.DoInternalConnect;
 procedure TOracleConnection.DoInternalConnect;
 
 
 var
 var
@@ -276,7 +354,6 @@ var tel      : integer;
 begin
 begin
   with cursor as TOracleCursor do
   with cursor as TOracleCursor do
     begin
     begin
-    OciHandleAlloc(FOciEnvironment,FOciStmt,OCI_HTYPE_STMT,0,FUserMem);
     if OCIStmtPrepare2(TOracleTrans(ATransaction.Handle).FOciSvcCtx,FOciStmt,FOciError,@buf[1],length(buf),nil,0,OCI_NTV_SYNTAX,OCI_DEFAULT) = OCI_ERROR then
     if OCIStmtPrepare2(TOracleTrans(ATransaction.Handle).FOciSvcCtx,FOciStmt,FOciError,@buf[1],length(buf),nil,0,OCI_NTV_SYNTAX,OCI_DEFAULT) = OCI_ERROR then
       HandleError;
       HandleError;
     if assigned(AParams) then
     if assigned(AParams) then
@@ -293,13 +370,23 @@ begin
 
 
         end;
         end;
         parambuffers[tel].buffer := getmem(OFieldSize);
         parambuffers[tel].buffer := getmem(OFieldSize);
+        parambuffers[tel].len := OFieldSize;
 
 
 
 
         FOciBind := nil;
         FOciBind := nil;
 
 
-        if OCIBindByName(FOciStmt,FOcibind,FOciError,pchar(AParams[tel].Name),length(AParams[tel].Name),ParamBuffers[tel].buffer,OFieldSize,OFieldType,@ParamBuffers[tel].ind,nil,nil,0,nil,OCI_DEFAULT )= OCI_ERROR then
-          HandleError;
-
+        if AParams[tel].ParamType=ptInput then
+          begin
+          if OCIBindByName(FOciStmt,FOcibind,FOciError,pchar(AParams[tel].Name),length(AParams[tel].Name),ParamBuffers[tel].buffer,OFieldSize,OFieldType,@ParamBuffers[tel].ind,nil,nil,0,nil,OCI_DEFAULT )= OCI_ERROR then
+            HandleError;
+          end
+        else if AParams[tel].ParamType=ptOutput then
+          begin
+          if OCIBindByName(FOciStmt,FOcibind,FOciError,pchar(AParams[tel].Name),length(AParams[tel].Name),nil,OFieldSize,OFieldType,nil,nil,nil,0,nil,OCI_DATA_AT_EXEC )= OCI_ERROR then
+            HandleError;
+          if OCIBindDynamic(FOcibind, FOciError, nil, @cbf_no_data, @parambuffers[tel], @cbf_get_data) <> OCI_SUCCESS then
+            HandleError;
+          end;
         end;
         end;
       end;
       end;
     FPrepared := True;
     FPrepared := True;
@@ -318,43 +405,45 @@ var SQLVarNr       : integer;
 
 
 begin
 begin
   with cursor as TOracleCursor do for SQLVarNr := 0 to High(ParamBuffers) do with AParams[SQLVarNr] do
   with cursor as TOracleCursor do for SQLVarNr := 0 to High(ParamBuffers) do with AParams[SQLVarNr] do
-    begin
-    if IsNull then parambuffers[SQLVarNr].ind := -1 else
-      parambuffers[SQLVarNr].ind := 0;
-
-    case DataType of
-      ftInteger         : begin
-                          i := asInteger;
-                          move(i,parambuffers[SQLVarNr].buffer^,sizeof(integer));
-                          end;
-      ftFloat           : begin
-                          f := asFloat;
-                          move(f,parambuffers[SQLVarNr].buffer^,sizeof(double));
-                          end;
-      ftString          : begin
-                          s := asString+#0;
-                          move(s[1],parambuffers[SQLVarNr].buffer^,length(s)+1);
-                          end;
-      ftDate, ftDateTime: begin
-                          DecodeDate(asDateTime,year,month,day);
-                          pb := parambuffers[SQLVarNr].buffer;
-                          pb[0] := (year div 100)+100;
-                          pb[1] := (year mod 100)+100;
-                          pb[2] := month;
-                          pb[3] := day;
-                          pb[4] := 1;
-                          pb[5] := 1;
-                          pb[6] := 1;
-                          end;
-    end;
+    if ParamType=ptInput then
+      begin
+      if IsNull then parambuffers[SQLVarNr].ind := -1 else
+        parambuffers[SQLVarNr].ind := 0;
 
 
-    end;
+      case DataType of
+        ftInteger         : begin
+                            i := asInteger;
+                            move(i,parambuffers[SQLVarNr].buffer^,sizeof(integer));
+                            end;
+        ftFloat           : begin
+                            f := asFloat;
+                            move(f,parambuffers[SQLVarNr].buffer^,sizeof(double));
+                            end;
+        ftString          : begin
+                            s := asString+#0;
+                            move(s[1],parambuffers[SQLVarNr].buffer^,length(s)+1);
+                            end;
+        ftDate, ftDateTime: begin
+                            DecodeDate(asDateTime,year,month,day);
+                            pb := parambuffers[SQLVarNr].buffer;
+                            pb[0] := (year div 100)+100;
+                            pb[1] := (year mod 100)+100;
+                            pb[2] := month;
+                            pb[3] := day;
+                            pb[4] := 1;
+                            pb[5] := 1;
+                            pb[6] := 1;
+                            end;
+      end;
+
+      end;
 
 
 end;
 end;
 
 
 procedure TOracleConnection.UnPrepareStatement(cursor: TSQLCursor);
 procedure TOracleConnection.UnPrepareStatement(cursor: TSQLCursor);
 begin
 begin
-  OCIHandleFree(TOracleCursor(cursor).FOciStmt,OCI_HTYPE_STMT);
+  if OCIStmtRelease(TOracleCursor(cursor).FOciStmt,FOciError,nil,0,OCI_DEFAULT)<> OCI_SUCCESS then
+    HandleError();
   cursor.FPrepared:=False;
   cursor.FPrepared:=False;
 end;
 end;
 
 
@@ -437,6 +526,7 @@ begin
     begin
     begin
     if OCIStmtExecute(TOracleTrans(ATransaction.Handle).FOciSvcCtx,(cursor as TOracleCursor).FOciStmt,FOciError,1,0,nil,nil,OCI_DEFAULT) = OCI_ERROR then
     if OCIStmtExecute(TOracleTrans(ATransaction.Handle).FOciSvcCtx,(cursor as TOracleCursor).FOciStmt,FOciError,1,0,nil,nil,OCI_DEFAULT) = OCI_ERROR then
       HandleError;
       HandleError;
+    if Assigned(APArams) and (AParams.count > 0) then GetParameters(cursor, AParams);
     end;
     end;
 end;
 end;
 
 

+ 29 - 37
packages/fcl-db/src/sqldb/sqldb.pp

@@ -1544,17 +1544,17 @@ Procedure TCustomSQLQuery.ApplyRecUpdate(UpdateKind : TUpdateKind);
 
 
 var FieldNamesQuoteChars : TQuoteChars;
 var FieldNamesQuoteChars : TQuoteChars;
 
 
-  procedure InitialiseModifyQuery(var qry : TCustomSQLQuery; aSQL: String);
+  function InitialiseModifyQuery(var qry : TCustomSQLQuery): TCustomSQLQuery;
 
 
   begin
   begin
-    qry := TCustomSQLQuery.Create(nil);
-    with qry do
-      begin
-      ParseSQL := False;
-      DataBase := Self.DataBase;
-      Transaction := Self.Transaction;
-      SQL.text := aSQL;
-      end;
+    if not assigned(qry) then
+    begin
+      qry := TCustomSQLQuery.Create(nil);
+      qry.ParseSQL := False;
+      qry.DataBase := Self.DataBase;
+      qry.Transaction := Self.Transaction;
+    end;
+    Result:=qry;
   end;
   end;
 
 
   procedure UpdateWherePart(var sql_where : string;x : integer);
   procedure UpdateWherePart(var sql_where : string;x : integer);
@@ -1562,8 +1562,11 @@ var FieldNamesQuoteChars : TQuoteChars;
   begin
   begin
     if (pfInKey in Fields[x].ProviderFlags) or
     if (pfInKey in Fields[x].ProviderFlags) or
        ((FUpdateMode = upWhereAll) and (pfInWhere 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
-      sql_where := sql_where + '(' + FieldNamesQuoteChars[0] + fields[x].FieldName + FieldNamesQuoteChars[1] + '= :"' + 'OLD_' + fields[x].FieldName + '") and ';
+       ((FUpdateMode = UpWhereChanged) and (pfInWhere in Fields[x].ProviderFlags) and (Fields[x].Value <> Fields[x].OldValue)) then
+       if Fields[x].OldValue = NULL then
+          sql_where := sql_where + FieldNamesQuoteChars[0] + Fields[x].FieldName + FieldNamesQuoteChars[1] + ' is null and '
+       else
+          sql_where := sql_where + '(' + FieldNamesQuoteChars[0] + Fields[x].FieldName + FieldNamesQuoteChars[1] + '= :"' + 'OLD_' + Fields[x].FieldName + '") and ';
   end;
   end;
 
 
   function ModifyRecQuery : string;
   function ModifyRecQuery : string;
@@ -1632,6 +1635,7 @@ var FieldNamesQuoteChars : TQuoteChars;
   end;
   end;
 
 
 var qry : TCustomSQLQuery;
 var qry : TCustomSQLQuery;
+    s   : string;
     x   : integer;
     x   : integer;
     Fld : TField;
     Fld : TField;
 
 
@@ -1639,37 +1643,25 @@ begin
   FieldNamesQuoteChars := TSQLConnection(DataBase).FieldNameQuoteChars;
   FieldNamesQuoteChars := TSQLConnection(DataBase).FieldNameQuoteChars;
 
 
   case UpdateKind of
   case UpdateKind of
-    ukModify : begin
-               if not assigned(FUpdateQry) then
-                 begin
-                 if (trim(FUpdateSQL.Text)<> '') then
-                   InitialiseModifyQuery(FUpdateQry,FUpdateSQL.Text)
-                 else
-                   InitialiseModifyQuery(FUpdateQry,ModifyRecQuery);
-                 end;
-               qry := FUpdateQry;
-               end;
     ukInsert : begin
     ukInsert : begin
-               if not assigned(FInsertQry) then
-                 begin
-                 if (trim(FInsertSQL.Text)<> '') then
-                   InitialiseModifyQuery(FInsertQry,FInsertSQL.Text)
-                 else
-                   InitialiseModifyQuery(FInsertQry,InsertRecQuery);
-                 end;
-               qry := FInsertQry;
+               s := trim(FInsertSQL.Text);
+               if s = '' then s := InsertRecQuery;
+               qry := InitialiseModifyQuery(FInsertQry);
+               end;
+    ukModify : begin
+               s := trim(FUpdateSQL.Text);
+               if (s='') and (not assigned(FUpdateQry) or (UpdateMode<>upWhereKeyOnly)) then //first time or dynamic where part
+                 s := ModifyRecQuery;
+               qry := InitialiseModifyQuery(FUpdateQry);
                end;
                end;
     ukDelete : begin
     ukDelete : begin
-               if not assigned(FDeleteQry) then
-                 begin
-                 if (trim(FDeleteSQL.Text)<> '') then
-                   InitialiseModifyQuery(FDeleteQry,FDeleteSQL.Text)
-                 else
-                   InitialiseModifyQuery(FDeleteQry,DeleteRecQuery);
-                 end;
-               qry := FDeleteQry;
+               s := trim(FDeleteSQL.Text);
+               if (s='') and (not assigned(FDeleteQry) or (UpdateMode<>upWhereKeyOnly)) then
+                 s := DeleteRecQuery;
+               qry := InitialiseModifyQuery(FDeleteQry);
                end;
                end;
   end;
   end;
+  if (qry.SQL.Text<>s) and (s<>'') then qry.SQL.Text:=s; //assign only when changed, to avoid UnPrepare/Prepare
   assert(qry.sql.Text<>'');
   assert(qry.sql.Text<>'');
   with qry do
   with qry do
     begin
     begin

+ 1 - 1
packages/fcl-db/src/sqlite/sqlite3ds.pas

@@ -278,7 +278,7 @@ var
 begin
 begin
   //Get AutoInc Field initial value
   //Get AutoInc Field initial value
   if FAutoIncFieldNo <> -1 then
   if FAutoIncFieldNo <> -1 then
-    sqlite3_exec(FSqliteHandle, PChar('Select Max(' + Fields[FAutoIncFieldNo].FieldName +
+    sqlite3_exec(FSqliteHandle, PChar('Select Max(' + FieldDefs[FAutoIncFieldNo].Name +
       ') from ' + FTableName), @GetAutoIncValue, @FNextAutoInc, nil);
       ') from ' + FTableName), @GetAutoIncValue, @FNextAutoInc, nil);
 
 
   FReturnCode := sqlite3_prepare(FSqliteHandle, PChar(FEffectiveSQL), -1, @vm, nil);
   FReturnCode := sqlite3_prepare(FSqliteHandle, PChar(FEffectiveSQL), -1, @vm, nil);

+ 1 - 1
packages/fcl-db/src/sqlite/sqliteds.pas

@@ -228,7 +228,7 @@ var
 begin
 begin
   //Get AutoInc Field initial value
   //Get AutoInc Field initial value
   if FAutoIncFieldNo <> -1 then
   if FAutoIncFieldNo <> -1 then
-    sqlite_exec(FSqliteHandle, PChar('Select Max(' + Fields[FAutoIncFieldNo].FieldName + ') from ' + FTableName),
+    sqlite_exec(FSqliteHandle, PChar('Select Max(' + FieldDefs[FAutoIncFieldNo].Name + ') from ' + FTableName),
       @GetAutoIncValue, @FNextAutoInc, nil);
       @GetAutoIncValue, @FNextAutoInc, nil);
 
 
   FReturnCode := sqlite_compile(FSqliteHandle, PChar(FEffectiveSQL), nil, @vm, nil);
   FReturnCode := sqlite_compile(FSqliteHandle, PChar(FEffectiveSQL), nil, @vm, nil);

+ 55 - 4
packages/fcl-db/tests/testbufdatasetstreams.pas

@@ -32,6 +32,8 @@ type
     procedure SeveralEditsChange(ADataset: TCustomBufDataset);
     procedure SeveralEditsChange(ADataset: TCustomBufDataset);
     procedure DeleteAllChange(ADataset: TCustomBufDataset);
     procedure DeleteAllChange(ADataset: TCustomBufDataset);
     procedure DeleteAllInsertChange(ADataset: TCustomBufDataset);
     procedure DeleteAllInsertChange(ADataset: TCustomBufDataset);
+    procedure NullInsertChange(ADataset: TCustomBufDataset);
+    procedure NullEditChange(ADataset: TCustomBufDataset);
   protected
   protected
     procedure SetUp; override;
     procedure SetUp; override;
     procedure TearDown; override;
     procedure TearDown; override;
@@ -53,6 +55,7 @@ type
     procedure SeveralEditsApplUpd;
     procedure SeveralEditsApplUpd;
     procedure DeleteAllApplUpd;
     procedure DeleteAllApplUpd;
     procedure DeleteAllInsertApplUpd;
     procedure DeleteAllInsertApplUpd;
+    procedure NullInsertUpdateApplUpd;
 
 
     procedure TestBasicsXML;
     procedure TestBasicsXML;
     procedure TestSimpleEditXML;
     procedure TestSimpleEditXML;
@@ -99,6 +102,7 @@ begin
   if Inserts then
   if Inserts then
     begin
     begin
     ChangedDs := TSQLDBConnector(DBConnector).Query;
     ChangedDs := TSQLDBConnector(DBConnector).Query;
+    ChangedDs.Close;
     TSQLQuery(ChangedDS).SQL.Text:='SELECT * FROM FPDEV WHERE (ID < 16) or (ID>100) ORDER BY ID';
     TSQLQuery(ChangedDS).SQL.Text:='SELECT * FROM FPDEV WHERE (ID < 16) or (ID>100) ORDER BY ID';
 
 
     OrgDs.IndexFieldNames:='ID';
     OrgDs.IndexFieldNames:='ID';
@@ -274,14 +278,43 @@ begin
     end;
     end;
 end;
 end;
 
 
-procedure TTestBufDatasetStreams.SetUp;
+procedure TTestBufDatasetStreams.NullInsertChange(ADataset: TCustomBufDataset);
 begin
 begin
-  DBConnector.StartTest;
+  with ADataset do
+  begin
+    AssertTrue(Locate('ID',11,[]));
+    Delete; //11
+    Delete; //12
+    Delete; //13
+    Delete; //14
+    Append;
+    FieldByName('ID').AsInteger:=11;
+    //FieldByName('NAME').Clear;
+    Post;
+    AppendRecord([12,'AfterNull']);
+    AppendRecord([13,null]);
+    AppendRecord([14,'AfterNull']);
+    //Append; Post;
+  end;
 end;
 end;
 
 
-procedure TTestBufDatasetStreams.TearDown;
+procedure TTestBufDatasetStreams.NullEditChange(ADataset: TCustomBufDataset);
+var i: integer;
 begin
 begin
-  DBConnector.StopTest;
+  //depends on procedure TTestBufDatasetStreams.NullInsertChange
+  if ADataSet is TSQLQuery then
+    with ADataset as TSQLQuery do
+    begin
+      AssertTrue(Locate('ID',11,[]));
+      for i:=11 to 14 do
+      begin
+        Edit;
+        FieldByName('NAME').AsString:='TestName'+inttostr(i);
+        Post;
+        Next;
+      end;
+      UpdateMode:=upWhereAll; //test when also null fields will be in where
+    end;
 end;
 end;
 
 
 procedure TTestBufDatasetStreams.TestSimpleEditCancelUpd;
 procedure TTestBufDatasetStreams.TestSimpleEditCancelUpd;
@@ -459,6 +492,24 @@ begin
   TestChangesApplyUpdates(@DeleteAllInsertChange, False);
   TestChangesApplyUpdates(@DeleteAllInsertChange, False);
 end;
 end;
 
 
+procedure TTestBufDatasetStreams.NullInsertUpdateApplUpd;
+begin
+  TestChangesApplyUpdates(@NullInsertChange, True);
+  TestChangesApplyUpdates(@NullEditChange, True);
+end;
+
+
+procedure TTestBufDatasetStreams.SetUp;
+begin
+  DBConnector.StartTest;
+end;
+
+procedure TTestBufDatasetStreams.TearDown;
+begin
+  DBConnector.StopTest;
+end;
+
+
 initialization
 initialization
   if uppercase(dbconnectorname)='SQL' then
   if uppercase(dbconnectorname)='SQL' then
     RegisterTestDecorator(TDBBasicsTestSetup, TTestBufDatasetStreams);
     RegisterTestDecorator(TDBBasicsTestSetup, TTestBufDatasetStreams);

+ 85 - 0
packages/fcl-db/tests/testwherenull.lpr

@@ -0,0 +1,85 @@
+program testWhereNULL;
+
+{$mode objfpc}{$H+}
+
+uses
+  {$IFDEF UNIX}{$IFDEF UseCThreads}
+  cthreads,
+  {$ENDIF}{$ENDIF}
+  Classes, SysUtils,
+  db, sqldb, sqlite3conn, variants;
+
+
+var
+  Conn: TSQLite3Connection;
+  Tran: TSQLTransaction;
+  Q: TSQLQuery;
+
+  sql: string;
+  i: integer;
+
+begin
+  Conn:=TSQLite3Connection.Create(nil);
+  Conn.DatabaseName:='test.db';
+
+  Tran:=TSQLTransaction.Create(nil);
+  Tran.DataBase:=Conn;
+
+  Q:=TSQLQuery.Create(nil);
+  Q.DataBase:=Conn;
+
+  Conn.Open;
+  writeln('Connected');
+
+  Conn.ExecuteDirect('CREATE TEMPORARY TABLE t (int_field INT, string_field VARCHAR(30))');
+  writeln('Temporary table created');
+
+  Q.SQL.Text:='SELECT * FROM t';
+  Q.UpdateMode:=upWhereAll; // <-- UpdateMode is upWhereAll or upWhereCahnged
+  Q.Open;
+  Q.AppendRecord([NULL,'a']);
+  Q.AppendRecord([2,'c']);
+  Q.ApplyUpdates;
+  Q.Close;
+
+  writeln('1. Bug: second row has instead of 2 in first column NULL');
+  Q.Open;
+  Q.Next;
+  writeln('Value of ', Q.Fields[0].FieldName,' is: ', Q.Fields[0].AsString, ' expected: 2');
+  Q.Close;
+
+  writeln;
+  writeln('2. Case update of record, where some value is null (upWhereAll or upWhereChanged)');
+  Q.Open;
+  Q.Edit;
+  Q.Fields[1].AsString:='b';
+  Q.Post;
+  Q.ApplyUpdates;
+  Q.Close;
+
+  Q.Open;
+  writeln('Value of ', Q.Fields[1].FieldName,' is: ', Q.Fields[1].AsString,' expected: b');
+  Q.Close;
+
+  writeln;
+  writeln('3. Case delete of record, where some value is null (upWhereAll or upWhereChanged)');
+  Q.Open;
+  Q.Delete;
+  Q.ApplyUpdates;
+  Q.Close;
+
+  Q.Open;
+  writeln('Number of rows: ', Q.RecordCount, ' expected: 1');
+  Q.Close;
+
+  //END
+  Tran.Commit;
+  Conn.Close;
+
+  Q.Free;
+  Tran.Free;
+  Conn.Free;
+  writeln('End. Press any key');
+  readln;
+end.
+