Hi

I managed to implement correct TField.IsNull behaviour for fields inside TIBQuery dataset. I'm attaching:

-- patch to packages/base/ibase/ibase60types.inc, this fixes a small bug in ibase60 unit that defined Short as Integer (= 32 bits, since ibase60 unit is compiled in objfpc mode), while it should be 16 bits.

-- patch to fcl/db/interbase/interbase.pp that

1) Fixes FFieldFlag declaration, it should be an array of IBase60.Short type (16 bits, not 8-bits ShortInt type as it was).

2) Implements correct TField.IsNull behaviour by recording "IsNull" state of each field of each record, along with data of that field. ThenTIBQuery.GetFieldData can be modified to return correct value of IsNull, and set Buffer^ only if field is not null and Buffer <> nil (since TField.GetIsNull calls TField.GetData with Buffer = Nil, and this causes call to FDataset.GetFieldData with Buffer = nil)

3) Oh, and small changes to TIBTransaction that I sent in my previous email this day (publishing 4 properties) are also included in this patch. Sorry, this was just easier for me, and since I didn't see them committed yet... Anyway, these changes to TIBTransaction are absolutely not related to all that "IsNull stuff", so feel free to cut them out if you don't like them.

-- I'm also attaching a trivial program using Interbase unit and some testing db to check that IsNull works as it should. Just in case it will save you some time in testing my patch :)

Michalis.
Index: ibase60types.inc
===================================================================
RCS file: /FPC/CVS/fpc/packages/base/ibase/ibase60types.inc,v
retrieving revision 1.2
diff -u -r1.2 ibase60types.inc
--- ibase60types.inc	4 Feb 2005 18:14:22 -0000	1.2
+++ ibase60types.inc	17 Mar 2005 00:15:54 -0000
@@ -14,7 +14,7 @@
 
   Int                  = LongInt;
   Long                 = LongInt;
-  Short                = Integer;
+  Short                = SmallInt;
   Float                = Single;
 
   { Pointers to basic types }
Index: interbase.pp
===================================================================
RCS file: /FPC/CVS/fpc/fcl/db/interbase/interbase.pp,v
retrieving revision 1.15
diff -u -r1.15 interbase.pp
--- interbase.pp	14 Feb 2005 17:13:12 -0000	1.15
+++ interbase.pp	17 Mar 2005 00:26:20 -0000
@@ -175,6 +175,16 @@
     { Transaction must be assigned to some database session, for which purpose
       you must use this property}
     property Database : TIBDatabase read FDatabase write FDatabase;
+
+    { These four properties will be used in next StartTransaction calls }
+    property AccessMode: TAccessMode
+      read FAccessMode write FAccessMode default amReadWrite;
+    property IsolationLevel: TIsolationLevel
+      read FIsolationLevel write FIsolationLevel default ilReadCommitted;
+    property LockResolution: TLockResolution
+      read FLockResolution write FLockResolution default lrWait;
+    property TableReservation: TTableReservation
+      read FTableReservation write FTableReservation default trNone;
   end;
 
 { TIBQuery }
@@ -195,7 +205,7 @@
     FTransaction         : TIBTransaction;
     FDatabase            : TIBDatabase;
     FStatus              : array [0..19] of ISC_STATUS;
-    FFieldFlag           : array [0..1023] of shortint;
+    FFieldFlag           : array [0..1023] of IBase60.Short;
     FBufferSize          : integer;
     FSQLDA               : PXSQLDA;
     FSQLDAAllocated      : integer;
@@ -578,6 +588,18 @@
 
 { TIBQuery }
 
+type
+  { For now, we could simply say here that TFieldDataPrefix = boolean.
+    But making TFieldDataPrefix as record will be allow to very easy add
+    similar things like "IsNull" in the future.
+    Any information that has constant length, and should be
+    specified separately for every field of every row can be added as
+    another TFieldDataPrefix field. }
+  TFieldDataPrefix = record
+    IsNull: boolean;
+  end;
+  PFieldDataPrefix = ^TFieldDataPrefix;
+
 procedure TIBQuery.SetTransaction(Value : TIBTransaction);
 begin
   CheckInactive;
@@ -748,6 +770,12 @@
   begin
     with FSQLDA^.SQLVar[x] do
     begin
+      PFieldDataPrefix(Buffer)^.IsNull :=
+        { If 1st bit of SQLType is not set then field *cannot* be null,
+          and we shouldn't check SQLInd }
+        ((SQLType and 1) <> 0) and (SQLInd^ = -1);
+      Inc(Buffer, SizeOf(TFieldDataPrefix));
+
       if ((SQLType and not 1) = SQL_VARYING) then
       begin
         Move(SQLData^, VarcharLen, 2);
@@ -785,11 +813,12 @@
   x : integer;
 begin
   FRecordSize := 0;
-  FBufferSize := 0;
   {$R-}
   for x := 0 to FSQLDA^.SQLD - 1 do
     Inc(FRecordSize, FSQLDA^.SQLVar[x].SQLLen);
   {$R+}
+  Inc(FRecordSize, SizeOf(TFieldDataPrefix) * FSQLDA^.SQLD);
+
   FBufferSize := FRecordSize + SizeOf(TIBBookmark);
 end;
 
@@ -963,29 +992,33 @@
     {$R-}
     if (Field.FieldName = FSQLDA^.SQLVar[x].SQLName) then
     begin
-      case Field.DataType of
-        ftInteger :
-          begin
-            b := 0;
-            Move(b, Buffer^, 4);
-            Move(CurrBuff^, Buffer^, Field.Size);
-          end;
-        ftDate, ftTime, ftDateTime:
-          GetDateTime(CurrBuff, Buffer, FSQLDA^.SQLVar[x].SQLType);
-        ftString  :
-          begin
-            Move(CurrBuff^, Buffer^, Field.Size);
-            PChar(Buffer + Field.Size)^ := #0;
-          end;
-        ftFloat   :
-          GetFloat(CurrBuff, Buffer, Field);
-      end;
+      Result := not PFieldDataPrefix(CurrBuff)^.IsNull;
 
-      Result := True;
+      if Result and (Buffer <> nil) then
+      begin
+        Inc(CurrBuff, SizeOf(TFieldDataPrefix));
+        case Field.DataType of
+          ftInteger :
+            begin
+              b := 0;
+              Move(b, Buffer^, 4);
+              Move(CurrBuff^, Buffer^, Field.Size);
+            end;
+          ftDate, ftTime, ftDateTime:
+            GetDateTime(CurrBuff, Buffer, FSQLDA^.SQLVar[x].SQLType);
+          ftString  :
+            begin
+              Move(CurrBuff^, Buffer^, Field.Size);
+              PChar(Buffer + Field.Size)^ := #0;
+            end;
+          ftFloat   :
+            GetFloat(CurrBuff, Buffer, Field);
+        end;
+      end;
 
       Break;
     end
-    else Inc(CurrBuff, FSQLDA^.SQLVar[x].SQLLen);
+    else Inc(CurrBuff, FSQLDA^.SQLVar[x].SQLLen + SizeOf(TFieldDataPrefix));
     {$R+}
   end;
 end;
{ Create test db with SQL

    create database 'test_isnull.fdb';
    create table TestTable(Foo integer not null, Bar integer);
    insert into TestTable(Foo) values(1);
    insert into TestTable(Foo, Bar) values(222, 333);
    insert into TestTable(Foo) values(44);
    insert into TestTable(Foo, Bar) values(888, 999);

 Then run this program with $1 = SYSDBA password, it should correctly
 output table TestTable including null values.
}

{$mode delphi}

uses SysUtils, DB, Interbase;

function FieldToStr(F: TField): string;
begin
 if F.IsNull then
  Result := 'NULL' else
  Result := IntToStr(F.AsInteger);
end;

var
  Database: TIBDatabase;
  Transaction: TIBTransaction;
  Query: TIBQuery;
begin
 Database := TIBDatabase.Create(nil);
 try
  Database.DatabaseName := 'test_isnull.fdb';
  Database.UserName := 'SYSDBA';
  Database.Password := ParamStr(1);
  Database.Open;
  try
   Transaction := TIBTransaction.Create(nil);
   try
    Transaction.Database := Database;
    Transaction.StartTransaction;
    try
     Query := TIBQuery.Create(nil);
     try
      Query.Database := Database;
      Query.Transaction := Transaction;

      Query.SQL.Text := 'select * from TestTable';
      Query.Open;
      try
       while not Query.Eof do
       begin
        Writeln(FieldToStr(Query.Fields[0]), ' ',
                FieldToStr(Query.Fields[1]));
        Query.Next;
       end;
      finally Query.Close end;

     finally Query.Free end;
    finally Transaction.Rollback end;
   finally Transaction.Free end
  finally Database.Close end;
 finally Database.Free end;
end.
_______________________________________________
fpc-devel maillist  -  fpc-devel@lists.freepascal.org
http://lists.freepascal.org/mailman/listinfo/fpc-devel

Reply via email to