Pārlūkot izejas kodu

fcl-db: memds: implemented basic blob support. Blobs are allocated in memory only. Saving to stream/file is not supported yet. Bug #26476

git-svn-id: trunk@31027 -
lacak 10 gadi atpakaļ
vecāks
revīzija
8214e72841

+ 176 - 39
packages/fcl-db/src/memds/memds.pp

@@ -46,29 +46,31 @@ type
 
 
   MDSError=class(Exception);
   MDSError=class(Exception);
 
 
-  PRecInfo=^TMTRecInfo;
-  TMTRecInfo=record
-    Bookmark: Longint;
-    BookmarkFlag: TBookmarkFlag;
-  end;
-
   { TMemDataset }
   { TMemDataset }
 
 
   TMemDataset=class(TDataSet)
   TMemDataset=class(TDataSet)
   private
   private
-    FOpenStream : TStream;
-    FFileName : String;
-    FFileModified : Boolean;
-    FStream: TMemoryStream;
-    FRecInfoOffset: integer;
-    FRecCount: integer;
-    FRecSize: integer;
-    FCurrRecNo: integer;
-    FIsOpen: boolean;
-    FTableIsCreated: boolean;
-    FFilterBuffer: TRecordBuffer;
-    ffieldoffsets: PInteger;
-    ffieldsizes: PInteger;
+    type
+      TMDSBlobList = class(TFPList)
+        public
+          procedure Clear; reintroduce;
+      end;
+    var
+      FOpenStream : TStream;
+      FFileName : String;
+      FFileModified : Boolean;
+      FStream: TMemoryStream;
+      FRecInfoOffset: integer;
+      FRecCount: integer;
+      FRecSize: integer;
+      FCurrRecNo: integer;
+      FIsOpen: boolean;
+      FTableIsCreated: boolean;
+      FFilterBuffer: TRecordBuffer;
+      ffieldoffsets: PInteger;
+      ffieldsizes: PInteger;
+      FBlobs: TMDSBlobList;
+
     function GetRecordBufferPointer(p:TRecordBuffer; Pos:Integer):TRecordBuffer;
     function GetRecordBufferPointer(p:TRecordBuffer; Pos:Integer):TRecordBuffer;
     function GetIntegerPointer(p:PInteger; Pos:Integer):PInteger;
     function GetIntegerPointer(p:PInteger; Pos:Integer):PInteger;
 
 
@@ -126,17 +128,16 @@ type
     // If SaveData=False, a size 0 block should be written.
     // If SaveData=False, a size 0 block should be written.
     Procedure SaveDataToStream(F : TStream; SaveData : Boolean); virtual;
     Procedure SaveDataToStream(F : TStream; SaveData : Boolean); virtual;
 
 
-
   public
   public
-    constructor Create(AOwner:tComponent); override;
+    constructor Create(AOwner:TComponent); override;
     destructor Destroy; override;
     destructor Destroy; override;
     function BookmarkValid(ABookmark: TBookmark): Boolean; override;
     function BookmarkValid(ABookmark: TBookmark): Boolean; override;
+    function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; override;
     function Locate(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions): boolean; override;
     function Locate(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions): boolean; override;
     function Lookup(const KeyFields: string; const KeyValues: Variant; const ResultFields: string): Variant; override;
     function Lookup(const KeyFields: string; const KeyValues: Variant; const ResultFields: string): Variant; override;
-    procedure CreateTable;
 
 
+    procedure CreateTable;
     Function  DataSize : Integer;
     Function  DataSize : Integer;
-
     Procedure Clear(ClearDefs : Boolean);{$IFNDEF FPC} overload; {$ENDIF}
     Procedure Clear(ClearDefs : Boolean);{$IFNDEF FPC} overload; {$ENDIF}
     Procedure Clear;{$IFNDEF FPC} overload; {$ENDIF}
     Procedure Clear;{$IFNDEF FPC} overload; {$ENDIF}
     Procedure SaveToFile(AFileName : String);{$IFNDEF FPC} overload; {$ENDIF}
     Procedure SaveToFile(AFileName : String);{$IFNDEF FPC} overload; {$ENDIF}
@@ -183,7 +184,7 @@ type
 implementation
 implementation
 
 
 uses
 uses
-  Variants, FmtBCD;
+  DBConst, Variants, FmtBCD;
 
 
 ResourceString
 ResourceString
   SErrFieldTypeNotSupported = 'Fieldtype of Field "%s" not supported.';
   SErrFieldTypeNotSupported = 'Fieldtype of Field "%s" not supported.';
@@ -192,8 +193,40 @@ ResourceString
   SErrInvalidMarkerAtPos    = 'Wrong data stream marker at position %d. Got %d, expected %d';
   SErrInvalidMarkerAtPos    = 'Wrong data stream marker at position %d. Got %d, expected %d';
   SErrNoFileName            = 'Filename must not be empty.';
   SErrNoFileName            = 'Filename must not be empty.';
 
 
+type
+  TMDSRecInfo=record
+    Bookmark: Longint;
+    BookmarkFlag: TBookmarkFlag;
+  end;
+  PRecInfo=^TMDSRecInfo;
+
+  TMDSBlobField = record
+    Buffer: Pointer;  // pointer to memory allocated for Blob data
+    Size: PtrInt;     // size of Blob data
+  end;
+
+  { TMDSBlobStream }
+
+  TMDSBlobStream = class(TStream)
+    private
+      FField      : TBlobField;
+      FDataSet    : TMemDataset;
+      FBlobField  : TMDSBlobField;
+      FPosition   : PtrInt;
+      FModified   : boolean;
+      procedure AllocBlobField(NewSize: PtrInt);
+      procedure FreeBlobField;
+    public
+      constructor Create(Field: TField; Mode: TBlobStreamMode);
+      destructor Destroy; override;
+      function Seek(const Offset: int64; Origin: TSeekOrigin): int64; override;
+      function Read(var Buffer; Count: Longint): Longint; override;
+      function Write(const Buffer; Count: Longint): Longint; override;
+  end;
+
 Const
 Const
-  SizeRecInfo = SizeOf(TMTRecInfo);
+  SizeRecInfo = SizeOf(TMDSRecInfo);
+
 
 
 procedure unsetfieldisnull(nullmask: pbyte; const x: integer);
 procedure unsetfieldisnull(nullmask: pbyte; const x: integer);
 
 
@@ -259,22 +292,110 @@ begin
     S.WriteBuffer(Value[1],L);
     S.WriteBuffer(Value[1],L);
 end;
 end;
 
 
+
+{ TMDSBlobStream }
+
+constructor TMDSBlobStream.Create(Field: TField; Mode: TBlobStreamMode);
+begin
+  FField := Field as TBlobField;
+  FDataSet := Field.DataSet as TMemDataset;
+  if not Field.GetData(@FBlobField) then // IsNull
+  begin
+    FBlobField.Buffer := nil;
+    FBlobField.Size := 0;
+  end;
+
+  if Mode = bmWrite then
+    // release existing Blob
+    FreeBlobField;
+end;
+
+destructor TMDSBlobStream.Destroy;
+begin
+  if FModified then
+  begin
+    if FBlobField.Size = 0 then // Empty blob = IsNull
+      FField.SetData(nil)
+    else
+      FField.SetData(@FBlobField);
+  end;
+  inherited;
+end;
+
+procedure TMDSBlobStream.FreeBlobField;
+begin
+  FDataSet.FBlobs.Remove(FBlobField.Buffer);
+  FreeMem(FBlobField.Buffer, FBlobField.Size);
+  FBlobField.Buffer := nil;
+  FBlobField.Size := 0;
+  FModified := True;
+end;
+
+procedure TMDSBlobStream.AllocBlobField(NewSize: PtrInt);
+begin
+  FDataSet.FBlobs.Remove(FBlobField.Buffer);
+  ReAllocMem(FBlobField.Buffer, NewSize);
+  FDataSet.FBlobs.Add(FBlobField.Buffer);
+  FModified := True;
+end;
+
+function TMDSBlobStream.Seek(const Offset: int64; Origin: TSeekOrigin): int64;
+begin
+  Case Origin of
+    soBeginning : FPosition := Offset;
+    soEnd       : FPosition := FBlobField.Size + Offset;
+    soCurrent   : FPosition := FPosition + Offset;
+  end;
+  Result := FPosition;
+end;
+
+function TMDSBlobStream.Read(var Buffer; Count: Longint): Longint;
+var p: Pointer;
+begin
+  if FPosition + Count > FBlobField.Size then
+    Count := FBlobField.Size - FPosition;
+  p := FBlobField.Buffer + FPosition;
+  Move(p^, Buffer, Count);
+  Inc(FPosition, Count);
+  Result := Count;
+end;
+
+function TMDSBlobStream.Write(const Buffer; Count: Longint): Longint;
+var p: Pointer;
+begin
+  AllocBlobField(FPosition+Count);
+  p := FBlobField.Buffer + FPosition;
+  Move(Buffer, p^, Count);
+  Inc(FBlobField.Size, Count);
+  Inc(FPosition, Count);
+  Result := Count;
+end;
+
+
+{ TMemDataset.TMDSBlobList }
+
+procedure TMemDataset.TMDSBlobList.Clear;
+var i: integer;
+begin
+  for i:=0 to Count-1 do FreeMem(Items[i]);
+  inherited Clear;
+end;
+
 { ---------------------------------------------------------------------
 { ---------------------------------------------------------------------
     TMemDataset
     TMemDataset
   ---------------------------------------------------------------------}
   ---------------------------------------------------------------------}
 
 
-
-constructor TMemDataset.Create(AOwner:tComponent);
+constructor TMemDataset.Create(AOwner:TComponent);
 
 
 begin
 begin
-  inherited create(aOwner);
+  inherited Create(AOwner);
   FStream:=TMemoryStream.Create;
   FStream:=TMemoryStream.Create;
   FRecCount:=0;
   FRecCount:=0;
   FRecSize:=0;
   FRecSize:=0;
   FRecInfoOffset:=0;
   FRecInfoOffset:=0;
   FCurrRecNo:=-1;
   FCurrRecNo:=-1;
   BookmarkSize := sizeof(Longint);
   BookmarkSize := sizeof(Longint);
-  FIsOpen:=False;
+  FBlobs := TMDSBlobList.Create;
 end;
 end;
 
 
 destructor TMemDataset.Destroy;
 destructor TMemDataset.Destroy;
@@ -282,6 +403,8 @@ begin
   FStream.Free;
   FStream.Free;
   FreeMem(FFieldOffsets);
   FreeMem(FFieldOffsets);
   FreeMem(FFieldSizes);
   FreeMem(FFieldSizes);
+  FBlobs.Clear;
+  FBlobs.Free;
   inherited Destroy;
   inherited Destroy;
 end;
 end;
 
 
@@ -295,6 +418,20 @@ begin
   Result := (ReqBookmark>=0) and (ReqBookmark<FRecCount);
   Result := (ReqBookmark>=0) and (ReqBookmark<FRecCount);
 end;
 end;
 
 
+function TMemDataset.CreateBlobStream(Field: TField; Mode: TBlobStreamMode
+  ): TStream;
+begin
+  // Blobs are not saved to stream/file !
+  if Mode = bmWrite then
+    begin
+    if not (State in [dsEdit, dsInsert, dsFilter, dsCalcFields]) then
+      DatabaseErrorFmt(SNotEditing, [Name], Self);
+    if Field.ReadOnly and not (State in [dsSetKey, dsFilter]) then
+      DatabaseErrorFmt(SReadOnlyField, [Field.DisplayName]);
+    end;
+  Result := TMDSBlobStream.Create(Field, Mode);
+end;
+
 function TMemDataset.MDSGetRecordOffset(ARecNo: integer): longint;
 function TMemDataset.MDSGetRecordOffset(ARecNo: integer): longint;
 begin
 begin
   Result:=FRecSize*ARecNo
   Result:=FRecSize*ARecNo
@@ -302,7 +439,7 @@ end;
 
 
 function TMemDataset.MDSGetFieldOffset(FieldNo: integer): integer;
 function TMemDataset.MDSGetFieldOffset(FieldNo: integer): integer;
 begin
 begin
- result:= getIntegerpointer(ffieldoffsets, fieldno-1)^;
+  Result:= getIntegerPointer(ffieldoffsets, fieldno-1)^;
 end;
 end;
 
 
 procedure TMemDataset.RaiseError(Fmt: String; Args: array of const);
 procedure TMemDataset.RaiseError(Fmt: String; Args: array of const);
@@ -333,10 +470,12 @@ begin
     ftTime,
     ftTime,
     ftDate:   result:=SizeOf(TDateTime);
     ftDate:   result:=SizeOf(TDateTime);
   ftFmtBCD:   result:=SizeOf(TBCD);
   ftFmtBCD:   result:=SizeOf(TBCD);
-  ftWideString,
-  ftFixedWideChar: result:=(FD.Size+1)*SizeOf(WideChar);
+  ftWideString, ftFixedWideChar:
+              result:=(FD.Size+1)*SizeOf(WideChar);
   ftBytes:    result := FD.Size;
   ftBytes:    result := FD.Size;
   ftVarBytes: result := FD.Size + SizeOf(Word);
   ftVarBytes: result := FD.Size + SizeOf(Word);
+  ftBlob, ftMemo, ftWideMemo:
+              result := SizeOf(TMDSBlobField);
  else
  else
   RaiseError(SErrFieldTypeNotSupported,[FD.Name]);
   RaiseError(SErrFieldTypeNotSupported,[FD.Name]);
  end;
  end;
@@ -533,6 +672,7 @@ Var
 begin
 begin
   CheckMarker(F,smData);
   CheckMarker(F,smData);
   Size:=ReadInteger(F);
   Size:=ReadInteger(F);
+  FBlobs.Clear;
   FStream.Clear;
   FStream.Clear;
   FStream.CopyFrom(F,Size);
   FStream.CopyFrom(F,Size);
   FRecCount:=Size div FRecSize;
   FRecCount:=Size div FRecSize;
@@ -654,9 +794,8 @@ begin
  FIsOpen:=False;
  FIsOpen:=False;
  FFileModified:=False;
  FFileModified:=False;
  // BindFields(False);
  // BindFields(False);
- if DefaultFields then begin
+ if DefaultFields then
   DestroyFields;
   DestroyFields;
- end;
 end;
 end;
 
 
 procedure TMemDataset.InternalPost;
 procedure TMemDataset.InternalPost;
@@ -872,6 +1011,7 @@ end;
 procedure TMemDataset.Clear(ClearDefs : Boolean);
 procedure TMemDataset.Clear(ClearDefs : Boolean);
 
 
 begin
 begin
+  FBlobs.Clear;
   FStream.Clear;
   FStream.Clear;
   FRecCount:=0;
   FRecCount:=0;
   FCurrRecNo:=-1;
   FCurrRecNo:=-1;
@@ -907,7 +1047,7 @@ begin
  for i:= 0 to Count-1 do
  for i:= 0 to Count-1 do
    begin
    begin
    GetIntegerPointer(FFieldOffsets, i)^ := FRecSize;
    GetIntegerPointer(FFieldOffsets, i)^ := FRecSize;
-   GetIntegerPointer(FFieldSizes,   i)^ := MDSGetbufferSize(i+1);
+   GetIntegerPointer(FFieldSizes,   i)^ := MDSGetBufferSize(i+1);
    FRecSize:= FRecSize+GetIntegerPointer(FFieldSizes, i)^;
    FRecSize:= FRecSize+GetIntegerPointer(FFieldSizes, i)^;
    end;
    end;
  FRecInfoOffset:=FRecSize;
  FRecInfoOffset:=FRecSize;
@@ -918,10 +1058,7 @@ procedure TMemDataset.CreateTable;
 
 
 begin
 begin
   CheckInactive;
   CheckInactive;
-  FStream.Clear;
-  FRecCount:=0;
-  FCurrRecNo:=-1;
-  FIsOpen:=False;
+  Clear(False);
   calcrecordlayout;
   calcrecordlayout;
   FTableIsCreated:=True;
   FTableIsCreated:=True;
 end;
 end;

+ 12 - 2
packages/fcl-db/tests/memdstoolsunit.pas

@@ -7,7 +7,7 @@ interface
 uses
 uses
   Classes, SysUtils, toolsunit,
   Classes, SysUtils, toolsunit,
   db,
   db,
-  Memds;
+  MemDS;
 
 
 type
 type
 { TMemDSConnector }
 { TMemDSConnector }
@@ -81,7 +81,7 @@ begin
   testTimeValues[2] := '23:59:59.000';
   testTimeValues[2] := '23:59:59.000';
   testTimeValues[3] := '23:59:59.003';
   testTimeValues[3] := '23:59:59.003';
 
 
-  MemDs := TMemDataset.Create(nil);
+  MemDS := TMemDataset.Create(nil);
   with MemDS do
   with MemDS do
     begin
     begin
     Name := 'FieldDataset';
     Name := 'FieldDataset';
@@ -100,6 +100,11 @@ begin
     FieldDefs.Add('FFIXEDCHAR',ftFixedChar,10);
     FieldDefs.Add('FFIXEDCHAR',ftFixedChar,10);
     FieldDefs.Add('FLARGEINT',ftLargeint);
     FieldDefs.Add('FLARGEINT',ftLargeint);
     FieldDefs.Add('FFMTBCD',ftFmtBCD);
     FieldDefs.Add('FFMTBCD',ftFmtBCD);
+    FieldDefs.Add('FBLOB',ftBlob);
+    FieldDefs.Add('FMEMO',ftMemo);
+    FieldDefs.Add('FWIDESTRING',ftWideString);
+    FieldDefs.Add('FFIXEDWIDECHAR',ftFixedWideChar);
+    FieldDefs.Add('FWIDEMEMO',ftWideMemo);
     CreateTable;
     CreateTable;
     Open;
     Open;
     for i := 0 to testValuesCount-1 do
     for i := 0 to testValuesCount-1 do
@@ -120,6 +125,11 @@ begin
       FieldByName('FFIXEDCHAR').AsString := PadRight(testStringValues[i], 10);
       FieldByName('FFIXEDCHAR').AsString := PadRight(testStringValues[i], 10);
       FieldByName('FLARGEINT').AsLargeInt := testLargeIntValues[i];
       FieldByName('FLARGEINT').AsLargeInt := testLargeIntValues[i];
       FieldByName('FFMTBCD').AsBCD := StrToBCD(testFmtBCDValues[i], Self.FormatSettings);
       FieldByName('FFMTBCD').AsBCD := StrToBCD(testFmtBCDValues[i], Self.FormatSettings);
+      FieldByName('FBLOB').AsString := testValues[ftBlob, i];
+      FieldByName('FMEMO').AsString := testValues[ftMemo, i];
+      FieldByName('FWIDESTRING').AsWideString := testValues[ftWideString, i];
+      FieldByName('FFIXEDWIDECHAR').AsWideString := testValues[ftFixedWideChar, i];
+      FieldByName('FWIDEMEMO').AsWideString := testValues[ftWideMemo, i];
       Post;
       Post;
       end;
       end;
     Close;
     Close;

+ 1 - 1
packages/fcl-db/tests/testspecifictmemdataset.pas

@@ -10,7 +10,7 @@ interface
 
 
 uses
 uses
   Classes, SysUtils,
   Classes, SysUtils,
-  fpcunit, testregistry,
+  testregistry,
   ToolsUnit;
   ToolsUnit;
 
 
 type
 type