|
@@ -0,0 +1,2896 @@
|
|
|
+unit dbf;
|
|
|
+{===============================================================================
|
|
|
+|| TDbf Component || http://tdbf.netfirms.com ||
|
|
|
+===============================================================================}
|
|
|
+interface
|
|
|
+
|
|
|
+uses
|
|
|
+{$ifdef fpc}
|
|
|
+ SysUtils, Classes, db;
|
|
|
+{$else}
|
|
|
+ Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
|
|
|
+ Db, DsgnIntf, ExptIntf;
|
|
|
+{$endif}
|
|
|
+// If you got a compilation error here or asking for dsgntf.pas, then just add
|
|
|
+// this file in your project:
|
|
|
+// dsgnintf.pas in 'C:\Program Files\Borland\Delphi5\Source\Toolsapi\dsgnintf.pas'
|
|
|
+
|
|
|
+const
|
|
|
+ _MAJOR_VERSION = 3;
|
|
|
+ _MINOR_VERSION = 007;
|
|
|
+
|
|
|
+
|
|
|
+{$ifdef VER100} // Delphi 3
|
|
|
+ {$define DELPHI_3}
|
|
|
+{$endif}
|
|
|
+
|
|
|
+{$ifdef VER110} // CBuilder 3
|
|
|
+ {$define DELPHI_3}
|
|
|
+{$endif}
|
|
|
+
|
|
|
+{$ifdef linux}
|
|
|
+ DirSeparator = '/';
|
|
|
+{$else}
|
|
|
+ DirSeparator = '\';
|
|
|
+{$endif}
|
|
|
+
|
|
|
+//====================================================================
|
|
|
+// Delphi is a bit to permissive for me, I mean protected doesn't work within
|
|
|
+// one unit. So i decided that convention:
|
|
|
+// private member begins by '_'
|
|
|
+// It's forbidden to access any '_something' except from the class where it
|
|
|
+// is defined. To check that, I just have to look for '._' anywhere in the code.
|
|
|
+//====================================================================
|
|
|
+type
|
|
|
+
|
|
|
+//====================================================================
|
|
|
+//=== Common exceptions and constants
|
|
|
+//====================================================================
|
|
|
+ EBinaryDataSetError = class (Exception);
|
|
|
+ EFieldToLongError = class (Exception);
|
|
|
+
|
|
|
+ xBaseVersion = (xBaseIII,xBaseIV,xBaseV);
|
|
|
+
|
|
|
+//====================================================================
|
|
|
+//=== Utility classes
|
|
|
+//====================================================================
|
|
|
+ TPagedFile = class(TObject)
|
|
|
+ protected
|
|
|
+ Stream : TStream;
|
|
|
+ HeaderSize : Integer;
|
|
|
+ RecordSize : Integer;
|
|
|
+ _cntuse:integer;
|
|
|
+ _Filename:string;
|
|
|
+ public
|
|
|
+ constructor Create(const FileName: string; Mode: Word);
|
|
|
+ destructor Destroy; override;
|
|
|
+
|
|
|
+ procedure Release;
|
|
|
+ function CalcRecordCount:Integer;
|
|
|
+ procedure _Seek(page:Integer);
|
|
|
+ procedure ReadRecord(IntRecNum:Integer;Buffer:Pointer);
|
|
|
+ procedure WriteRecord(IntRecNum:Integer;Buffer:Pointer);
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+//=== Dbf support (first part)
|
|
|
+//====================================================================
|
|
|
+ rDbfHdr = record
|
|
|
+ VerDBF : byte; // 0
|
|
|
+ Year : byte; // 1
|
|
|
+ Month : byte; // 2
|
|
|
+ Day : byte; // 3
|
|
|
+ RecordCount : Integer; // 4-7
|
|
|
+ FullHdrSize : word; // 8-9
|
|
|
+ RecordSize : word; // 10-11
|
|
|
+ Dummy1 : Word; // 12-13
|
|
|
+ IncTrans : byte; // 14
|
|
|
+ Encrypt : byte; // 15
|
|
|
+ Dummy2 : Integer; // 16-19
|
|
|
+ Dummy3 : array[20..27] of byte; // 20-27
|
|
|
+ MDXFlag : char; // 28
|
|
|
+ Language : char; // 29
|
|
|
+ dummy4 : word; // 30-31
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+ TMyFieldInfo = class
|
|
|
+ public
|
|
|
+ FieldName:string;
|
|
|
+ Size:Integer;
|
|
|
+ Prec:Integer;
|
|
|
+ Offset:Integer;
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+ TDbfFile = class(TPagedFile)
|
|
|
+ protected
|
|
|
+ _RecordBufferSize:integer;
|
|
|
+ _DataHdr : rDbfHdr;
|
|
|
+ _DbfVersion : xBaseVersion;
|
|
|
+ _MyFieldInfos: TList;
|
|
|
+ public
|
|
|
+ constructor Create(const FileName: string; Mode: Word);
|
|
|
+ destructor Destroy; override;
|
|
|
+ function RecordCount:integer;
|
|
|
+ procedure CreateFieldDefs(FieldDefs:TFieldDefs);
|
|
|
+ procedure ClearMyFieldInfos;
|
|
|
+ procedure DbfFile_CreateTable(FieldDefs:TFieldDefs);
|
|
|
+ procedure DbfFile_PackTable;
|
|
|
+ function GetFieldInfo(FieldName:string):TMyFieldInfo;
|
|
|
+ function GetFieldData(Column:Integer;DataType:TFieldType; Src,Dst: Pointer): Boolean;
|
|
|
+ procedure SetFieldData(Column:integer;DataType:TFieldType; Src,Dst: Pointer);
|
|
|
+ procedure WriteHeader;
|
|
|
+
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+//=== Index support
|
|
|
+//====================================================================
|
|
|
+ TIndex = class;
|
|
|
+//====================================================================
|
|
|
+ rNdxHdr = record
|
|
|
+ startpage : Integer; // 0..3
|
|
|
+ nbPage : Integer; // 4..7
|
|
|
+ keyformat: Char; //8
|
|
|
+ keytype : char; //9
|
|
|
+ dummy : Word; // 10..11
|
|
|
+ keylen : Word; // 12..13
|
|
|
+ nbkey : Word; // 14..15
|
|
|
+ skeytype : Word; // 16..17
|
|
|
+ keyreclen : Word; // 18..19
|
|
|
+ dummy2 : Word; // 20..21
|
|
|
+ dummy3 : Byte; // 22
|
|
|
+ Unique : Byte; // 23
|
|
|
+ KeyDesc : array[0..255] of char; // 24...
|
|
|
+ end;
|
|
|
+
|
|
|
+ rMdxTag = record
|
|
|
+ pageno : Integer; // 0..3
|
|
|
+ tagname : array [0..11] of char; // 4..14
|
|
|
+ keyformat : byte; // 15
|
|
|
+ forwardTag1 : char; // 16
|
|
|
+ forwardTag2 : byte; // 17
|
|
|
+ backwardTag : byte; // 18
|
|
|
+ dummy : byte; // 19
|
|
|
+ keytype : byte; // 20
|
|
|
+ end;
|
|
|
+
|
|
|
+ NdxKeyType = (N,C);
|
|
|
+ PNdxPage = ^rNdxPage;
|
|
|
+ rNdxPage = record
|
|
|
+ NbEntries : longint; // 0..3 lower page
|
|
|
+ Entries : ARRAY [0..507] OF char;
|
|
|
+ end;
|
|
|
+
|
|
|
+ PNdxentry = ^rNdxentry;
|
|
|
+ rNdxentry = record
|
|
|
+ _LowerPage : longint; // 0..3 lower page
|
|
|
+ RecNo : Longint; // 4..7 recno
|
|
|
+ case NdxKeyType of
|
|
|
+ N: ( NKey: double);
|
|
|
+ C: ( CKey: array [0..503] of char);
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+ rMdxHdr = record
|
|
|
+ MdxHdr : byte; // 0
|
|
|
+ Year : byte; // 1
|
|
|
+ Month : byte; // 2
|
|
|
+ Day : byte; // 3
|
|
|
+ FileName : array[0..15] of char; // 4..19 of byte
|
|
|
+ BlockSize : word; // 20 21
|
|
|
+ BlockAdder : word; // 22 23
|
|
|
+ IndexFlag : byte; // 24
|
|
|
+ NoTag : byte; // 25
|
|
|
+ TagSize : byte; // 26
|
|
|
+ Dummy1 : byte; // 27
|
|
|
+ TagUsed : word; // 28..29
|
|
|
+ Dummy2 : word; // 30..31
|
|
|
+ NbPage : Integer; // 32..35
|
|
|
+ FreePage : Integer; // 36..39
|
|
|
+ BlockFree : Integer; // 40..43
|
|
|
+ UpdYear : byte; // 44
|
|
|
+ UpdMonth : byte; // 45
|
|
|
+ UpdDay : byte; // 46
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+ TIndexFile = class(TPagedFile)
|
|
|
+ protected
|
|
|
+ _IndexVersion : xBaseVersion;
|
|
|
+ _MdxHdr : rMdxHdr;
|
|
|
+ public
|
|
|
+ constructor Create(const FileName: string; Mode: Word);
|
|
|
+ destructor Destroy; override;
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+ PIndexPosInfo = ^TIndexPage;
|
|
|
+ TIndexPage = class
|
|
|
+ protected
|
|
|
+ _Index : TIndex;
|
|
|
+ _PageNo : Integer;
|
|
|
+ _EntryNo : Integer;
|
|
|
+ Entry : PNdxentry;
|
|
|
+ _LowerLevel : TIndexPage;
|
|
|
+ _UpperLevel : TIndexPage;
|
|
|
+ _PageBuff:rNdxPage;
|
|
|
+
|
|
|
+ procedure LocalFirst;
|
|
|
+ procedure LocalLast;
|
|
|
+ function LocalPrev:boolean;
|
|
|
+ function LocalNext:boolean;
|
|
|
+ function LastEntryNo:integer;
|
|
|
+ function LocalInsert(Recno:Integer; Buffer:PChar; LowerPage:integer):boolean;
|
|
|
+ function LocalDelete:boolean;
|
|
|
+
|
|
|
+ function GetPEntry(EntryNo:integer):PNdxEntry;
|
|
|
+ procedure First;
|
|
|
+ procedure Last;
|
|
|
+ function Prev:boolean;
|
|
|
+ function Next:boolean;
|
|
|
+ procedure Write;
|
|
|
+ procedure AddNewLevel;
|
|
|
+ public
|
|
|
+ constructor Create(Parent:TIndex);
|
|
|
+ destructor Destroy; override;
|
|
|
+
|
|
|
+ procedure SetPageNo(page:Integer);
|
|
|
+ procedure SetEntryNo(entryno:Integer);
|
|
|
+ procedure WritePage(Page:integer);
|
|
|
+ function FindNearest(Recno:integer; Key:PChar):integer;
|
|
|
+ function Insert(Recno:integer; Buffer:pchar; LowerPage:integer):boolean;
|
|
|
+ procedure SetEntry(Recno:integer; key:pchar; LowerPage:integer);
|
|
|
+ function Delete:boolean;
|
|
|
+ function LowerLevel : TIndexPage;
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+ TIndex = class(TObject)
|
|
|
+ protected
|
|
|
+ _IndexFile:TIndexFile;
|
|
|
+ _NdxHdr:rNdxHdr;
|
|
|
+ _Root:TIndexPage;
|
|
|
+ _TagPosition:Integer;
|
|
|
+ _FieldPos : integer;
|
|
|
+ _FieldLen : integer;
|
|
|
+ _NbLevel : integer;
|
|
|
+ _RootPage: integer;
|
|
|
+
|
|
|
+ function Pos:TIndexPage;
|
|
|
+ public
|
|
|
+ IndexRecNo:integer;
|
|
|
+ function Prev:boolean;
|
|
|
+ function Next:boolean;
|
|
|
+ procedure First;
|
|
|
+ procedure Last;
|
|
|
+ function Find(Recno:integer; Buffer:PChar; var pPos:TIndexPage):integer;
|
|
|
+ procedure Insert(Recno:integer; Buffer:PChar);
|
|
|
+ function Delete:boolean;
|
|
|
+ procedure GotoKey(Recno:integer; Buffer:PChar);
|
|
|
+ procedure Update(Recno: integer; PrevBuffer,NewBuffer: PChar);
|
|
|
+// procedure ResyncInd;
|
|
|
+ function GetRealRecNo: Integer;
|
|
|
+ constructor Create(Parent:TIndexFile; RootPage:integer;CreateIt:boolean);
|
|
|
+ procedure InitFieldDef(dbfFile:TDbfFile;FieldDesc:string);
|
|
|
+ destructor Destroy; override;
|
|
|
+// optionnal
|
|
|
+ function GuessRecordCount: Integer;
|
|
|
+ function GuessRecNo: Integer;
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+//=== Memo and binary fields support
|
|
|
+//====================================================================
|
|
|
+ rDbtHdr = record
|
|
|
+ NextBlock:Longint;
|
|
|
+ Dummy : array [4..7] of byte;
|
|
|
+ _dbfFile : array [0..7] of Byte; //8..15
|
|
|
+ bVer : Byte; //16
|
|
|
+ Dummy2 : array [17..19] of byte;
|
|
|
+ BlockLen: Word;
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+ TDbtFile = class(TPagedFile)
|
|
|
+ protected
|
|
|
+ _DbtVersion:xBaseVersion;
|
|
|
+ _MemoHdr:rDbtHdr;
|
|
|
+ public
|
|
|
+ constructor Create(const FileName: string; Mode: Word; Ver:xBaseVersion);
|
|
|
+ procedure ReadMemo(recno:Integer;Dst:TStream);
|
|
|
+ procedure WriteMemo(var MemoRecno:Integer;ReadSize:Integer;Src:TStream);
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+ TMyBlobFile = class(TMemoryStream)
|
|
|
+ public
|
|
|
+ Mode: TBlobStreamMode;
|
|
|
+ Field:TField;
|
|
|
+ MemoRecno:Integer;
|
|
|
+ ReadSize:Integer;
|
|
|
+ constructor Create(ModeVal:TBlobStreamMode; FieldVal:TField);
|
|
|
+ destructor destroy; override;
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+//=== Dbf support 2
|
|
|
+//====================================================================
|
|
|
+ rFieldHdrIII = record
|
|
|
+ FieldName : array[0..10] of char;
|
|
|
+ FieldType : char; // 11
|
|
|
+ Dummy : array[12..15] of byte;
|
|
|
+ FieldSize : byte; // 16
|
|
|
+ FieldPrecision : byte; //17
|
|
|
+ dummy2 : array[18..31] of byte;
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+ rFieldHdrV = record
|
|
|
+ FieldName : array[0..10] of char;
|
|
|
+ Dummy0 : array[11..31] of byte;
|
|
|
+ FieldType : char; // 32
|
|
|
+ FieldSize : byte; // 33
|
|
|
+ FieldPrecision : byte; //34
|
|
|
+ dummy2 : array[35..47] of byte;
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+ PBookMarkData = ^rBookMarkData;
|
|
|
+ rBookmarkData = record
|
|
|
+ RecNo:longint;
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+ rBeforeRecord = record
|
|
|
+ BookmarkData: rBookmarkData;
|
|
|
+ BookmarkFlag: TBookmarkFlag;
|
|
|
+ //... record come here
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+ pDbfRecord = ^rDbfRecord;
|
|
|
+ rDbfRecord = record
|
|
|
+ BookmarkData: rBookmarkData;
|
|
|
+ BookmarkFlag: TBookmarkFlag;
|
|
|
+ DeletedFlag : char;
|
|
|
+ Fields : array[0..4000] of char;
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+ PRecInfo = ^TRecInfo;
|
|
|
+ TRecInfo = record
|
|
|
+ Bookmark: Longint;
|
|
|
+ IdxBookmark: Longint;
|
|
|
+ BookmarkFlag: TBookmarkFlag;
|
|
|
+ end;
|
|
|
+//====================================================================
|
|
|
+ pRecordHdr = ^tRecordHdr;
|
|
|
+ tRecordHdr = record
|
|
|
+ DeletedFlag : char;
|
|
|
+ end;
|
|
|
+
|
|
|
+// and at LEAST the most useful class : TDbf
|
|
|
+//====================================================================
|
|
|
+ TDbf = class(TDataSet)
|
|
|
+ private
|
|
|
+ _ShowDeleted:boolean;
|
|
|
+ _TableName: string; // table path and file name
|
|
|
+ _RunTimePath: string; // table path and file name
|
|
|
+ _DesignTimePath: string; // table path and file name
|
|
|
+ _ReadOnly : Boolean;
|
|
|
+ _FilterBuffer:pchar;
|
|
|
+ _PrevBuffer:pchar;
|
|
|
+ _IndexFiles:TStrings;
|
|
|
+ protected
|
|
|
+ function _FullRecordSize:integer;
|
|
|
+ function _FilterRecord(Buffer: PChar): Boolean;
|
|
|
+ procedure _OpenFiles(CreateIt:boolean);
|
|
|
+ procedure _CloseFiles;
|
|
|
+ procedure _ResyncIndexes(Buffer: PChar);
|
|
|
+ function _GetIndexName: string;
|
|
|
+ procedure _SetIndexName(const Value: string);
|
|
|
+ function _GetIndex(filename:string):TIndex;
|
|
|
+ function _GetPath:string;
|
|
|
+ function _ComponentInfo:string;
|
|
|
+ public
|
|
|
+ { my own methods and properties}
|
|
|
+ { most looks like ttable functions but they are not tdataset related
|
|
|
+ I use the same syntax to facilitate the conversion between bde and tdbf }
|
|
|
+ easyfilter:string;
|
|
|
+ procedure CreateTable; //(FieldDefs:TFieldDefs);
|
|
|
+ procedure DeleteIndex(const AName: string);
|
|
|
+ property IndexName: string read _GetIndexName write _SetIndexName;
|
|
|
+
|
|
|
+{$ifdef DELPHI_3}
|
|
|
+ procedure AddIndex(const IndexName, Fields: String; Options: TIndexOptions);
|
|
|
+{$else}
|
|
|
+{$ifndef FPC}
|
|
|
+ procedure AddIndex(const IndexName, Fields: String; Options: TIndexOptions; const DescFields: String='');
|
|
|
+{$else}
|
|
|
+ procedure AddIndex(const AnIndexName, IndexFields: String; Options: TIndexOptions);
|
|
|
+ procedure AddIndex(const AnIndexName, IndexFields: String; Options: TIndexOptions; const DescFields: String);
|
|
|
+
|
|
|
+{$endif}
|
|
|
+{$endif}
|
|
|
+ procedure CloseIndexFile(const IndexFileName: string);
|
|
|
+ procedure OpenIndexFile(AnIndexName:string);
|
|
|
+ procedure PackTable;
|
|
|
+ public
|
|
|
+ { abstract methods }
|
|
|
+ function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override; {virtual abstract}
|
|
|
+ {virtual methods (mostly optionnal) }
|
|
|
+ function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; override; {virtual}
|
|
|
+{$ifdef DELPHI_3}
|
|
|
+ procedure Translate(Src, Dest: PChar; ToOem: Boolean); override; {virtual}
|
|
|
+{$else}
|
|
|
+{$ifdef fpc}
|
|
|
+ procedure Translate(Src, Dest: PChar; ToOem: Boolean); override; {virtual}
|
|
|
+{$else}
|
|
|
+ function Translate(Src, Dest: PChar; ToOem: Boolean): Integer; override; {virtual}
|
|
|
+{$endif}
|
|
|
+{$endif}
|
|
|
+ procedure ClearCalcFields(Buffer : PChar); override;
|
|
|
+ protected
|
|
|
+ { abstract methods }
|
|
|
+ function AllocRecordBuffer: PChar; override; {virtual abstract}
|
|
|
+ procedure FreeRecordBuffer(var Buffer: PChar); override; {virtual abstract}
|
|
|
+ procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override; {virtual abstract}
|
|
|
+ function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override; {virtual abstract}
|
|
|
+ function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override; {virtual abstract}
|
|
|
+ function GetRecordSize: Word; override; {virtual abstract}
|
|
|
+ procedure InternalAddRecord(Buffer: Pointer; DoAppend: Boolean); override; {virtual abstract}
|
|
|
+ procedure InternalClose; override; {virtual abstract}
|
|
|
+ procedure InternalDelete; override; {virtual abstract}
|
|
|
+ procedure InternalFirst; override; {virtual abstract}
|
|
|
+ procedure InternalGotoBookmark(ABookmark: Pointer); override; {virtual abstract}
|
|
|
+ procedure InternalHandleException; override; {virtual abstract}
|
|
|
+ procedure InternalInitFieldDefs; override; {virtual abstract}
|
|
|
+ procedure InternalInitRecord(Buffer: PChar); override; {virtual abstract}
|
|
|
+ procedure InternalLast; override; {virtual abstract}
|
|
|
+ procedure InternalOpen; override; {virtual abstract}
|
|
|
+ procedure InternalPost; override; {virtual abstract}
|
|
|
+ procedure InternalSetToRecord(Buffer: PChar); override; {virtual abstract}
|
|
|
+ function IsCursorOpen: Boolean; override; {virtual abstract}
|
|
|
+ procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override; {virtual abstract}
|
|
|
+ procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override; {virtual abstract}
|
|
|
+ procedure SetFieldData(Field: TField; Buffer: Pointer); override; {virtual abstract}
|
|
|
+ {virtual methods (mostly optionnal) }
|
|
|
+
|
|
|
+ function GetRecordCount: Integer; override; {virtual}
|
|
|
+ function GetRecNo: Integer; override; {virtual}
|
|
|
+ procedure SetRecNo(Value: Integer); override; {virual}
|
|
|
+
|
|
|
+ public
|
|
|
+ constructor Create(AOwner: TComponent); override;
|
|
|
+ destructor Destroy; override;
|
|
|
+
|
|
|
+ published
|
|
|
+ property ComponentInfo: string read _ComponentInfo;
|
|
|
+ property TableName: string read _TableName write _TableName;
|
|
|
+ property RunTimePath: string read _RunTimePath write _RunTimePath;
|
|
|
+ property DesignTimePath: string read _DesignTimePath write _DesignTimePath;
|
|
|
+ property ReadOnly : Boolean read _ReadOnly write _Readonly default False;
|
|
|
+ property ShowDeleted:boolean read _ShowDeleted write _ShowDeleted;
|
|
|
+ // redeclared data set properties
|
|
|
+ property Active;
|
|
|
+ property Filtered;
|
|
|
+ property BeforeOpen;
|
|
|
+ property AfterOpen;
|
|
|
+ property BeforeClose;
|
|
|
+ property AfterClose;
|
|
|
+ property BeforeInsert;
|
|
|
+ property AfterInsert;
|
|
|
+ property BeforeEdit;
|
|
|
+ property AfterEdit;
|
|
|
+ property BeforePost;
|
|
|
+ property AfterPost;
|
|
|
+ property BeforeCancel;
|
|
|
+ property AfterCancel;
|
|
|
+ property BeforeDelete;
|
|
|
+ property AfterDelete;
|
|
|
+ property BeforeScroll;
|
|
|
+ property AfterScroll;
|
|
|
+ property OnCalcFields;
|
|
|
+ property OnDeleteError;
|
|
|
+ property OnEditError;
|
|
|
+ property OnFilterRecord;
|
|
|
+ property OnNewRecord;
|
|
|
+ property OnPostError;
|
|
|
+
|
|
|
+//my datas....
|
|
|
+ protected
|
|
|
+ _IsCursorOpen:boolean;
|
|
|
+ _PhysicalRecno:integer;
|
|
|
+ _CurIndex: TIndex;
|
|
|
+ _Indexes:TList; // index
|
|
|
+ _indexFile : TIndexFile;
|
|
|
+ _dbtFile : TDbtFile;
|
|
|
+ public
|
|
|
+ _dbfFile:TDbfFile;
|
|
|
+ property PhysicalRecno:integer read _PhysicalRecno;
|
|
|
+ function _RecordDataSize:integer;
|
|
|
+ end;
|
|
|
+
|
|
|
+{$ifndef fpc}
|
|
|
+procedure Register;
|
|
|
+{$endif}
|
|
|
+
|
|
|
+var
|
|
|
+ tDbf_TrimFields : boolean;
|
|
|
+
|
|
|
+implementation
|
|
|
+
|
|
|
+var
|
|
|
+ _PagedFiles : TList;
|
|
|
+
|
|
|
+//====================================================================
|
|
|
+// Some types and consts which are not useful in the interface.
|
|
|
+//====================================================================
|
|
|
+(*
|
|
|
+ * tSmallint 16 bits = -32768 to 32767
|
|
|
+ * 123456 = 6 digit max
|
|
|
+ * ftInteger 32 bits = -2147483648 to 2147483647
|
|
|
+ * 12345678901 = 11 digits max
|
|
|
+ * ftLargeInt 64 bits = -9223372036854775808 to 9223372036854775807
|
|
|
+ * 12345678901234567890 = 20 digits max
|
|
|
+ *)
|
|
|
+const
|
|
|
+ DIGITS_SMALLINT = 6;
|
|
|
+ DIGITS_INTEGER = 11;
|
|
|
+ DIGITS_LARGEINT = 20;
|
|
|
+ sDBF_DEC_SEP= '.';
|
|
|
+
|
|
|
+type
|
|
|
+ rAfterHdrIII = record // Empty
|
|
|
+ end;
|
|
|
+
|
|
|
+ rAfterHdrV = record
|
|
|
+ Dummy : array[32..67] of byte;
|
|
|
+ end;
|
|
|
+
|
|
|
+ PMdxTag = ^rMdxTag;
|
|
|
+
|
|
|
+ rMdxTagHdr = record
|
|
|
+ RootPage : longint;// 0..3
|
|
|
+ FilePages : longint;// 4..7
|
|
|
+ KeyFormat : byte; // 8
|
|
|
+ KeyType : char; // 9
|
|
|
+ dummy : word; // 10..11
|
|
|
+ IndexKeyLength : word; // 12..13
|
|
|
+ MaxNbKeys : word; // 14..15
|
|
|
+ SecondKeyType : word; // 16..17
|
|
|
+ IndexKeyItemLen : word; // 18..19
|
|
|
+ dummy2 : array [20..22] of byte;
|
|
|
+ UniqueFlag : byte; // 23
|
|
|
+ end;
|
|
|
+
|
|
|
+
|
|
|
+ rAfterHdrV3 = record
|
|
|
+ Dummy : array[12..31] of byte;
|
|
|
+ end;
|
|
|
+
|
|
|
+ rAfterHdrV4 = record
|
|
|
+ Dummy : array[12..67] of byte;
|
|
|
+ end;
|
|
|
+
|
|
|
+ rFieldHdrV3 = record
|
|
|
+ FieldName : array[0..10] of char;
|
|
|
+ FieldType : char; // 11
|
|
|
+ Dummy : array[12..15] of byte;
|
|
|
+ FieldSize : byte; // 16
|
|
|
+ FieldPrecision : byte; //17
|
|
|
+ dummy2 : array[18..31] of byte;
|
|
|
+ end;
|
|
|
+
|
|
|
+ rFieldHdrV4 = record
|
|
|
+ FieldName : array[0..10] of char;
|
|
|
+ Dummy0 : array[11..31] of byte;
|
|
|
+ FieldType : char; // 32
|
|
|
+ FieldSize : byte; // 33
|
|
|
+ FieldPrecision : byte; //34
|
|
|
+ dummy2 : array[35..47] of byte;
|
|
|
+ end;
|
|
|
+ PDouble = ^double;
|
|
|
+//====================================================================
|
|
|
+// Now some common functions and procedure
|
|
|
+//====================================================================
|
|
|
+// ****************************************************************************
|
|
|
+// International separator
|
|
|
+// thanks to Bruno Depero from Italy
|
|
|
+// and Andreas Wöllenstein from Denmark
|
|
|
+
|
|
|
+function DbfStrToFloat(s: string): Extended;
|
|
|
+var iPos: integer;
|
|
|
+ eValue: extended;
|
|
|
+begin
|
|
|
+ iPos:= Pos(sDBF_DEC_SEP, s);
|
|
|
+ if iPos> 0 then
|
|
|
+ s[iPos]:= DecimalSeparator;
|
|
|
+{$ifndef fpc}
|
|
|
+ if TextToFloat(pchar(s), eValue, fvExtended) then
|
|
|
+{$else}
|
|
|
+ Val(s,eValue,Ipos);
|
|
|
+ If Ipos=0 then
|
|
|
+{$endif}
|
|
|
+ Result:= eValue
|
|
|
+ else Result:= 0;
|
|
|
+end;
|
|
|
+
|
|
|
+function FloatToDbfStr(f: Extended; size, prec: integer): string;
|
|
|
+var iPos: integer;
|
|
|
+begin
|
|
|
+ Result:= FloatToStrF(f, ffFixed, Size, prec);
|
|
|
+ iPos:= Pos(DecimalSeparator, Result);
|
|
|
+ if iPos> 0 then
|
|
|
+ Result[iPos]:= sDBF_DEC_SEP;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure MyMove(Source, Dest:PChar; Count: Integer);
|
|
|
+var
|
|
|
+ c:char;
|
|
|
+ i:integer;
|
|
|
+begin
|
|
|
+ i:=0;
|
|
|
+ while i<Count do begin
|
|
|
+ c:=PChar(Source)[i];
|
|
|
+ if c=#0 then break;
|
|
|
+ PChar(Dest)[i]:=c;
|
|
|
+ Inc(i);
|
|
|
+ end;
|
|
|
+ while i<Count do begin
|
|
|
+ PChar(Dest)[i]:=' ';
|
|
|
+ Inc(i);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+//====================================================================
|
|
|
+// TPagedFile
|
|
|
+//====================================================================
|
|
|
+function GetPagedFile(FileName: string):TPagedFile;
|
|
|
+var
|
|
|
+ idx:integer;
|
|
|
+ idf:TPagedFile;
|
|
|
+begin
|
|
|
+ FileName:=LowerCase(FileName);
|
|
|
+ for idx:=0 to _PagedFiles.Count-1 do begin
|
|
|
+ idf:= TPagedFile(_PagedFiles[idx]);
|
|
|
+ if idf._FileName=FileName then begin
|
|
|
+ result:=idf;
|
|
|
+ exit;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ result:=nil;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TPagedFile.Release;
|
|
|
+begin
|
|
|
+ dec(_cntuse);
|
|
|
+ if _cntuse<=0 then begin
|
|
|
+ _PagedFiles.Delete(_PagedFiles.IndexOf(self));
|
|
|
+ Free;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TPagedFile.CalcRecordCount:Integer;
|
|
|
+begin
|
|
|
+ if RecordSize = 0 then Result:=0
|
|
|
+ else Result:=(Stream.Size - HeaderSize) div RecordSize;
|
|
|
+end;
|
|
|
+
|
|
|
+constructor TPagedFile.Create(const FileName: string; Mode: Word);
|
|
|
+begin
|
|
|
+ if filename='' then Stream:=TMemoryStream.Create()
|
|
|
+ else begin
|
|
|
+ Stream:=TFileStream.Create(FileName,Mode);
|
|
|
+ end;
|
|
|
+ HeaderSize:=0;
|
|
|
+ RecordSize:=0;
|
|
|
+ _cntuse:=0;
|
|
|
+ _filename:=lowercase(filename);
|
|
|
+ _PagedFiles.Add(Self);
|
|
|
+end;
|
|
|
+
|
|
|
+destructor TPagedFile.Destroy;
|
|
|
+begin
|
|
|
+ Stream.Free;
|
|
|
+ Stream:=nil;
|
|
|
+ inherited;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TPagedFile._Seek(page:Integer);
|
|
|
+var
|
|
|
+ p:Integer;
|
|
|
+begin
|
|
|
+ p:=HeaderSize + (RecordSize * page );
|
|
|
+ Stream.Position := p;
|
|
|
+end;
|
|
|
+
|
|
|
+Procedure TPagedFile.ReadRecord(IntRecNum:Integer; Buffer:Pointer);
|
|
|
+begin
|
|
|
+ _Seek(IntRecNum);
|
|
|
+ Stream.Read(Buffer^,RecordSize);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TPagedFile.WriteRecord(IntRecNum:Integer; Buffer:Pointer);
|
|
|
+begin
|
|
|
+ _Seek(IntRecNum);
|
|
|
+ Stream.Write(Buffer^, RecordSize);
|
|
|
+end;
|
|
|
+
|
|
|
+//====================================================================
|
|
|
+// TDbfFile
|
|
|
+//====================================================================
|
|
|
+constructor TDbfFile.Create(const FileName: string; Mode: Word);
|
|
|
+var
|
|
|
+ lRecordCount:Integer;
|
|
|
+begin
|
|
|
+ _MyFieldInfos:=TList.Create;
|
|
|
+ // check if the file exists
|
|
|
+ inherited Create(Filename, Mode);
|
|
|
+
|
|
|
+
|
|
|
+ if Mode = fmCreate then begin
|
|
|
+ FillChar(_DataHdr,sizeof(_DataHdr),0);
|
|
|
+ HeaderSize:=0;
|
|
|
+ RecordSize:=0;
|
|
|
+ _DataHdr.VerDBF:=$03; // Default version xBaseIV without memo
|
|
|
+ _DataHdr.Language:='X';
|
|
|
+ end else begin
|
|
|
+ Stream.Seek(0,soFromBeginning);
|
|
|
+ Stream.ReadBuffer (_DataHdr, SizeOf(_DataHdr));
|
|
|
+ case _DataHdr.VerDBF of
|
|
|
+ $03,$83: _DbfVersion:=xBaseIII;
|
|
|
+ $04,$8B,$8E,$7B: _DbfVersion:=xBaseIV;
|
|
|
+ $05 : _DbfVersion:=xbaseV;
|
|
|
+ else
|
|
|
+ _DbfVersion:=xBaseIV; // My favorite...
|
|
|
+ end;
|
|
|
+ HeaderSize:=_DataHdr.FullHdrSize;
|
|
|
+ RecordSize:=_DataHdr.RecordSize;
|
|
|
+ lRecordCount:=CalcRecordCount;
|
|
|
+ if _DataHdr.RecordCount <> lRecordCount then begin
|
|
|
+{$ifndef fpc}
|
|
|
+ ShowMessage('Invalid Record Count,'+^M+
|
|
|
+ 'RecordCount in Hdr : '+IntToStr(_DataHdr.RecordCount)+^M+
|
|
|
+ 'expected : '+IntToStr(lRecordCount));
|
|
|
+{$endif}
|
|
|
+ _DataHdr.RecordCount := lRecordCount;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+destructor TDbfFile.Destroy;
|
|
|
+begin
|
|
|
+ inherited;
|
|
|
+ ClearMyFieldInfos;
|
|
|
+ _MyFieldInfos.Free;
|
|
|
+ _MyFieldInfos:=nil;
|
|
|
+
|
|
|
+end;
|
|
|
+
|
|
|
+function TDbfFile.RecordCount:integer;
|
|
|
+begin
|
|
|
+ if RecordSize=0 then result:=0
|
|
|
+ else result:=(Stream.Size - HeaderSize) div RecordSize;
|
|
|
+ if result<0 then result:=0;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbfFile.ClearMyFieldInfos;
|
|
|
+var
|
|
|
+ i:Integer;
|
|
|
+begin
|
|
|
+ for i:=0 to _MyFieldInfos.Count-1 do begin
|
|
|
+ TMyFieldInfo(_MyFieldInfos.Items[i]).Free;
|
|
|
+ end;
|
|
|
+ _MyFieldInfos.Clear;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbfFile.CreateFieldDefs(FieldDefs:TFieldDefs);
|
|
|
+var
|
|
|
+ lColumnCount,lHeaderSize,lFieldSize:Integer;
|
|
|
+ Il : Integer;
|
|
|
+ lFieldOffset : Integer;
|
|
|
+ fn:string;
|
|
|
+ ft:TFieldType;
|
|
|
+ fs,nfs,fd:Integer;
|
|
|
+ MyFieldInfo:TMyFieldInfo;
|
|
|
+ lFieldHdrIII:rFieldHdrIII;
|
|
|
+ lFieldHdrV:rFieldHdrV;
|
|
|
+
|
|
|
+ function ToFieldType(dbasetype:char;fs,fd:Integer):TFieldType;
|
|
|
+ begin
|
|
|
+ case dbasetype of
|
|
|
+ 'C' :
|
|
|
+ begin
|
|
|
+ Result:=ftString;
|
|
|
+ end;
|
|
|
+ 'L' :
|
|
|
+ begin
|
|
|
+ Result:=ftBoolean;
|
|
|
+ end;
|
|
|
+ 'F' :
|
|
|
+ begin
|
|
|
+ Result:=ftFloat;
|
|
|
+ end;
|
|
|
+ 'N' :
|
|
|
+ begin
|
|
|
+ if fd=0 then begin
|
|
|
+ if fs <= DIGITS_SMALLINT then begin
|
|
|
+ Result:=ftSmallInt;
|
|
|
+ end else begin
|
|
|
+{$ifdef DELPHI_3}
|
|
|
+ Result:=ftInteger;
|
|
|
+{$else}
|
|
|
+ if fs <= DIGITS_INTEGER then Result:=ftInteger
|
|
|
+ else Result:=ftLargeInt;
|
|
|
+{$endif}
|
|
|
+ end;
|
|
|
+ end else begin
|
|
|
+ Result:=ftFloat;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ 'D' :
|
|
|
+ begin
|
|
|
+ Result:=ftDate;
|
|
|
+ end;
|
|
|
+ 'M' :
|
|
|
+ begin
|
|
|
+ Result:=ftMemo;
|
|
|
+ end;
|
|
|
+ else
|
|
|
+ begin
|
|
|
+ Result:=ftString;
|
|
|
+ end;
|
|
|
+ end; //case
|
|
|
+ end;
|
|
|
+begin
|
|
|
+ ClearMyFieldInfos;
|
|
|
+
|
|
|
+ if _DbfVersion>=xBaseV then begin
|
|
|
+ lHeaderSize:=SizeOf(rAfterHdrV) + SizeOf(rDbfHdr);
|
|
|
+ lFieldSize:=SizeOf(rFieldHdrV);
|
|
|
+ end else begin
|
|
|
+ lHeaderSize:=SizeOf(rAfterHdrIII) + SizeOf(rDbfHdr);
|
|
|
+ lFieldSize:=SizeOf(rFieldHdrIII);
|
|
|
+ end;
|
|
|
+ lColumnCount:= (_DataHdr.FullHdrSize - lHeaderSize) div lFieldSize;
|
|
|
+
|
|
|
+ if (lColumnCount <= 0) or (lColumnCount > 255) then
|
|
|
+ Raise eBinaryDataSetError.Create('Invalid field count : ' + IntToStr(lColumnCount) + ' (must be between 1 and 255)');
|
|
|
+
|
|
|
+ lFieldOffset := 1;
|
|
|
+
|
|
|
+ Stream.Position := lHeaderSize;
|
|
|
+ for Il:=0 to lColumnCount-1 do begin
|
|
|
+ if _DbfVersion>=xBaseV then begin
|
|
|
+ Stream.ReadBuffer(lFieldHdrV,SizeOf(lFieldHdrV));
|
|
|
+ fn:=PCHAR(@lFieldHdrV.FieldName[0]);
|
|
|
+ fs:=lFieldHdrV.FieldSize;
|
|
|
+ fd:=lFieldHdrV.FieldPrecision;
|
|
|
+ nfs:=fs;
|
|
|
+ ft:=ToFieldType(lFieldHdrV.FieldType,nfs,fd);
|
|
|
+ end else begin
|
|
|
+ Stream.ReadBuffer(lFieldHdrIII,SizeOf(lFieldHdrIII));
|
|
|
+ fn:=PCHAR(@lFieldHdrIII.FieldName[0]);
|
|
|
+ fs:=lFieldHdrIII.FieldSize;
|
|
|
+ fd:=lFieldHdrIII.FieldPrecision;
|
|
|
+ nfs:=fs;
|
|
|
+ ft:=ToFieldType(lFieldHdrIII.FieldType,nfs,fd);
|
|
|
+
|
|
|
+ end;
|
|
|
+ // first create the bde field
|
|
|
+ if ft in [ftString,ftBCD] then fieldDefs.Add(fn,ft,fs,false)
|
|
|
+ else fieldDefs.Add(fn,ft,0,false);
|
|
|
+ // then create the for our own fieldinfo
|
|
|
+ MyFieldInfo:=TMyFieldInfo.Create;
|
|
|
+ MyFieldInfo.Offset:=lFieldOffset;
|
|
|
+ MyFieldInfo.Size:=fs;
|
|
|
+ MyFieldInfo.Prec:=fd;
|
|
|
+ MyFieldInfo.FieldName:=lowercase(fn);
|
|
|
+
|
|
|
+ _MyFieldInfos.Add(MyFieldInfo);
|
|
|
+ Inc(lFieldOffset,fs);
|
|
|
+ end;
|
|
|
+ if (lFieldOffset <> _DataHdr.RecordSize) then begin
|
|
|
+{$ifndef fpc}
|
|
|
+ ShowMessage('Invalid Record Size,'+^M+
|
|
|
+ 'Record Size in Hdr : '+IntToStr(_DataHdr.RecordSize)+^M+
|
|
|
+ 'Expected : '+IntToStr(lFieldOffset));
|
|
|
+{$endif}
|
|
|
+ _DataHdr.RecordSize := lFieldOffset;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbfFile.DbfFile_CreateTable(FieldDefs:TFieldDefs);
|
|
|
+var
|
|
|
+ ix:Integer;
|
|
|
+ lFieldHdrIII:rFieldHdrIII;
|
|
|
+ lType:Char;
|
|
|
+ lSize,lPrec:Integer;
|
|
|
+ Offs:Integer;
|
|
|
+ lterminator:Byte;
|
|
|
+begin
|
|
|
+ // first reset file.
|
|
|
+ Stream.Size:= 0;
|
|
|
+ Stream.Position:=SizeOf(rDbfHdr) + SizeOf(rAfterHdrIII);
|
|
|
+ Offs:=1; // deleted mark count 1.
|
|
|
+ for Ix:=0 to FieldDefs.Count-1 do
|
|
|
+ begin
|
|
|
+ with FieldDefs.Items[Ix] do
|
|
|
+ begin
|
|
|
+ FillChar(lFieldHdrIII,SizeOf(lFieldHdrIII),#0);
|
|
|
+ lPrec:=0;
|
|
|
+ case DataType of
|
|
|
+ ftString:
|
|
|
+ begin
|
|
|
+ ltype:='C';
|
|
|
+ lSize := Size;
|
|
|
+ end;
|
|
|
+ ftBoolean:
|
|
|
+ begin
|
|
|
+ ltype:='L';
|
|
|
+ lSize := 1;
|
|
|
+ end;
|
|
|
+ ftSmallInt:
|
|
|
+ begin
|
|
|
+ ltype:='N';
|
|
|
+ lSize := 6;
|
|
|
+ end;
|
|
|
+ ftInteger:
|
|
|
+ begin
|
|
|
+ ltype:='N';
|
|
|
+ lSize := 11;
|
|
|
+ end;
|
|
|
+ ftCurrency:
|
|
|
+ begin
|
|
|
+ ltype:='N';
|
|
|
+ lSize := 20;
|
|
|
+ lPrec := 2;
|
|
|
+ end;
|
|
|
+{$ifndef DELPHI_3}
|
|
|
+ ftLargeInt:
|
|
|
+ begin
|
|
|
+ ltype:='N';
|
|
|
+ lSize := 20;
|
|
|
+ lPrec := 0;
|
|
|
+ end;
|
|
|
+{$endif}
|
|
|
+ ftFloat:
|
|
|
+ begin
|
|
|
+ ltype:='N';
|
|
|
+ lSize := 20;
|
|
|
+ lPrec := 4;
|
|
|
+ end;
|
|
|
+ ftDate:
|
|
|
+ begin
|
|
|
+ ltype:='D';
|
|
|
+ lSize := 8;
|
|
|
+ end;
|
|
|
+ ftMemo:
|
|
|
+ begin
|
|
|
+ ltype:='M';
|
|
|
+ lSize := 10;
|
|
|
+ end;
|
|
|
+ else
|
|
|
+ begin
|
|
|
+ raise EBinaryDataSetError.Create(
|
|
|
+ 'InitFieldDefs: Unsupported field type');
|
|
|
+ end;
|
|
|
+ end; // case
|
|
|
+
|
|
|
+ lFieldHdrIII.FieldType:=ltype; //DataType;
|
|
|
+ StrPCopy(lFieldHdrIII.FieldName,FieldDefs.Items[Ix].Name);
|
|
|
+ lFieldHdrIII.FieldSize:=lSize;
|
|
|
+ lFieldHdrIII.FieldPrecision:=lPrec;
|
|
|
+
|
|
|
+ Stream.Write(lFieldHdrIII,SizeOf(lFieldHdrIII));
|
|
|
+ Inc(Offs,lSize);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ // end of header
|
|
|
+ lterminator := $0d;
|
|
|
+ Stream.Write(lterminator,SizeOf(lterminator));
|
|
|
+
|
|
|
+ // update header
|
|
|
+ _DataHdr.RecordSize := Offs;
|
|
|
+ _DataHdr.FullHdrSize := Stream.Position;
|
|
|
+ RecordSize := _DataHdr.RecordSize;
|
|
|
+ HeaderSize := _DataHdr.FullHdrSize;
|
|
|
+ // write the updated header
|
|
|
+ WriteHeader;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbfFile.DbfFile_PackTable;
|
|
|
+var
|
|
|
+ first,last:integer;
|
|
|
+ p: Pointer;
|
|
|
+begin
|
|
|
+ // Non tested.
|
|
|
+ if (RecordSize <> 0) then
|
|
|
+ begin
|
|
|
+ first:=0;
|
|
|
+ last:=CalcRecordCount-1;
|
|
|
+ GetMem(p, RecordSize);
|
|
|
+ try
|
|
|
+ while first<last do begin
|
|
|
+ // first find the first hole
|
|
|
+ while first<last do begin
|
|
|
+ ReadRecord(first, p);
|
|
|
+ if (pRecordHdr(p)^.DeletedFlag <> ' ') then break;
|
|
|
+ inc(first);
|
|
|
+ end;
|
|
|
+ // now find last one non deleted.
|
|
|
+ while first<last do begin
|
|
|
+ ReadRecord(last, p);
|
|
|
+ if (pRecordHdr(p)^.DeletedFlag = ' ') then break;
|
|
|
+ dec(last);
|
|
|
+ end;
|
|
|
+ if first<last then begin
|
|
|
+ // found a non deleted record to put in the hole.
|
|
|
+ WriteRecord(first, p);
|
|
|
+ inc(first);
|
|
|
+ dec(last);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ last:=CalcRecordCount;
|
|
|
+ Stream.Size:=(last+1) * RecordSize + HeaderSize;
|
|
|
+ finally
|
|
|
+ FreeMem(p);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TDbfFile.GetFieldInfo(FieldName:string):TMyFieldInfo;
|
|
|
+var
|
|
|
+ i:Integer;
|
|
|
+ lfi:TMyFieldInfo;
|
|
|
+begin
|
|
|
+ FieldName:=LowerCase(FieldName);
|
|
|
+ for i:=0 to _MyFieldInfos.Count-1 do begin
|
|
|
+ lfi:=TMyFieldInfo(_MyFieldInfos.Items[i]);
|
|
|
+ if lfi.FieldName = FieldName then begin
|
|
|
+ result:=lfi;
|
|
|
+ exit;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ result:=nil;
|
|
|
+end;
|
|
|
+
|
|
|
+function TDbfFile.GetFieldData(Column:Integer;DataType:TFieldType; Src,Dst:Pointer): Boolean;
|
|
|
+var
|
|
|
+ FieldOffset: Integer;
|
|
|
+ FieldSize: Integer;
|
|
|
+ s:string;
|
|
|
+ d:TDateTime;
|
|
|
+ ld,lm,ly: word;
|
|
|
+ MyFieldInfo:TMyFieldInfo;
|
|
|
+ function TrimStr(const s: string): string;
|
|
|
+ var
|
|
|
+ iPos: integer;
|
|
|
+ begin
|
|
|
+ if DataType=ftString then
|
|
|
+ begin
|
|
|
+ if tDbf_TrimFields then Result:=Trim(s)
|
|
|
+ else Result:=TrimRight(s);
|
|
|
+ end
|
|
|
+ else Result:= Trim(s);
|
|
|
+ end;
|
|
|
+ procedure CorrectYear(var wYear: word);
|
|
|
+ var wD, wM, wY, CenturyBase: word;
|
|
|
+{$ifdef DELPHI_3}
|
|
|
+ // Delphi 3 standard-behavior no change possible
|
|
|
+ const TwoDigitYearCenturyWindow= 0;
|
|
|
+{$endif}
|
|
|
+ begin
|
|
|
+ if wYear>= 100 then
|
|
|
+ Exit;
|
|
|
+ DecodeDate(Date, wY, wm, wD);
|
|
|
+ // use Delphi-Date-Window
|
|
|
+ CenturyBase := wY{must be CurrentYear} - TwoDigitYearCenturyWindow;
|
|
|
+ Inc(wYear, CenturyBase div 100 * 100);
|
|
|
+ if (TwoDigitYearCenturyWindow > 0) and (wYear < CenturyBase) then
|
|
|
+ Inc(wYear, 100);
|
|
|
+ end;
|
|
|
+begin
|
|
|
+ MyFieldInfo:=TMyFieldInfo(_MyFieldInfos.Items[Column]);
|
|
|
+ FieldOffset := MyFieldInfo.Offset;
|
|
|
+ FieldSize := MyFieldInfo.Size;
|
|
|
+ SetString(s, PChar(Src) + FieldOffset, FieldSize );
|
|
|
+ s:=TrimStr(s);
|
|
|
+ result:=length(s)>0; // return if field is empty
|
|
|
+ if Result and (Dst<>nil) then// data not needed if Result= FALSE or Dst=nil
|
|
|
+ case DataType of
|
|
|
+ ftBoolean:
|
|
|
+ begin
|
|
|
+ // in DBase- FileDescription lowercase t is allowed too
|
|
|
+ // with asking for Result= TRUE s must be longer then 0
|
|
|
+ // else it happens an AV, maybe field is NULL
|
|
|
+ if (UpCase(s[1])='T') then Word(Dst^) := 1
|
|
|
+ else Word(Dst^) := 0;
|
|
|
+ end;
|
|
|
+ ftInteger, ftSmallInt{$ifndef DELPHI_3},ftLargeInt{$endif}:
|
|
|
+ begin
|
|
|
+ case DataType of
|
|
|
+ ftSmallInt : SmallInt(Dst^):= StrToIntDef(s, 0);
|
|
|
+ {$ifndef DELPHI_3}
|
|
|
+ ftLargeint : LargeInt(Dst^):= StrToInt64Def(s, 0);
|
|
|
+ {$endif}
|
|
|
+ else // ftInteger :
|
|
|
+ Integer(Dst^):= StrToIntDef(s, 0);
|
|
|
+ end; // case
|
|
|
+ end;
|
|
|
+ ftFloat:
|
|
|
+ begin
|
|
|
+ Double(Dst^) := DBFStrToFloat(s);
|
|
|
+ end;
|
|
|
+ ftCurrency:
|
|
|
+ begin
|
|
|
+ Double(Dst^) := DBFStrToFloat(s);
|
|
|
+ end;
|
|
|
+ ftDate:
|
|
|
+ begin
|
|
|
+ ld:=StrToIntDef(Copy(s,7,2),1);
|
|
|
+ lm:=StrToIntDef(Copy(s,5,2),1);
|
|
|
+ ly:=StrToIntDef(Copy(s,1,4),0);
|
|
|
+ if ld=0 then ld:=1;
|
|
|
+ if lm=0 then lm:=1;
|
|
|
+// if (ly<1900) or (ly>2100) then ly:=1900;
|
|
|
+// Year from 0001 to 9999 is possible
|
|
|
+// everyting else is an error, an empty string too
|
|
|
+// Do DateCorrection with Delphis possibillities for one or two digits
|
|
|
+ if (ly< 100) and (Length(Trim(Copy(s,1,4)))in [1, 2]) then CorrectYear(ly);
|
|
|
+ try
|
|
|
+ d:=EncodeDate(ly,lm,ld);
|
|
|
+ if Assigned(Dst) then Integer(Dst^) := DateTimeToTimeStamp(d).Date;
|
|
|
+ except
|
|
|
+ Integer(Dst^) := 0;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ ftString: begin
|
|
|
+ StrPCopy(Dst,s);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbfFile.SetFieldData(Column:integer;DataType:TFieldType; Src,Dst:Pointer);
|
|
|
+var
|
|
|
+ FieldSize,FieldPrec: Integer;
|
|
|
+ s:string;
|
|
|
+ fl:Double;
|
|
|
+ ts:TTimeStamp;
|
|
|
+ MyFieldInfo:TMyFieldInfo;
|
|
|
+begin
|
|
|
+ MyFieldInfo:=TMyFieldInfo(_MyFieldInfos.Items[Column]);
|
|
|
+ FieldSize := MyFieldInfo.Size;
|
|
|
+ FieldPrec := MyFieldInfo.Prec;
|
|
|
+
|
|
|
+ Dst:=PChar(Dst)+MyFieldInfo.Offset;
|
|
|
+ if src<>nil then begin
|
|
|
+ case DataType of
|
|
|
+ ftBoolean:
|
|
|
+ begin
|
|
|
+ if Word(Src^) = 1 then s:='T'
|
|
|
+ else s:='F';
|
|
|
+ end;
|
|
|
+ ftInteger, ftSmallInt {$ifndef DELPHI_3},ftLargeInt{$endif}:
|
|
|
+ begin
|
|
|
+ case DataType of
|
|
|
+ ftSmallInt : s:= IntToStr(SmallInt(Src^));
|
|
|
+ {$ifndef DELPHI_3}
|
|
|
+ ftLargeInt: s:= IntToStr(LargeInt(Src^));
|
|
|
+ {$endif}
|
|
|
+ else //ftInteger
|
|
|
+ s:= IntToStr(Integer(Src^));
|
|
|
+ end;
|
|
|
+ // left filling
|
|
|
+ if Length(s)<FieldSize then s:=StringOfChar(' ',FieldSize-Length(s)) + s;
|
|
|
+ end;
|
|
|
+ ftFloat,ftCurrency:
|
|
|
+ begin
|
|
|
+ fl := Double(Src^);
|
|
|
+ s:=FloatToDbfStr(fl,FieldSize,FieldPrec);
|
|
|
+ if Length(s)<FieldSize then s:=StringOfChar(' ',FieldSize-Length(s)) + s;
|
|
|
+ end;
|
|
|
+ ftDate:
|
|
|
+ begin
|
|
|
+ ts.Time:=0;
|
|
|
+ ts.Date:=Integer(Src^);
|
|
|
+ s:= FormatDateTime('yyyymmdd', TimeStampToDateTime(ts));
|
|
|
+ end;
|
|
|
+ ftString:
|
|
|
+ begin
|
|
|
+ s:=PChar(Src); // finish with first 0
|
|
|
+ end;
|
|
|
+ end; // case
|
|
|
+ end; // if src<>nil (thanks andreas)
|
|
|
+ if Length(s)<FieldSize then begin
|
|
|
+ s:=s+StringOfChar(' ',FieldSize-Length(s));
|
|
|
+ end else if (Length(s)>FieldSize) then begin
|
|
|
+ if DataType= ftString then begin
|
|
|
+ // never raise for strings to long, its not customary
|
|
|
+ // TTable never raises
|
|
|
+ SetLength(s, FieldSize)
|
|
|
+ end else begin
|
|
|
+ raise eFieldToLongError.Create('Fielddata too long :' + IntToStr(Length(s))
|
|
|
+ + ' (must be between 1 and ' + IntToStr(FieldSize) + ').');
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ Move(PChar(s)^, Dst^, FieldSize);
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+procedure TDbfFile.WriteHeader;
|
|
|
+var
|
|
|
+ SystemTime: TSystemTime;
|
|
|
+ lAfterHdrIII:rAfterHdrIII;
|
|
|
+ lAfterHdrV:rAfterHdrV;
|
|
|
+ lterminator:Byte;
|
|
|
+begin
|
|
|
+ Assert(Stream<>nil,'_dbfFile=Nil');
|
|
|
+
|
|
|
+ Stream.Position:=0;
|
|
|
+ GetLocalTime(SystemTime);
|
|
|
+{$ifndef fpc}
|
|
|
+ _DataHdr.Year := SystemTime.wYear - 1900;
|
|
|
+ _DataHdr.Month := SystemTime.wMonth;
|
|
|
+ _DataHdr.Day := SystemTime.wDay;
|
|
|
+{$else}
|
|
|
+ _DataHdr.Year := SystemTime.Year - 1900;
|
|
|
+ _DataHdr.Month := SystemTime.Month;
|
|
|
+ _DataHdr.Day := SystemTime.Day;
|
|
|
+{$endif}
|
|
|
+ Stream.Seek(0,soFromBeginning);
|
|
|
+ Stream.WriteBuffer (_DataHdr, SizeOf(_DataHdr));
|
|
|
+ _DataHdr.RecordCount := CalcRecordCount;
|
|
|
+
|
|
|
+ if _DbfVersion >= xBaseV then begin
|
|
|
+ FillChar(lAfterHdrV,SizeOf(lAfterHdrV),0);
|
|
|
+ Stream.WriteBuffer (lAfterHdrV, SizeOf(lAfterHdrV));
|
|
|
+ end else begin
|
|
|
+ FillChar(lAfterHdrIII,SizeOf(lAfterHdrIII),0);
|
|
|
+ Stream.WriteBuffer (lAfterHdrIII, SizeOf(lAfterHdrIII));
|
|
|
+ end;
|
|
|
+ _Seek(_DataHdr.RecordCount); // last byte usually...
|
|
|
+ lterminator := $1A;
|
|
|
+ Stream.Write(lterminator,SizeOf(lterminator));
|
|
|
+end;
|
|
|
+
|
|
|
+function TDbf._ComponentInfo:string;
|
|
|
+begin
|
|
|
+ Result:='TDbf V' + IntToStr(_MAJOR_VERSION) + '.' + IntToStr(_MINOR_VERSION);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf._OpenFiles(CreateIt:boolean);
|
|
|
+var
|
|
|
+ fileopenmode : integer;
|
|
|
+ lPath,lFilename,lIndexName,lMemoName : string;
|
|
|
+ isAbsolute:boolean;
|
|
|
+ design,doreadonly:boolean;
|
|
|
+
|
|
|
+begin
|
|
|
+ design:=(csDesigning in ComponentState);
|
|
|
+ doreadonly:=design or _ReadOnly;
|
|
|
+
|
|
|
+ lPath:=_GetPath;
|
|
|
+ isAbsolute:=((length(_TableName)>=1) and (_TableName[1]='\'))
|
|
|
+ or ((length(_TableName)>=2) and (_TableName[2]=':'));
|
|
|
+ if isAbsolute then lfilename:=_TableName
|
|
|
+ else lFilename:=lPath+_TableName;
|
|
|
+ lFilename:=ChangeFileExt(lFilename,'.dbf');
|
|
|
+ lIndexName:=ChangeFileExt(lFilename,'.mdx');
|
|
|
+ lMemoName:=ChangeFileExt(lFilename,'.dbt');
|
|
|
+
|
|
|
+ // check if the file exists
|
|
|
+ _dbfFile:=TDbfFile(GetPagedFile(lFileName));
|
|
|
+ _indexFile:=TIndexFile(GetPagedFile(lIndexName));
|
|
|
+ _dbtFile:=TDbtFile(GetPagedFile(lMemoName));
|
|
|
+
|
|
|
+ if CreateIt then begin
|
|
|
+ if _dbfFile=nil then _dbfFile:=TDbfFile.Create(lFileName,fmCreate);
|
|
|
+ //if _indexfile=nil then _indexFile := TIndexFile.Create(lIndexName, fmCreate);
|
|
|
+ if _dbtfile=nil then _dbtFile := TDbtFile.Create(lMemoName, fmCreate,_dbfFile._DbfVersion);
|
|
|
+ end else if not FileExists(lFileName) then begin
|
|
|
+ raise eBinaryDataSetError.Create ('Open: Table file not found : ' + lFileName);
|
|
|
+ end else begin
|
|
|
+ if DoReadOnly then
|
|
|
+ fileopenmode := fmOpenRead + fmShareDenyNone
|
|
|
+ else
|
|
|
+ fileopenmode := fmOpenReadWrite + fmShareDenyWrite;
|
|
|
+
|
|
|
+ if _dbfFile=nil then _dbfFile := TDBFFile.Create(lFileName, fileopenmode);
|
|
|
+ if (_indexFile=nil) and FileExists (lIndexName) then begin
|
|
|
+ _indexFile := TIndexFile.Create(lIndexName, fileopenmode);
|
|
|
+ end;
|
|
|
+ if (_dbtFile=nil) and FileExists (lMemoName) then begin
|
|
|
+ _dbtFile := TDbtFile.Create(lMemoName, fileopenmode,_dbfFile._DbfVersion);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ _PrevBuffer:=AllocRecordBuffer;
|
|
|
+ _IsCursorOpen:=true;
|
|
|
+
|
|
|
+end;
|
|
|
+
|
|
|
+function TDbf._GetPath:string;
|
|
|
+var
|
|
|
+ lPath:string;
|
|
|
+begin
|
|
|
+ if (csDesigning in ComponentState) then begin
|
|
|
+ lPath:=_DesignTimePath;
|
|
|
+ end else begin
|
|
|
+ if ((length(_RunTimePath)>=1) and (_RunTimePath[1]=DirSeparator))
|
|
|
+ or ((length(_RunTimePath)>=2) and (_RunTimePath[2]=':'))
|
|
|
+ then begin
|
|
|
+ // if the _RunTimePath is absolute...
|
|
|
+ // it is either \ or \blahblah or c:\
|
|
|
+ lPath:=_RunTimePath;
|
|
|
+ end else begin
|
|
|
+{$ifndef fpc}
|
|
|
+ lPath:=extractfilepath(Application.Exename)+_RunTimePath;
|
|
|
+{$else}
|
|
|
+ lPath:=extractfilepath(paramstr(0))+_RunTimePath;
|
|
|
+{$endif}
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ lPath:=ExpandFileName(trim(lPath));
|
|
|
+ if (length(lPath)>0) and (lPath[length(lPath)]<>DirSeparator) then lPath:=lPath+DirSeparator;
|
|
|
+ result:=lPath;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf._CloseFiles;
|
|
|
+var
|
|
|
+ i:integer;
|
|
|
+begin
|
|
|
+ if _dbfFile<>nil then begin
|
|
|
+ if not _ReadOnly then _dbfFile.WriteHeader;
|
|
|
+ _dbfFile.Release;
|
|
|
+ _dbfFile:=nil;
|
|
|
+ end;
|
|
|
+ if _indexFile<>nil then begin
|
|
|
+ _indexFile.Release;
|
|
|
+ _indexFile:=nil;
|
|
|
+ end;
|
|
|
+
|
|
|
+ if _dbtFile<>nil then begin
|
|
|
+ _dbtFile.Release;
|
|
|
+ _dbtFile:=nil;
|
|
|
+ end;
|
|
|
+
|
|
|
+ if _indexes<>nil then begin
|
|
|
+ for i:=0 to _Indexes.Count-1 do begin
|
|
|
+ TIndex(_Indexes[i]).Free;
|
|
|
+ end;
|
|
|
+ _Indexes.Clear;
|
|
|
+ _CurIndex:=nil;
|
|
|
+ end;
|
|
|
+ if (_PrevBuffer<>nil) then begin
|
|
|
+ FreeRecordBuffer(_PrevBuffer);
|
|
|
+ _PrevBuffer:=nil;
|
|
|
+ end;
|
|
|
+ _IsCursorOpen:=false;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf._SetIndexName(const Value: string);
|
|
|
+begin
|
|
|
+ _CurIndex:=_GetIndex(Value);
|
|
|
+ Resync([]);
|
|
|
+end;
|
|
|
+
|
|
|
+function TDbf._GetIndexName: string;
|
|
|
+begin
|
|
|
+ if _CurIndex=nil then Result:=''
|
|
|
+ else Result:=_CurIndex._IndexFile._Filename;
|
|
|
+end;
|
|
|
+
|
|
|
+function TDbf._GetIndex(filename:string):TIndex;
|
|
|
+var
|
|
|
+ i:integer;
|
|
|
+ lindex:TIndex;
|
|
|
+begin
|
|
|
+ result:=nil;
|
|
|
+ filename:=lowercase(_GetPath + filename);
|
|
|
+ for i:=0 to _indexes.Count-1 do begin
|
|
|
+ lindex:=TIndex(_indexes.Items[i]);
|
|
|
+ if lindex._IndexFile._Filename=filename then begin
|
|
|
+ result:=lindex;
|
|
|
+ exit;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+//==========================================================
|
|
|
+//============ TMyBlobFile
|
|
|
+//==========================================================
|
|
|
+constructor TMyBlobFile.Create(ModeVal:TBlobStreamMode;FieldVal:TField);
|
|
|
+begin
|
|
|
+ Mode:=ModeVal;
|
|
|
+ Field:=FieldVal;
|
|
|
+end;
|
|
|
+
|
|
|
+destructor TMyBlobFile.destroy;
|
|
|
+var
|
|
|
+ Dbf:TDbf;
|
|
|
+begin
|
|
|
+ if (Mode=bmWrite) then begin
|
|
|
+ Size:=Position; // Strange but it leave tailing trash bytes if I do not write that.
|
|
|
+ Dbf:=TDbf(Field.DataSet);
|
|
|
+ Dbf._dbtFile.WriteMemo(MemoRecno,ReadSize,Self);
|
|
|
+
|
|
|
+ Dbf._dbfFile.SetFieldData(Field.FieldNo-1,
|
|
|
+ ftInteger,@MemoRecno,@pDbfRecord(TDbf(Field.DataSet).ActiveBuffer)^.deletedflag);
|
|
|
+ // seems not bad
|
|
|
+{$ifndef fpc}
|
|
|
+ // FPC doesn't allow to call protected methods ?!!
|
|
|
+ Dbf.SetModified(true);
|
|
|
+{$endif}
|
|
|
+ // but would that be better
|
|
|
+ //if not (State in [dsCalcFields, dsFilter, dsNewValue]) then begin
|
|
|
+ // DataEvent(deFieldChange, Longint(Field));
|
|
|
+ //end;
|
|
|
+ end;
|
|
|
+ inherited;
|
|
|
+end;
|
|
|
+
|
|
|
+//====================================================================
|
|
|
+// TDbf = TDataset Descendant.
|
|
|
+//====================================================================
|
|
|
+constructor TDbf.Create(AOwner: TComponent); {override;}
|
|
|
+begin
|
|
|
+ inherited create(aOwner);
|
|
|
+ BookmarkSize:=sizeof(rBookmarkData);
|
|
|
+
|
|
|
+ _RunTimePath:='.';
|
|
|
+ _IsCursorOpen:=false;
|
|
|
+ _Indexes:=TList.Create;
|
|
|
+ _CurIndex:=nil;
|
|
|
+ _IndexFiles:=TStringList.Create;
|
|
|
+end;
|
|
|
+
|
|
|
+destructor TDbf.Destroy; {override;}
|
|
|
+var
|
|
|
+ i:integer;
|
|
|
+begin
|
|
|
+ inherited;
|
|
|
+ _CurIndex:=nil;
|
|
|
+ for i:=0 to _Indexes.Count-1 do begin
|
|
|
+ TIndex(_Indexes[i]).Free;
|
|
|
+ end;
|
|
|
+ _Indexes.Free;
|
|
|
+ _IndexFiles.Free;
|
|
|
+// _MemIndex.Free;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+function TDbf._FilterRecord(Buffer: PChar): Boolean;
|
|
|
+var
|
|
|
+ SaveState: TDatasetState;
|
|
|
+ s:string;
|
|
|
+begin
|
|
|
+ Result:=True;
|
|
|
+ if Length(easyfilter)<>0 then begin
|
|
|
+ SetString(s,buffer,RecordSize);
|
|
|
+ s:=LowerCase(s);
|
|
|
+ if Pos(easyfilter,s)=0 then begin
|
|
|
+ Result:=False;
|
|
|
+ Exit;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ if not Assigned(OnFilterRecord) then Exit;
|
|
|
+ if not Filtered then Exit;
|
|
|
+ _FilterBuffer:=buffer;
|
|
|
+ SaveState:=SetTempState(dsFilter);
|
|
|
+ OnFilterRecord(self,Result);
|
|
|
+ RestoreState(SaveState);
|
|
|
+end;
|
|
|
+
|
|
|
+function TDbf._RecordDataSize:integer;
|
|
|
+begin
|
|
|
+ if _dbfFile=nil then result:=0
|
|
|
+ else result:=_dbfFile.RecordSize;
|
|
|
+end;
|
|
|
+
|
|
|
+function TDbf._FullRecordSize:integer;
|
|
|
+begin
|
|
|
+ result:=sizeof(rBeforeRecord) + _RecordDataSize + CalcFieldsSize;
|
|
|
+end;
|
|
|
+
|
|
|
+function TDbf.AllocRecordBuffer: PChar; {override virtual abstract from TDataset}
|
|
|
+begin
|
|
|
+ result:=StrAlloc(_FullRecordSize);
|
|
|
+ InternalInitRecord(result);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.FreeRecordBuffer(var Buffer: PChar); {override virtual abstract from TDataset}
|
|
|
+begin
|
|
|
+ StrDispose(Buffer);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.GetBookmarkData(Buffer: PChar; Data: Pointer); {override virtual abstract from TDataset}
|
|
|
+var
|
|
|
+ prec:pDbfRecord;
|
|
|
+begin
|
|
|
+ prec:=pDbfRecord(Buffer);
|
|
|
+ pBookMarkData(Data)^:=prec^.BookMarkData;
|
|
|
+end;
|
|
|
+
|
|
|
+function TDbf.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; {override virtual abstract from TDataset}
|
|
|
+var
|
|
|
+ prec:pDbfRecord;
|
|
|
+begin
|
|
|
+ prec:=pDbfRecord(Buffer);
|
|
|
+ result:=prec^.BookMarkFlag;
|
|
|
+end;
|
|
|
+
|
|
|
+function TDbf.GetFieldData(Field: TField; Buffer: Pointer): Boolean; {override virtual abstract from TDataset}
|
|
|
+var
|
|
|
+ ptr:pointer;
|
|
|
+begin
|
|
|
+ Result := False;
|
|
|
+ if State=dsFilter then begin
|
|
|
+ Ptr:=_FilterBuffer;
|
|
|
+ end else if State = dsCalcFields then begin
|
|
|
+ // ***** calc fields ***** set correct buffer
|
|
|
+ ptr := @(pDbfRecord(CalcBuffer)^.deletedflag);
|
|
|
+ end else begin
|
|
|
+ if IsEmpty then exit;
|
|
|
+ ptr:=@(pDbfRecord(ActiveBuffer)^.deletedflag);
|
|
|
+ end;
|
|
|
+
|
|
|
+ if Field.FieldNo>0 then begin
|
|
|
+ Result:=_dbfFile.GetFieldData(Field.FieldNo - 1,Field.DataType,ptr,Buffer);
|
|
|
+ end else begin { calculated fields.... }
|
|
|
+ Inc(PChar(Ptr), Field.Offset + GetRecordSize);
|
|
|
+{$ifndef fpc}
|
|
|
+ Result := Boolean(PChar(Ptr)[0]);
|
|
|
+{$else}
|
|
|
+ Result := (Pchar(ptr)[0]<>#0);
|
|
|
+{$endif}
|
|
|
+ if Result and (Buffer <> nil) then
|
|
|
+ Move(PChar(Ptr)[1], Buffer^, Field.DataSize);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+function TDbf.GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; {override virtual abstract from TDataset}
|
|
|
+var
|
|
|
+ Acceptable : Boolean;
|
|
|
+ prec:pDBFRecord;
|
|
|
+begin
|
|
|
+ prec:=pDBFRecord(Buffer);
|
|
|
+ if _dbfFile.RecordCount < 1 then
|
|
|
+ Result := grEOF
|
|
|
+ else repeat
|
|
|
+ result := grOk;
|
|
|
+ case GetMode of
|
|
|
+ gmCurrent :
|
|
|
+ begin
|
|
|
+ if prec^.BookmarkData.Recno=_PhysicalRecno then begin
|
|
|
+ exit; // try to fasten a bit...
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ gmNext :
|
|
|
+ begin
|
|
|
+ if _curIndex<>nil then begin
|
|
|
+ Acceptable:=_curIndex.Next;
|
|
|
+ end else begin
|
|
|
+ inc(_PhysicalRecno);
|
|
|
+ Acceptable:=(_PhysicalRecno<_dbfFile.RecordCount);
|
|
|
+ end;
|
|
|
+ if Acceptable then begin
|
|
|
+ result:= grOk;
|
|
|
+ end else begin
|
|
|
+ InternalLast;
|
|
|
+ result:= grEOF
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ gmPrior :
|
|
|
+ begin
|
|
|
+ if _curIndex<>nil then begin
|
|
|
+ Acceptable:=_curIndex.Prev;
|
|
|
+ end else begin
|
|
|
+ dec(_PhysicalRecno);
|
|
|
+ Acceptable:=(_PhysicalRecno>=0);
|
|
|
+ end;
|
|
|
+ if Acceptable then begin
|
|
|
+ result:= grOk;
|
|
|
+ end else begin
|
|
|
+ InternalFirst;
|
|
|
+ result:= grBOF
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ if result=grOk then begin
|
|
|
+ if _curIndex<>nil then _PhysicalRecno:=_CurIndex.GetRealRecNo;
|
|
|
+ if (_PhysicalRecno>=_dbfFile.RecordCount)
|
|
|
+ or (_PhysicalRecno<0) then begin
|
|
|
+ result:=grError;
|
|
|
+ end else begin
|
|
|
+ _dbfFile.ReadRecord(_PhysicalRecno,@prec^.DeletedFlag);
|
|
|
+ result:=grOk;
|
|
|
+ end;
|
|
|
+ if Result = grOK then begin
|
|
|
+ ClearCalcFields(Buffer);
|
|
|
+ GetCalcFields(Buffer);
|
|
|
+ prec^.BookmarkFlag := bfCurrent;
|
|
|
+ prec^.BookmarkData.Recno:=PhysicalRecno;
|
|
|
+ end else if (Result = grError) and DoCheck then
|
|
|
+ raise eBinaryDataSetError.Create ('GetRecord: Invalid record');
|
|
|
+ end;
|
|
|
+ Acceptable := (_ShowDeleted or (prec^.DeletedFlag = ' '))
|
|
|
+ and _FilterRecord(Buffer);
|
|
|
+ if (GetMode=gmCurrent) and Not Acceptable then Result := grError;
|
|
|
+ until (Result <> grOK) or Acceptable;
|
|
|
+end;
|
|
|
+
|
|
|
+function TDbf.GetRecordSize: Word; {override virtual abstract from TDataset}
|
|
|
+begin
|
|
|
+ Result := _RecordDataSize; // data only
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.InternalAddRecord(Buffer: Pointer; DoAppend: Boolean); {override virtual abstract from TDataset}
|
|
|
+begin
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.InternalClose; {override virtual abstract from TDataset}
|
|
|
+begin
|
|
|
+ _CloseFiles;
|
|
|
+
|
|
|
+ // disconnect field objects
|
|
|
+ BindFields(False);
|
|
|
+ // destroy field object (if not persistent)
|
|
|
+ if DefaultFields then
|
|
|
+ DestroyFields;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.InternalDelete; {override virtual abstract from TDataset}
|
|
|
+begin
|
|
|
+// CheckActive;
|
|
|
+ pRecordHdr(ActiveBuffer)^.DeletedFlag := '*'; //_DataHdr.LastDeleted;
|
|
|
+ _dbfFile.WriteRecord(_PhysicalRecNo,ActiveBuffer);
|
|
|
+ Resync([]);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.InternalFirst; {override virtual abstract from TDataset}
|
|
|
+begin
|
|
|
+ if _dbfFile.RecordCount=0 then InternalLast
|
|
|
+ else if _curindex=nil then _PhysicalRecno:=-1
|
|
|
+ else _curIndex.First;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.InternalGotoBookmark(ABookmark: Pointer); {override virtual abstract from TDataset}
|
|
|
+var
|
|
|
+ RecInfo: TRecInfo;
|
|
|
+begin
|
|
|
+ RecInfo := TRecInfo(ABookmark^);
|
|
|
+ if (RecInfo.Bookmark >= 0) and (RecInfo.Bookmark < _dbfFile.RecordCount) then begin
|
|
|
+ _PhysicalRecno:=RecInfo.Bookmark;
|
|
|
+ end else
|
|
|
+ raise eBinaryDataSetError.Create ('Bookmark ' +
|
|
|
+ IntToStr (RecInfo.Bookmark) + ' not found');
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.InternalHandleException; {override virtual abstract from TDataset}
|
|
|
+begin
|
|
|
+{$ifndef fpc}
|
|
|
+ Application.HandleException(Self);
|
|
|
+{$endif}
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.InternalInitFieldDefs; {override virtual abstract from TDataset}
|
|
|
+begin
|
|
|
+ FieldDefs.Clear;
|
|
|
+ with FieldDefs do
|
|
|
+ begin
|
|
|
+ if IsCursorOpen then begin
|
|
|
+ _dbfFile.CreateFieldDefs(FieldDefs);
|
|
|
+ end else begin
|
|
|
+ _OpenFiles(false);
|
|
|
+ _dbfFile.CreateFieldDefs(FieldDefs);
|
|
|
+ Close();
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.InternalInitRecord(Buffer: PChar); {override virtual abstract from TDataset}
|
|
|
+var
|
|
|
+ prec:pDbfRecord;
|
|
|
+begin
|
|
|
+ prec:=pDbfRecord(Buffer);
|
|
|
+ prec^.BookmarkData.RecNo:=-1;
|
|
|
+ prec^.BookmarkFlag:=TBookmarkFlag(0);
|
|
|
+ fillchar(prec^.DeletedFlag,_RecordDataSize,' ');
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.InternalLast; {override virtual abstract from TDataset}
|
|
|
+begin
|
|
|
+ if _curindex=nil then _PhysicalRecno:=_dbfFile.RecordCount
|
|
|
+ else _curIndex.Last;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.InternalOpen; {override virtual abstract from TDataset}
|
|
|
+begin
|
|
|
+ _OpenFiles(false);
|
|
|
+ // if there are no persistent field objects,
|
|
|
+ InternalInitFieldDefs;
|
|
|
+ // create the fields dynamically
|
|
|
+ if DefaultFields then begin
|
|
|
+ CreateFields;
|
|
|
+ end;
|
|
|
+ BindFields (True);
|
|
|
+ // connect the TField objects with the actual fields
|
|
|
+
|
|
|
+ InternalFirst;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.InternalPost; {override virtual abstract from TDataset}
|
|
|
+var
|
|
|
+ prec:pDbfRecord;
|
|
|
+ lIndex:TIndex;
|
|
|
+ i:integer;
|
|
|
+begin
|
|
|
+ CheckActive;
|
|
|
+ prec:=pDbfRecord(ActiveBuffer);
|
|
|
+ prec^.DeletedFlag:=' ';
|
|
|
+
|
|
|
+ if State = dsEdit then
|
|
|
+ begin
|
|
|
+ // replace data with new data
|
|
|
+ if _indexes.Count>0 then begin
|
|
|
+ _dbfFile.ReadRecord(_PhysicalRecno,_PrevBuffer);
|
|
|
+ for i:=0 to _indexes.Count-1 do begin
|
|
|
+ lindex:=TIndex(_indexes.Items[i]);
|
|
|
+ lindex.Update(_PhysicalRecno,_PrevBuffer,@prec^.DeletedFlag);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ end else begin
|
|
|
+ // append
|
|
|
+ _PhysicalRecno:=_dbfFile._DataHdr.RecordCount;
|
|
|
+ inc(_dbfFile._DataHdr.RecordCount);
|
|
|
+ if _indexes.Count>0 then begin
|
|
|
+ _dbfFile.ReadRecord(_PhysicalRecno,_PrevBuffer);
|
|
|
+ for i:=0 to _indexes.Count-1 do begin
|
|
|
+ lindex:=TIndex(_indexes.Items[i]);
|
|
|
+ lindex.Insert(_PhysicalRecno,@prec^.DeletedFlag);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ _dbfFile.WriteRecord(_PhysicalRecno,@prec^.DeletedFlag);
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+procedure TDbf.CreateTable; //(FieldDefs:TFieldDefs);
|
|
|
+var
|
|
|
+ ix:integer;
|
|
|
+begin
|
|
|
+ CheckInactive;
|
|
|
+ // InternalInitFieldDefs;
|
|
|
+ if FieldDefs.Count = 0 then
|
|
|
+ begin
|
|
|
+ for Ix := 0 to FieldCount - 1 do
|
|
|
+ begin
|
|
|
+ with Fields[Ix] do
|
|
|
+ begin
|
|
|
+ if FieldKind = fkData then
|
|
|
+ FieldDefs.Add(FieldName,DataType,Size,Required);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ _OpenFiles(true);
|
|
|
+ try
|
|
|
+ _dbfFile.DbfFile_CreateTable(FieldDefs);
|
|
|
+ finally
|
|
|
+ // close the file
|
|
|
+ _CloseFiles;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.PackTable;
|
|
|
+begin
|
|
|
+ _dbfFile.dbfFile_PackTable;
|
|
|
+ Resync([]);
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+function TDbf.CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; {override virtual}
|
|
|
+var
|
|
|
+ Memoi:array[1..32] of char;
|
|
|
+ lBlob:TMyBlobFile;
|
|
|
+begin
|
|
|
+ lBlob:=TMyBlobFile.Create(Mode,Field);
|
|
|
+ if _dbfFile.GetFieldData(Field.FieldNo-1, ftString,@pDbfRecord(ActiveBuffer)^.deletedflag,@Memoi[1]) then begin
|
|
|
+ lBlob.MemoRecno:=StrToIntDef(Memoi,0);
|
|
|
+ _dbtFile.ReadMemo(lBlob.MemoRecno,lBlob);
|
|
|
+ lBlob.ReadSize:=lBlob.Size;
|
|
|
+ end else lBlob.MemoRecno:=0;
|
|
|
+ Result:=lBlob;
|
|
|
+end;
|
|
|
+
|
|
|
+{$ifdef DELPHI_3}
|
|
|
+procedure TDbf.Translate(Src, Dest: PChar; ToOem: Boolean); {override virtual}
|
|
|
+begin
|
|
|
+ if (Src <> nil) and (Dest<>nil) then begin
|
|
|
+ if ToOem then CharToOem(Src,Dest)
|
|
|
+ else OemToChar(Src,Dest);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+{$else}
|
|
|
+{$ifndef fpc}
|
|
|
+function TDbf.Translate(Src, Dest: PChar; ToOem: Boolean): Integer; {override virtual}
|
|
|
+begin
|
|
|
+ if (Src <> nil) and (Dest<>nil) then begin
|
|
|
+ if ToOem then CharToOem(Src,Dest)
|
|
|
+ else OemToChar(Src,Dest);
|
|
|
+ result:= StrLen(Dest);
|
|
|
+ end else result:=0;
|
|
|
+end;
|
|
|
+{$else}
|
|
|
+procedure TDbf.Translate(Src, Dest: PChar; ToOem: Boolean); {override virtual}
|
|
|
+begin
|
|
|
+end;
|
|
|
+{$endif}
|
|
|
+{$endif}
|
|
|
+
|
|
|
+procedure TDbf.ClearCalcFields(Buffer: PChar);
|
|
|
+begin
|
|
|
+ FillChar(Buffer[_dbfFile.RecordSize], CalcFieldsSize, 0);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.InternalSetToRecord(Buffer: PChar); {override virtual abstract from TDataset}
|
|
|
+var
|
|
|
+ prec:pDbfRecord;
|
|
|
+begin
|
|
|
+ if Buffer=nil then exit;
|
|
|
+ prec:=pDbfRecord(Buffer);
|
|
|
+ _PhysicalRecno:=prec^.BookmarkData.RecNo;
|
|
|
+ _ResyncIndexes(Buffer);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf._ResyncIndexes(Buffer: PChar);
|
|
|
+var
|
|
|
+ i:integer;
|
|
|
+ lindex:TIndex;
|
|
|
+begin
|
|
|
+ if _indexes.Count>0 then begin
|
|
|
+ _dbfFile.ReadRecord(_PhysicalRecno,_PrevBuffer);
|
|
|
+ for i:=0 to _indexes.Count-1 do begin
|
|
|
+ lindex:=TIndex(_indexes.Items[i]);
|
|
|
+ lindex.GotoKey(_physicalRecno,nil);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TDbf.IsCursorOpen: Boolean; {override virtual abstract from TDataset}
|
|
|
+begin
|
|
|
+ result:=_IsCursorOpen;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); {override virtual abstract from TDataset}
|
|
|
+var
|
|
|
+ prec:pDbfRecord;
|
|
|
+begin
|
|
|
+ prec:=pDbfRecord(Buffer);
|
|
|
+ prec^.BookMarkFlag:=Value;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.SetBookmarkData(Buffer: PChar; Data: Pointer); {override virtual abstract from TDataset}
|
|
|
+var
|
|
|
+ prec:pDbfRecord;
|
|
|
+begin
|
|
|
+ prec:=pDbfRecord(Buffer);
|
|
|
+ prec^.BookMarkData:=pBookMarkData(Data)^;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.SetFieldData(Field: TField; Buffer: Pointer); {override virtual abstract from TDataset}
|
|
|
+var
|
|
|
+ prec:pDbfRecord;
|
|
|
+ dst:pointer;
|
|
|
+begin
|
|
|
+ if (Field.FieldNo >= 0) then begin
|
|
|
+ prec:=pDbfRecord(ActiveBuffer);
|
|
|
+ dst:=@prec^.DeletedFlag;
|
|
|
+ _dbfFile.SetFieldData(Field.FieldNo - 1,Field.DataType,Buffer,Dst);
|
|
|
+ end else begin { ***** fkCalculated, fkLookup ***** }
|
|
|
+ prec:=pDbfRecord(CalcBuffer);
|
|
|
+ dst:=@prec^.DeletedFlag;
|
|
|
+ Inc(integer(dst), GetRecordSize + Field.Offset);
|
|
|
+ Boolean(dst^) := LongBool(Buffer);
|
|
|
+ if Boolean(dst^) then begin
|
|
|
+ Inc(integer(dst), 1);
|
|
|
+ Move(Buffer^, dst^, Field.DataSize);
|
|
|
+ end;
|
|
|
+ end; { end of ***** fkCalculated, fkLookup ***** }
|
|
|
+ if not (State in [dsCalcFields, dsFilter, dsNewValue]) then begin
|
|
|
+ DataEvent(deFieldChange, Longint(Field));
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+// this function is just for the grid scrollbars
|
|
|
+// it doesn't have to be perfectly accurate, but fast.
|
|
|
+function TDbf.GetRecordCount: Integer; {override virtual}
|
|
|
+begin
|
|
|
+ if _curIndex=nil then begin
|
|
|
+ result:=_dbfFile.RecordCount;
|
|
|
+ end else begin
|
|
|
+ result:=_curIndex.GuessRecordCount;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+// this function is just for the grid scrollbars
|
|
|
+// it doesn't have to be perfectly accurate, but fast.
|
|
|
+function TDbf.GetRecNo: Integer; {override virtual}
|
|
|
+begin
|
|
|
+ UpdateCursorPos;
|
|
|
+ if _curIndex=nil then begin
|
|
|
+ result:=_PhysicalRecno+1;
|
|
|
+ end else begin
|
|
|
+ result:=_curIndex.GuessRecNo;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.SetRecNo(Value: Integer); {override virual}
|
|
|
+begin
|
|
|
+ if _curIndex=nil then begin
|
|
|
+ _PhysicalRecno:=Value-1;
|
|
|
+ end else begin
|
|
|
+ //result:=_curIndex.GuessRecNo;
|
|
|
+ end;
|
|
|
+ Resync([rmExact]);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDBf.DeleteIndex(const AName: string);
|
|
|
+
|
|
|
+begin
|
|
|
+ // I must admit that is seems a bit expeditive.
|
|
|
+ // but I does implement this method because TTable does
|
|
|
+ DeleteFile(_GetPath + Name);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.CloseIndexFile(const IndexFileName: string);
|
|
|
+var
|
|
|
+ lindex:tindex;
|
|
|
+begin
|
|
|
+ lindex:=_GetIndex(IndexFileName);
|
|
|
+ if lindex<>nil then begin
|
|
|
+ lindex.Free;
|
|
|
+ _indexes.Delete(_indexes.IndexOf(lindex));
|
|
|
+ if _curindex = lindex then begin
|
|
|
+ _curindex:=nil;
|
|
|
+ resync([]);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbf.OpenIndexFile(AnIndexName:string);
|
|
|
+var
|
|
|
+ lIndexFile:TIndexFile;
|
|
|
+ lIndex:TIndex;
|
|
|
+begin
|
|
|
+ lindex:=_GetIndex(IndexName);
|
|
|
+ if lindex=nil then begin
|
|
|
+ IndexName:=lowercase(_GetPath + IndexName);
|
|
|
+ lIndexFile:=TIndexFile(GetPagedFile(IndexName));
|
|
|
+ if lIndexFile=nil then begin
|
|
|
+ lIndexFile:=TIndexFile.Create(IndexName,fmOpenReadWrite + fmShareDenyWrite);
|
|
|
+ end;
|
|
|
+ lIndex:=TIndex.Create(lIndexFile,0,false);
|
|
|
+ _Indexes.Add(lIndex);
|
|
|
+ lIndex.InitFieldDef(_DbfFile,lIndex._NdxHdr.KeyDesc);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+(*
|
|
|
+procedure TDbfFile.DbfFile_PackTable;
|
|
|
+var
|
|
|
+begin
|
|
|
+end;
|
|
|
+*)
|
|
|
+{$ifdef fpc}
|
|
|
+procedure TDbf.AddIndex(const AnIndexName, IndexFields: String; Options: TIndexOptions);
|
|
|
+
|
|
|
+begin
|
|
|
+ AddIndex(indexName,IndexFields,options,'');
|
|
|
+end;
|
|
|
+{$endif}
|
|
|
+
|
|
|
+{$ifdef DELPHI_3}
|
|
|
+procedure TDbf.AddIndex(const IndexName, Fields: String; Options: TIndexOptions);
|
|
|
+var
|
|
|
+ DescFields:string;
|
|
|
+{$else}
|
|
|
+{$ifndef fpc}
|
|
|
+procedure TDbf.AddIndex(const IndexName, Fields: String; Options: TIndexOptions; const DescFields: String='');
|
|
|
+var
|
|
|
+{$else}
|
|
|
+procedure TDbf.AddIndex(const AnIndexName, IndexFields: String; Options: TIndexOptions; const DescFields: String);
|
|
|
+var
|
|
|
+{$endif}
|
|
|
+{$endif}
|
|
|
+ lfilename:string;
|
|
|
+ lIndexFile:TIndexFile;
|
|
|
+ lIndex:TIndex;
|
|
|
+ cur,thelast:integer;
|
|
|
+begin
|
|
|
+ lfilename:=lowercase(_GetPath+IndexName);
|
|
|
+ lIndexFile:=TIndexFile(GetPagedFile(lfilename));
|
|
|
+ if lIndexFile<>nil then exit;
|
|
|
+ lIndexFile:=TIndexFile.Create(lfilename,fmCreate);
|
|
|
+ lIndex:=TIndex.Create(lIndexFile,0,true);
|
|
|
+{$ifndef fpc}
|
|
|
+ lIndex.InitFieldDef(_DbfFile,Fields);
|
|
|
+{$else}
|
|
|
+ lIndex.InitFieldDef(_DbfFile,IndexFields);
|
|
|
+{$endif}
|
|
|
+ with lIndex._NdxHdr do begin
|
|
|
+ startpage:=1;
|
|
|
+ nbPage:=1;
|
|
|
+ keyformat:=#0;
|
|
|
+ keytype:='C';
|
|
|
+ dummy:=$5800;
|
|
|
+ keylen:=lindex._FieldLen;
|
|
|
+ nbkey:=(512-8) div (lindex._FieldLen+8);
|
|
|
+ keyreclen:=lindex._FieldLen+8;
|
|
|
+ Unique:=0;
|
|
|
+ KeyDesc[0]:=' ';
|
|
|
+{$ifndef fpc}
|
|
|
+ StrLCopy(KeyDesc,PChar(UpperCase(Fields)),255);
|
|
|
+{$else}
|
|
|
+ StrLCopy(KeyDesc,PChar(UpperCase(IndexFields)),255);
|
|
|
+{$endif}
|
|
|
+ end;
|
|
|
+ lindex._IndexFile._Seek(lindex._RootPage);
|
|
|
+ lindex._IndexFile.Stream.Write(lindex._NdxHdr,SizeOf(lindex._NdxHdr));
|
|
|
+
|
|
|
+ cur:=0;
|
|
|
+ thelast:=_DbfFile.CalcRecordCount;
|
|
|
+
|
|
|
+ while cur<thelast do begin
|
|
|
+ _DbfFile.ReadRecord(cur, _PrevBuffer);
|
|
|
+ lIndex.Insert(cur,_PrevBuffer);
|
|
|
+ inc(cur);
|
|
|
+ end;
|
|
|
+ _Indexes.Add(lIndex);
|
|
|
+end;
|
|
|
+//==========================================================
|
|
|
+//============ dbtfile
|
|
|
+//==========================================================
|
|
|
+constructor TDbtFile.Create(const FileName: string; Mode: Word; Ver:xBaseVersion);
|
|
|
+begin
|
|
|
+ inherited Create(FileName,Mode);
|
|
|
+ _DbtVersion:=Ver;
|
|
|
+ if mode = fmCreate then begin
|
|
|
+ FillChar(_MemoHdr,sizeof(_MemoHdr),0);
|
|
|
+ end else begin
|
|
|
+ Stream.Position:=0;
|
|
|
+ Stream.read(_MemoHdr,SizeOf(_MemoHdr));
|
|
|
+ end;
|
|
|
+ HeaderSize:=0;
|
|
|
+ RecordSize:=_MemoHdr.BlockLen;
|
|
|
+
|
|
|
+ if (RecordSize=0) or ((RecordSize mod 128)<>0) then begin
|
|
|
+ _MemoHdr.BlockLen := $200;
|
|
|
+ RecordSize := $200;
|
|
|
+ end;
|
|
|
+ // Can you tell me why the header of dbase3 memo contains 1024 and it 512 ?
|
|
|
+ if _DbtVersion=xBaseIII then RecordSize:=512;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbtFile.ReadMemo(recno:Integer;Dst:TStream);
|
|
|
+var
|
|
|
+ Buff:array[0..511] of char;
|
|
|
+ i,lsize:integer;
|
|
|
+ finish:boolean;
|
|
|
+ lastc:char;
|
|
|
+begin
|
|
|
+ if recno=0 then Exit;
|
|
|
+ Stream.Position:= RecordSize * recno;
|
|
|
+ if _DbtVersion >= xBaseIV then begin // dBase4 memofiles
|
|
|
+ Stream.read(Buff[0],8);
|
|
|
+ if (Buff[0]=#$ff) and (Buff[1]=#$ff) and
|
|
|
+ (Buff[2]=#$08) and (Buff[3]=#$00) then begin
|
|
|
+ // dbase IV memo
|
|
|
+ lsize:=(PInteger(@Buff[4])^)-8;
|
|
|
+ end else begin
|
|
|
+ lsize:=0;
|
|
|
+ end;
|
|
|
+ repeat
|
|
|
+ if lsize>SizeOf(Buff) then begin
|
|
|
+ Stream.read(Buff,SizeOf(Buff));
|
|
|
+ Dst.Write(buff,SizeOf(Buff));
|
|
|
+ Dec(lsize,SizeOf(Buff));
|
|
|
+ end else if lsize>0 then begin
|
|
|
+ Stream.read(Buff,lsize);
|
|
|
+ Dst.Write(buff,lsize);
|
|
|
+ lsize:=0;
|
|
|
+ end;
|
|
|
+ until lsize=0;
|
|
|
+ end else begin
|
|
|
+ finish:=False;
|
|
|
+ Stream.read(Buff,SizeOf(Buff));
|
|
|
+ lastc:=#0;
|
|
|
+ repeat
|
|
|
+ for i:=0 to SizeOf(Buff)-2 do begin
|
|
|
+ if ((Buff[i]=#$1A) and
|
|
|
+ ((Buff[i+1]=#$1A) or ((i=0) and (lastc=#$1A))))
|
|
|
+ or (Buff[i]=#$0)
|
|
|
+ then begin
|
|
|
+ if i>0 then Dst.Write(buff,i);
|
|
|
+ finish:=True;
|
|
|
+ break;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ if finish then Break;
|
|
|
+ Dst.Write(buff,512);
|
|
|
+ lastc:=Buff[511];
|
|
|
+ Stream.read(Buff,SizeOf(Buff));
|
|
|
+ until finish;
|
|
|
+ end;
|
|
|
+ Dst.Seek(0,0);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TDbtFile.WriteMemo(var MemoRecno:Integer;ReadSize:Integer;Src:TStream);
|
|
|
+var
|
|
|
+ ByteBefore:Integer;
|
|
|
+ ByteAfter:Integer;
|
|
|
+ Buff:array[0..511] of char;
|
|
|
+ i:Integer;
|
|
|
+ c:Byte;
|
|
|
+ Append:Boolean;
|
|
|
+begin
|
|
|
+ if _DbtVersion >= xBaseIV then begin // dBase4 memofiles
|
|
|
+ ByteBefore:=8;
|
|
|
+ ByteAfter:=0;
|
|
|
+ end else begin // stupid files
|
|
|
+ ByteBefore:=0;
|
|
|
+ ByteAfter:=2;
|
|
|
+ end;
|
|
|
+ if Src.Size = 0 then begin
|
|
|
+ MemoRecno:=0;
|
|
|
+ end else begin
|
|
|
+ if ((ByteBefore+Src.Size+ByteAfter+_MemoHdr.BlockLen-1) div _MemoHdr.BlockLen)
|
|
|
+ <= ((ReadSize+_MemoHdr.BlockLen-1) div _MemoHdr.BlockLen)
|
|
|
+ then begin
|
|
|
+ Append:=false;
|
|
|
+ //MemoRecno:=MemoRecno;
|
|
|
+ end else begin
|
|
|
+ Append:=True;
|
|
|
+ MemoRecno:=_MemoHdr.NextBlock;
|
|
|
+ if MemoRecno=0 then begin
|
|
|
+ _MemoHdr.NextBlock:=1;
|
|
|
+ MemoRecno:=1;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ Stream.Seek(_MemoHdr.BlockLen * MemoRecno,0);
|
|
|
+ i:=Src.Position;
|
|
|
+ Src.Seek(0,0);
|
|
|
+ if ByteBefore=8 then begin
|
|
|
+ i:=$0008ffff;
|
|
|
+ Stream.Write(i,4);
|
|
|
+ i:=Src.Size+ByteBefore+ByteAfter;
|
|
|
+ Stream.Write(i,4);
|
|
|
+ end;
|
|
|
+ repeat
|
|
|
+ i:=Src.Read(buff,512);
|
|
|
+ if i=0 then break;
|
|
|
+ Inc(_MemoHdr.NextBlock);
|
|
|
+ Stream.Write(Buff,i);
|
|
|
+ until i<512;
|
|
|
+ if ByteAfter=2 then begin
|
|
|
+ c:=$1A;
|
|
|
+ Stream.Write(c,1);
|
|
|
+ Stream.Write(c,1);
|
|
|
+ end;
|
|
|
+ if Append then begin
|
|
|
+ Stream.Seek(0,0);
|
|
|
+ Stream.Write(_MemoHdr,SizeOf(_MemoHdr))
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+//==========================================================
|
|
|
+//============ TIndexFile
|
|
|
+//==========================================================
|
|
|
+constructor TIndexFile.Create(const FileName: string; Mode: Word);
|
|
|
+var
|
|
|
+ ext:string;
|
|
|
+ i:Integer;
|
|
|
+begin
|
|
|
+ inherited Create(FileName,Mode);
|
|
|
+ HeaderSize:=0;
|
|
|
+ RecordSize:=512;
|
|
|
+
|
|
|
+ ext:=ExtractFileExt(FileName);
|
|
|
+ if (ext='.mdx') then begin
|
|
|
+ _IndexVersion:=xBaseIV;
|
|
|
+ if Mode = fmCreate then begin
|
|
|
+ FillChar(_MdxHdr,sizeof(_MdxHdr),0);
|
|
|
+ end else begin
|
|
|
+ Stream.read(_MdxHdr,SizeOf(_MdxHdr));
|
|
|
+ end;
|
|
|
+ for i:= 0 to _MdxHdr.TagUsed-1 do begin
|
|
|
+// Stream.Position :=544 + i * _MdxHdr.TagSize;
|
|
|
+// Stream.read(lMdxTag,SizeOf(rMdxTag));
|
|
|
+// lIndex:=TIndex.Create(Self,lMdxTag.pageno);
|
|
|
+// _Indexes.Add(lIndex);
|
|
|
+// if i=0 then lIndex.ReadPage(lIndex._NdxHdr.startpage);
|
|
|
+ end;
|
|
|
+ end else begin
|
|
|
+ _IndexVersion:=xBaseIII;
|
|
|
+(*
|
|
|
+ _IndexFile._Seek(Pos);
|
|
|
+ _IndexFile.Stream.Read(_NdxHdr,SizeOf(_NdxHdr));
|
|
|
+ _Root:=TIndexPage.Create(Self);
|
|
|
+ _Root.SetPageNo(_NdxHdr.startpage);
|
|
|
+ lPos:=_Root;
|
|
|
+ _nblevel:=1;
|
|
|
+ repeat
|
|
|
+ lPos.LocalFirst;
|
|
|
+ if lPos.Entry._LowerPage=0 then break;
|
|
|
+ inc(_nblevel);
|
|
|
+ lChild:=TIndexPage.Create(Self);
|
|
|
+ lChild._UpperLevel:=lPos;
|
|
|
+ lPos._LowerLevel:=lChild;
|
|
|
+ lChild.SetPageNo(lPos.Entry._LowerPage);
|
|
|
+ lPos:=lChild;
|
|
|
+ until false;
|
|
|
+
|
|
|
+ _Spare:=TIndexPage.Create(Self);
|
|
|
+// _Field:=_IndexFile._Dbf.FindField(_NdxHdr.KeyDesc);
|
|
|
+ First;
|
|
|
+*)
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+destructor TIndexFile.Destroy;
|
|
|
+begin
|
|
|
+ inherited;
|
|
|
+end;
|
|
|
+
|
|
|
+//==========================================================
|
|
|
+//============ TIndexPage
|
|
|
+//==========================================================
|
|
|
+constructor TIndexPage.Create(Parent:TIndex);
|
|
|
+begin
|
|
|
+ _LowerLevel:=nil;
|
|
|
+ _UpperLevel:=nil;
|
|
|
+ _Index:=Parent;
|
|
|
+ _PageNo:=-1;
|
|
|
+ _EntryNo:=-1;
|
|
|
+end;
|
|
|
+
|
|
|
+destructor TIndexPage.Destroy;
|
|
|
+begin
|
|
|
+ if _LowerLevel<>nil then _LowerLevel.Free;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIndexPage.GetPEntry(EntryNo:integer):PNdxEntry;
|
|
|
+begin
|
|
|
+ Result:=PNdxentry(@_PageBuff.Entries[_Index._NdxHdr.keyreclen*entryno]);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIndexPage.LocalInsert(Recno:integer; Buffer:Pchar;LowerPage:integer):boolean;
|
|
|
+var
|
|
|
+ src,dst:pointer;
|
|
|
+ siz:integer;
|
|
|
+begin
|
|
|
+ if _PageBuff.NbEntries < _Index._NdxHdr.nbkey then begin
|
|
|
+ src:=Entry;
|
|
|
+ dst:=GetPEntry(_EntryNo+1);
|
|
|
+ siz:=(_PageBuff.NbEntries - _EntryNo)
|
|
|
+ * _Index._NdxHdr.keyreclen + 8;
|
|
|
+ Move(Src^, Dst^, Siz);
|
|
|
+ inc(_PageBuff.NbEntries);
|
|
|
+ SetEntry(Recno,Buffer,LowerPage);
|
|
|
+ Write;
|
|
|
+ Result:=true;
|
|
|
+ end else begin
|
|
|
+ Result:=false;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+function TIndexPage.LocalDelete:boolean;
|
|
|
+var
|
|
|
+ src,dst:pointer;
|
|
|
+ siz:integer;
|
|
|
+begin
|
|
|
+ if _PageBuff.NbEntries >=0 then begin
|
|
|
+ if _EntryNo<_PageBuff.NbEntries then begin
|
|
|
+ src:=GetPEntry(_EntryNo+1);
|
|
|
+ dst:=Entry;
|
|
|
+ siz:=(_PageBuff.NbEntries - _EntryNo - 1)
|
|
|
+ * _Index._NdxHdr.keyreclen + 8;
|
|
|
+ Move(Src^, Dst^, Siz);
|
|
|
+ end;
|
|
|
+ dec(_PageBuff.NbEntries);
|
|
|
+ Write;
|
|
|
+ if ((_PageBuff.NbEntries=0) and (_lowerlevel=nil))
|
|
|
+ or (_PageBuff.NbEntries<0) then begin
|
|
|
+ if _UpperLevel<>nil then begin
|
|
|
+ _UpperLevel.LocalDelete;
|
|
|
+ end;
|
|
|
+ end else if (_EntryNo>LastEntryNo) then begin
|
|
|
+ SetEntryNo(LastEntryNo); // We just removed the last on this page.
|
|
|
+ if (_UpperLevel<>nil) then begin
|
|
|
+ _UpperLevel.SetEntry(0,Entry^.CKey,_PageNo);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ Result:=true;
|
|
|
+ end else begin
|
|
|
+ Result:=false;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIndexPage.LastEntryNo:integer;
|
|
|
+begin
|
|
|
+ if (_LowerLevel=nil) then begin
|
|
|
+ result := _PageBuff.NbEntries - 1;
|
|
|
+ end else begin
|
|
|
+ result := _PageBuff.NbEntries;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIndexPage.LocalFirst;
|
|
|
+begin
|
|
|
+ SetEntryNo(0);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIndexPage.LocalLast;
|
|
|
+begin
|
|
|
+ SetEntryNo(LastEntryNo);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIndexPage.LocalPrev:boolean;
|
|
|
+begin
|
|
|
+ if _EntryNo>0 then begin
|
|
|
+ SetEntryNo(_EntryNo-1);
|
|
|
+ Result:=true;
|
|
|
+ end else begin
|
|
|
+ Result:=false;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIndexPage.LocalNext:boolean;
|
|
|
+begin
|
|
|
+ if (_EntryNo<LastEntryNo) then begin
|
|
|
+ SetEntryNo(_EntryNo+1);
|
|
|
+ Result:=true;
|
|
|
+ end else begin
|
|
|
+ Result:=false;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIndexPage.First;
|
|
|
+begin
|
|
|
+ LocalFirst;
|
|
|
+ if (_LowerLevel<>nil) then LowerLevel.First;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIndexPage.Last;
|
|
|
+begin
|
|
|
+ LocalLast;
|
|
|
+ if (_LowerLevel<>nil) then LowerLevel.Last;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIndexPage.Prev:boolean;
|
|
|
+begin
|
|
|
+ if (_LowerLevel<>nil) and LowerLevel.Prev then begin
|
|
|
+ result:=true;
|
|
|
+ exit;
|
|
|
+ end;
|
|
|
+ Result:=LocalPrev;
|
|
|
+ if Result and (Entry^._LowerPage>0) then LowerLevel.Last;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIndexPage.Next:boolean;
|
|
|
+begin
|
|
|
+ if (_LowerLevel<>nil) and LowerLevel.next then begin
|
|
|
+ result:=true;
|
|
|
+ exit;
|
|
|
+ end;
|
|
|
+ Result:=LocalNext;
|
|
|
+ if Result and (Entry^._LowerPage>0) then LowerLevel.First;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+function TIndexPage.FindNearest(Recno:integer; Key:pchar):integer;
|
|
|
+var
|
|
|
+ cmpres:integer;
|
|
|
+ v1,v2:double;
|
|
|
+ p:TIndexPage;
|
|
|
+begin
|
|
|
+ Result:=-1;
|
|
|
+ if @Key=nil then begin
|
|
|
+ Exit;
|
|
|
+ end;
|
|
|
+ SetEntryNo(0);
|
|
|
+ while _EntryNo<=_PageBuff.NbEntries do begin
|
|
|
+ if _EntryNo=_PageBuff.NbEntries then break;
|
|
|
+ if _Index._NdxHdr.keytype='C' then begin
|
|
|
+ cmpres:=StrLIComp(PChar(Key),Entry^.CKey,_Index._FieldLen);
|
|
|
+ end else begin
|
|
|
+ // Numeric field... to do
|
|
|
+ v1:=PDouble(Key)^;
|
|
|
+ v2:=Entry^.NKey;
|
|
|
+ if v1>v2 then cmpres:=1
|
|
|
+ else if v1<v2 then cmpres:=-1
|
|
|
+ else cmpres:=0;
|
|
|
+ end;
|
|
|
+ if cmpres=0 then begin
|
|
|
+ if _LowerLevel=nil then begin
|
|
|
+ if (Entry^.RecNo=Recno) then begin
|
|
|
+ result:=0;
|
|
|
+ Exit;
|
|
|
+ end else if (Entry^.Recno>Recno) then begin
|
|
|
+ result:=-1;
|
|
|
+ Exit;
|
|
|
+ end;
|
|
|
+ end else begin
|
|
|
+ p:=self;
|
|
|
+ while p._LowerLevel<>nil do begin
|
|
|
+ p:=p.LowerLevel;
|
|
|
+ p.LocalLast;
|
|
|
+ end;
|
|
|
+ if (p.Entry^.Recno>=Recno) then begin
|
|
|
+ result:=-1;
|
|
|
+ Exit;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ end else if cmpres<0 then begin
|
|
|
+ result:=-1;
|
|
|
+ exit;
|
|
|
+ end;
|
|
|
+ SetEntryNo(_EntryNo+1);
|
|
|
+ end;
|
|
|
+ result:=1;
|
|
|
+ Exit;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIndexPage.SetEntry(Recno:Integer; key:PChar; LowerPage:integer);
|
|
|
+begin
|
|
|
+ assert((_EntryNo>=0) and (_EntryNo<=_PageBuff.NbEntries));
|
|
|
+ if (_EntryNo=self._PageBuff.NbEntries) then begin
|
|
|
+ if (_UpperLevel<>nil) then begin
|
|
|
+ _UpperLevel.SetEntry(0,key,Self._PageNo);
|
|
|
+ end;
|
|
|
+ end else begin
|
|
|
+ if _Index._NdxHdr.keytype='C' then begin
|
|
|
+ mymove(key,Entry^.CKey,_Index._NdxHdr.keylen);
|
|
|
+ end else begin
|
|
|
+ Entry^.NKey:=PDouble(key)^;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ Entry^.RecNo:=RecNo;
|
|
|
+ Entry^._LowerPage:=LowerPage;
|
|
|
+ Write;
|
|
|
+end;
|
|
|
+function TIndexPage.LowerLevel : TIndexPage;
|
|
|
+begin
|
|
|
+ if (_LowerLevel<>nil) and (_LowerLevel._PageNo<>Entry^._LowerPage) then begin
|
|
|
+ _LowerLevel.SetPageNo(Entry^._LowerPage);
|
|
|
+ end;
|
|
|
+ result:=_LowerLevel;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIndexPage.Insert(Recno:Integer; Buffer:PChar; LowerPage:integer):boolean;
|
|
|
+var
|
|
|
+ src,dst:PNdxEntry;
|
|
|
+ siz:integer;
|
|
|
+ split,old_entry:integer;
|
|
|
+ lSpare:TIndexPage;
|
|
|
+begin
|
|
|
+ if not LocalInsert(recno,buffer,lowerpage) then begin
|
|
|
+ // The entry is FULL so we will split this page
|
|
|
+ // 1 - Check parent exist
|
|
|
+ if _UpperLevel=nil then begin
|
|
|
+ AddNewLevel;
|
|
|
+ end;
|
|
|
+
|
|
|
+ old_entry:=_EntryNo;
|
|
|
+ split:=_EntryNo;
|
|
|
+ if split < _Index._NdxHdr.nbkey div 2 then begin
|
|
|
+ split:=_Index._NdxHdr.nbkey div 2;
|
|
|
+ end;
|
|
|
+ lSpare:=TIndexPage.Create(_Index);
|
|
|
+ try
|
|
|
+ // 2 - Create new page with first part
|
|
|
+ inc(_Index._NdxHdr.nbPage);
|
|
|
+ lSpare._PageNo:=_Index._NdxHdr.nbPage;
|
|
|
+ _Index._IndexFile._Seek(_Index._RootPage);
|
|
|
+ _Index._IndexFile.Stream.WriteBuffer (_Index._NdxHdr, SizeOf(_Index._NdxHdr));
|
|
|
+
|
|
|
+ if _lowerlevel=nil then begin
|
|
|
+ lSpare._PageBuff.NbEntries:=split;
|
|
|
+ end else begin
|
|
|
+ lSpare._PageBuff.NbEntries:=split-1;
|
|
|
+ end;
|
|
|
+ siz:=split*_Index._NdxHdr.keyreclen+8;
|
|
|
+ src:=@_PageBuff.Entries;
|
|
|
+ dst:=@lSpare._PageBuff.Entries;
|
|
|
+ Move(src^,dst^,siz);
|
|
|
+ lSpare.Write;
|
|
|
+
|
|
|
+ // 3 - Keep only end-part in this page
|
|
|
+ siz:=(_PageBuff.NbEntries-Split);
|
|
|
+ _PageBuff.NbEntries:=siz;
|
|
|
+
|
|
|
+ siz:=siz*_Index._NdxHdr.keyreclen+8;
|
|
|
+ SetEntryNo(split);
|
|
|
+ src:=Entry;
|
|
|
+ SetEntryNo(0);
|
|
|
+ dst:=Entry;
|
|
|
+ Move(src^,dst^,siz);
|
|
|
+
|
|
|
+ // 3 - Update upper level
|
|
|
+ lSpare.SetEntryNo(split-1);
|
|
|
+ _UpperLevel.Insert(0,lSpare.Entry^.CKey,lSpare._PageNo);
|
|
|
+
|
|
|
+ // We just need to go on inserted record now
|
|
|
+
|
|
|
+ if old_entry>=split then begin
|
|
|
+ _UpperLevel.LocalNext;
|
|
|
+ SetEntryNo(old_entry - split);
|
|
|
+ LocalInsert(Recno,Buffer,LowerPage);
|
|
|
+ lSpare.Write;
|
|
|
+ end else begin
|
|
|
+ lSpare.SetEntryNo(old_entry);
|
|
|
+ lSpare.LocalInsert(Recno,Buffer,LowerPage);
|
|
|
+ Write;
|
|
|
+ end;
|
|
|
+ finally
|
|
|
+ lspare.free;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ Result:=true;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIndexPage.Delete:boolean;
|
|
|
+begin
|
|
|
+ Result:=LocalDelete;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIndexPage.SetPageNo(page:Integer);
|
|
|
+begin
|
|
|
+ if (_PageNo<>page) and (page>0) then begin
|
|
|
+ _Index._IndexFile.ReadRecord(Page,@_PageBuff);
|
|
|
+ _PageNo:=page;
|
|
|
+ _EntryNo:=-1;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIndexPage.AddNewLevel;
|
|
|
+var
|
|
|
+ lNewPage:TIndexPage;
|
|
|
+begin
|
|
|
+ lNewPage:=TIndexPage.Create(_Index);
|
|
|
+ inc(_Index._NdxHdr.nbPage);
|
|
|
+ lNewPage._PageNo:= _Index._NdxHdr.nbPage;
|
|
|
+ _Index._NdxHdr.startpage:= _Index._NdxHdr.nbPage;
|
|
|
+ _Index._IndexFile._Seek(_Index._RootPage);
|
|
|
+ _Index._IndexFile.Stream.WriteBuffer (_Index._NdxHdr, SizeOf(_Index._NdxHdr));
|
|
|
+
|
|
|
+ lNewPage._PageBuff.NbEntries:=0;
|
|
|
+ lNewPage._UpperLevel:=nil;
|
|
|
+ lNewPage._LowerLevel:=_Index._Root;
|
|
|
+ lNewPage.SetEntryNo(0);
|
|
|
+ lNewPage.SetEntry(0,nil,_PageNo);
|
|
|
+ _Index._Root._UpperLevel:=lNewPage;
|
|
|
+ _Index._Root:=lNewPage;
|
|
|
+ lNewPage:=nil;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIndexPage.Write;
|
|
|
+begin
|
|
|
+ _Index._IndexFile.WriteRecord(_PageNo,@_PageBuff);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIndexPage.SetEntryNo(entryno:Integer);
|
|
|
+begin
|
|
|
+ if (_EntryNo<>entryno) then begin
|
|
|
+ _EntryNo:=entryno;
|
|
|
+ if _EntryNo>=0 then Entry:=PNdxentry(@_PageBuff.Entries[_Index._NdxHdr.keyreclen*entryno]);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIndexPage.WritePage(Page:integer);
|
|
|
+begin
|
|
|
+ _Index._IndexFile.WriteRecord(Page,@_PageBuff);
|
|
|
+end;
|
|
|
+
|
|
|
+//==========================================================
|
|
|
+//============ TIndex
|
|
|
+//==========================================================
|
|
|
+constructor TIndex.Create(Parent:TIndexFile; RootPage:integer;CreateIt:boolean);
|
|
|
+var
|
|
|
+ lPos:TIndexPage;
|
|
|
+ lChild:TIndexPage;
|
|
|
+begin
|
|
|
+ _RootPage:=RootPage;
|
|
|
+ _IndexFile:=Parent;
|
|
|
+ //_IndexOrder:=TList.Create;
|
|
|
+ if CreateIt then begin
|
|
|
+ FillChar(_NdxHdr,sizeof(_NdxHdr),0);
|
|
|
+ _NdxHdr.startpage:=1;
|
|
|
+ _NdxHdr.nbPage:=2;
|
|
|
+ _NdxHdr.keyformat:=#0;
|
|
|
+ _NdxHdr.keytype:='C';
|
|
|
+
|
|
|
+ _IndexFile._Seek(RootPage);
|
|
|
+ _IndexFile.Stream.Write(_NdxHdr,SizeOf(_NdxHdr));
|
|
|
+ _FieldPos := 0;
|
|
|
+ _FieldLen := 0;
|
|
|
+ end else begin
|
|
|
+ _IndexFile._Seek(RootPage);
|
|
|
+ _IndexFile.Stream.Read(_NdxHdr,SizeOf(_NdxHdr));
|
|
|
+ end;
|
|
|
+
|
|
|
+ _Root:=TIndexPage.Create(Self);
|
|
|
+ _Root.SetPageNo(_NdxHdr.startpage);
|
|
|
+ lPos:=_Root;
|
|
|
+ _nblevel:=1;
|
|
|
+ repeat
|
|
|
+ lPos.LocalFirst;
|
|
|
+ if lPos.Entry^._LowerPage=0 then break;
|
|
|
+ inc(_nblevel);
|
|
|
+ lChild:=TIndexPage.Create(Self);
|
|
|
+ lChild._UpperLevel:=lPos;
|
|
|
+ lPos._LowerLevel:=lChild;
|
|
|
+ lChild.SetPageNo(lPos.Entry^._LowerPage);
|
|
|
+ lPos:=lChild;
|
|
|
+ until false;
|
|
|
+
|
|
|
+ inc(_IndexFile._cntuse);
|
|
|
+ First;
|
|
|
+end;
|
|
|
+
|
|
|
+destructor TIndex.Destroy;
|
|
|
+begin
|
|
|
+ _IndexFile.Release;
|
|
|
+ _Root.Free;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+function TIndex.Find(Recno:integer; Buffer:PChar; var pPos:TIndexPage):integer;
|
|
|
+var
|
|
|
+ res:integer;
|
|
|
+begin
|
|
|
+ pPos:=_Root;
|
|
|
+ repeat
|
|
|
+ res:=pPos.FindNearest(Recno,Buffer);
|
|
|
+ if res<>0 then begin
|
|
|
+ if pPos.Entry^._LowerPage<>0 then begin
|
|
|
+ pPos:=pPos.LowerLevel;
|
|
|
+ res:=2;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ until res<>2;
|
|
|
+ Result:=res;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIndex.Update(Recno: integer; PrevBuffer,NewBuffer: PChar);
|
|
|
+var
|
|
|
+ lPos:TIndexPage;
|
|
|
+begin
|
|
|
+ if _FieldLen=0 then exit;
|
|
|
+
|
|
|
+ inc(PrevBuffer,_FieldPos);
|
|
|
+ inc(NewBuffer,_FieldPos);
|
|
|
+
|
|
|
+ if StrLIComp(PrevBuffer,NewBuffer,_FieldLen)<>0 then begin
|
|
|
+ Delete;
|
|
|
+ Find(Recno+1,NewBuffer,lPos);
|
|
|
+ lPos.Insert(Recno+1,NewBuffer,0);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIndex.Insert(Recno:integer; Buffer:PChar);
|
|
|
+var
|
|
|
+ lPos:TIndexPage;
|
|
|
+begin
|
|
|
+ if _FieldLen=0 then exit;
|
|
|
+
|
|
|
+ inc(Buffer,_FieldPos);
|
|
|
+
|
|
|
+ Find(Recno+1,Buffer,lPos);
|
|
|
+ lPos.Insert(Recno+1,Buffer,0);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIndex.Delete:boolean;
|
|
|
+var
|
|
|
+ lPos:TIndexPage;
|
|
|
+begin
|
|
|
+ lpos:=_root;
|
|
|
+ while lpos._LowerLevel<>nil do begin
|
|
|
+ lPos:=lPos.LowerLevel;
|
|
|
+ end;
|
|
|
+ lPos.Delete;
|
|
|
+ Result:=true;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+function TIndex.Pos:TIndexPage;
|
|
|
+var
|
|
|
+ p:TIndexPage;
|
|
|
+begin
|
|
|
+ p:=_Root;
|
|
|
+ while p.Entry^._LowerPage>0 do begin
|
|
|
+ p:=p.LowerLevel;
|
|
|
+ end;
|
|
|
+ result:=p;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIndex.First;
|
|
|
+begin
|
|
|
+ _Root.First;
|
|
|
+ dec(Pos._EntryNo);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIndex.Last;
|
|
|
+begin
|
|
|
+ _Root.Last;
|
|
|
+ inc(Pos._EntryNo);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIndex.Prev:boolean;
|
|
|
+begin
|
|
|
+ result:=_Root.Prev;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIndex.Next:boolean;
|
|
|
+begin
|
|
|
+ result:=_Root.Next;
|
|
|
+end;
|
|
|
+
|
|
|
+(*
|
|
|
+procedure TIndex.SetRecNo(Value: Integer);
|
|
|
+var
|
|
|
+ pos:integer;
|
|
|
+ p:TIndexPage;
|
|
|
+ i:integer;
|
|
|
+ ldiv:integer;
|
|
|
+begin
|
|
|
+ p:=_Root;
|
|
|
+ ldiv:=1;
|
|
|
+ while p.Entry^._LowerPage>0 do begin
|
|
|
+ ldiv:=ldiv*(_NdxHdr.nbkey+1);
|
|
|
+ p:=p._LowerLevel;
|
|
|
+ end;
|
|
|
+ pos:=value div ldiv;
|
|
|
+ p:=_Root;
|
|
|
+ while p.Entry^._LowerPage>0 do begin
|
|
|
+ p._EntryNo:=pos;
|
|
|
+ value:=value - pos * (_NdxHdr.nbkey+1);
|
|
|
+ ldiv:=ldiv div (_NdxHdr.nbkey+1);
|
|
|
+ pos:=value div ldiv;
|
|
|
+ p:=p._LowerLevel;
|
|
|
+ end;
|
|
|
+{
|
|
|
+ pos:=1;
|
|
|
+ First;
|
|
|
+ While pos<value do begin
|
|
|
+ if Next = false then break;
|
|
|
+ inc(pos);
|
|
|
+ end;
|
|
|
+}
|
|
|
+end;
|
|
|
+*)
|
|
|
+function TIndex.GuessRecordCount: Integer;
|
|
|
+var
|
|
|
+ lPos:TIndexPage;
|
|
|
+ nbrecord:integer;
|
|
|
+begin
|
|
|
+ // I just read first level and Guess an approximate record count...
|
|
|
+ nbrecord:=_Root._PageBuff.NbEntries;
|
|
|
+ lPos:=_Root.LowerLevel;
|
|
|
+ while lpos<>nil do begin
|
|
|
+ nbrecord:=nbrecord*(_NdxHdr.nbkey+1);
|
|
|
+ lPos:=lPos.LowerLevel;
|
|
|
+ end;
|
|
|
+ result:=nbrecord;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+function TIndex.GuessRecNo:Integer;
|
|
|
+var
|
|
|
+ p:TIndexPage;
|
|
|
+begin
|
|
|
+ p:=_Root;
|
|
|
+ result:=p._EntryNo;
|
|
|
+ while p.Entry^._LowerPage>0 do begin
|
|
|
+ p:=p.LowerLevel;
|
|
|
+ Result:=Result*(_NdxHdr.nbkey+1) + p._EntryNo;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIndex.GetRealRecNo:integer;
|
|
|
+var
|
|
|
+ ippos : TIndexPage;
|
|
|
+begin
|
|
|
+ ippos:=_Root;
|
|
|
+ while ippos._LowerLevel<>nil do begin
|
|
|
+ ippos:=pos.LowerLevel;
|
|
|
+ end;
|
|
|
+ if (ippos._EntryNo<0) or (ippos._EntryNo>=ippos._PageBuff.NbEntries) then Result:=-1
|
|
|
+ else Result:=ippos.Entry^.RecNo-1;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIndex.GotoKey(recno:integer; buffer:pchar);
|
|
|
+begin
|
|
|
+ // very temporary implementation
|
|
|
+ // could definitely be a bit faster.
|
|
|
+ _Root.First;
|
|
|
+ repeat
|
|
|
+ if self.Pos.Entry^.RecNo=(recno+1) then begin
|
|
|
+ exit;
|
|
|
+ end;
|
|
|
+ until Next=false;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIndex.InitFieldDef(dbfFile:TDbfFile;FieldDesc:string);
|
|
|
+var
|
|
|
+ FieldInfo:TMyFieldInfo;
|
|
|
+begin
|
|
|
+ FieldInfo:=DbfFile.GetFieldInfo(FieldDesc);
|
|
|
+ if FieldInfo<>nil then begin
|
|
|
+ _FieldPos:=FieldInfo.Offset;
|
|
|
+ _FieldLen:=FieldInfo.Size;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+//==========================================================
|
|
|
+//============ initialization
|
|
|
+//==========================================================
|
|
|
+
|
|
|
+{$ifndef fpc}
|
|
|
+type
|
|
|
+
|
|
|
+ TTableNameProperty = class(TStringProperty)
|
|
|
+ public
|
|
|
+ procedure Edit; override;
|
|
|
+ function GetAttributes: TPropertyAttributes; override;
|
|
|
+ end;
|
|
|
+
|
|
|
+procedure TTableNameProperty.Edit; {override;}
|
|
|
+var
|
|
|
+ FileOpen: TOpenDialog;
|
|
|
+ Dbf: TDbf;
|
|
|
+begin
|
|
|
+ FileOpen := TOpenDialog.Create(Application);
|
|
|
+ try
|
|
|
+ with fileopen do begin
|
|
|
+ Dbf:=GetComponent(0) as TDbf;
|
|
|
+ Filename := Dbf.DesignTimePath + GetValue;
|
|
|
+ Filter := 'Dbf table|*.dbf';
|
|
|
+ if Execute then begin
|
|
|
+ SetValue(ExtractFilename(Filename));
|
|
|
+ Dbf.DesignTimePath:=ExtractFilePath(Filename);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ finally
|
|
|
+ Fileopen.free;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTableNameProperty.GetAttributes: TPropertyAttributes; {override;}
|
|
|
+begin
|
|
|
+ Result := [paDialog, paRevertable];
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+type
|
|
|
+ TRunTimePathProperty = class(TStringProperty)
|
|
|
+ end;
|
|
|
+
|
|
|
+ TDesignTimePathProperty = class(TStringProperty)
|
|
|
+ end;
|
|
|
+
|
|
|
+//==========================================================
|
|
|
+//============ initialization
|
|
|
+//==========================================================
|
|
|
+
|
|
|
+procedure Register;
|
|
|
+begin
|
|
|
+ RegisterComponents('Exemples', [TDbf]);
|
|
|
+ RegisterPropertyEditor(TypeInfo(string), TDbf, 'TableName', TTableNameProperty);
|
|
|
+ RegisterPropertyEditor(TypeInfo(string), TDbf, 'RunTimePath', TRunTimePathProperty);
|
|
|
+ RegisterPropertyEditor(TypeInfo(string), TDbf, 'DesignTimePath', TDesignTimePathProperty);
|
|
|
+// RegisterPropertyEditor(TypeInfo(TStrings), TDbf, 'IndexFiles', TIndexFilesProperty);
|
|
|
+// ShowMessage(ToolServices.GetProjectName);
|
|
|
+end;
|
|
|
+{$endif fpc}
|
|
|
+
|
|
|
+initialization
|
|
|
+ _PagedFiles := TList.Create;
|
|
|
+ tDbf_TrimFields := true;
|
|
|
+
|
|
|
+finalization
|
|
|
+ _PagedFiles.free;
|
|
|
+
|
|
|
+end.
|