123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673 |
- unit dbf_dbffile;
- interface
- {$I Dbf_Common.inc}
- uses
- Classes, SysUtils, Math,
- {$ifdef WIN32}
- Windows,
- {$else}
- {$ifdef KYLIX}
- Libc,
- {$endif}
- Types, Dbf_Wtil,
- {$endif}
- Db,
- Dbf_Common,
- Dbf_Cursor,
- Dbf_PgFile,
- Dbf_Fields,
- Dbf_Memo,
- Dbf_IdxFile;
- //====================================================================
- //=== Dbf support (first part)
- //====================================================================
- // TxBaseVersion = (xUnknown,xClipper,xBaseIII,xBaseIV,xBaseV,xFoxPro,xVisualFoxPro);
- // TPagedFileMode = (pfOpen,pfCreate);
- // TDbfGetMode = (xFirst,xPrev,xCurrent, xNext, xLast);
- // TDbfGetResult = (xOK, xBOF, xEOF, xError);
- type
- //====================================================================
- TDbfIndexMissingEvent = procedure(var DeleteLink: Boolean) of object;
- TUpdateNullField = (unClear, unSet);
- //====================================================================
- TDbfGlobals = class;
- //====================================================================
- TDbfFile = class(TPagedFile)
- protected
- FMdxFile: TIndexFile;
- FMemoFile: TMemoFile;
- FFieldDefs: TDbfFieldDefs;
- FIndexNames: TStringList;
- FIndexFiles: TList;
- FDbfVersion: TXBaseVersion;
- FPrevBuffer: PChar;
- FRecordBufferSize: Integer;
- FLockUserLen: DWORD;
- FFileCodePage: Cardinal;
- FUseCodePage: Cardinal;
- FFileLangId: Byte;
- FCountUse: Integer;
- FCurIndex: Integer;
- FForceClose: Boolean;
- FLockField: TDbfFieldDef;
- FNullField: TDbfFieldDef;
- FAutoIncPresent: Boolean;
- FCopyDateTimeAsString: Boolean;
- FDateTimeHandling: TDateTimeHandling;
- FOnLocaleError: TDbfLocaleErrorEvent;
- FOnIndexMissing: TDbfIndexMissingEvent;
- function HasBlob: Boolean;
- function GetMemoExt: string;
- function GetLanguageId: Integer;
- function GetLanguageStr: string;
- function GetUseFloatFields: Boolean;
- procedure SetUseFloatFields(NewUse: Boolean);
- protected
- procedure ConstructFieldDefs;
- procedure UpdateNullField(Buffer: Pointer; AFieldDef: TDbfFieldDef; Action: TUpdateNullField);
- procedure WriteLockInfo(Buffer: PChar);
- public
- constructor Create;
- destructor Destroy; override;
- procedure Open;
- procedure Close;
- procedure Zap;
- procedure FinishCreate(FieldDefs: TDbfFieldDefs; MemoSize: Integer);
- function GetIndexByName(AIndexName: string): TIndexFile;
- procedure SetRecordSize(NewSize: Integer); override;
- procedure TryExclusive; override;
- procedure EndExclusive; override;
- procedure OpenIndex(IndexName, IndexField: string; CreateIndex: Boolean; Options: TIndexOptions);
- function DeleteIndex(const AIndexName: string): Boolean;
- procedure CloseIndex(AIndexName: string);
- procedure RepageIndex(AIndexFile: string);
- procedure CompactIndex(AIndexFile: string);
- function Insert(Buffer: PChar): integer;
- procedure WriteHeader; override;
- procedure ApplyAutoIncToBuffer(DestBuf: PChar); // dBase7 support. Writeback last next-autoinc value
- procedure FastPackTable;
- procedure RestructureTable(DbfFieldDefs: TDbfFieldDefs; Pack: Boolean);
- function GetFieldInfo(FieldName: string): TDbfFieldDef;
- function GetFieldData(Column: Integer; DataType: TFieldType; Src,Dst: Pointer): Boolean;
- function GetFieldDataFromDef(AFieldDef: TDbfFieldDef; DataType: TFieldType; Src, Dst: Pointer): Boolean;
- procedure SetFieldData(Column: Integer; DataType: TFieldType; Src,Dst: Pointer);
- procedure InitRecord(DestBuf: PChar);
- procedure PackIndex(lIndexFile: TIndexFile; AIndexName: string);
- procedure RegenerateIndexes;
- procedure LockRecord(RecNo: Integer; Buffer: PChar);
- procedure UnlockRecord(RecNo: Integer; Buffer: PChar);
- procedure RecordDeleted(RecNo: Integer; Buffer: PChar);
- procedure RecordRecalled(RecNo: Integer; Buffer: PChar);
- property MemoFile: TMemoFile read FMemoFile;
- property FieldDefs: TDbfFieldDefs read FFieldDefs;
- property IndexNames: TStringList read FIndexNames;
- property IndexFiles: TList read FIndexFiles;
- property MdxFile: TIndexFile read FMdxFile;
- property LanguageId: Integer read GetLanguageId;
- property LanguageStr: string read GetLanguageStr;
- property FileCodePage: Cardinal read FFileCodePage;
- property UseCodePage: Cardinal read FUseCodePage write FUseCodePage;
- property FileLangId: Byte read FFileLangId write FFileLangId;
- property DbfVersion: TXBaseVersion read FDbfVersion write FDbfVersion;
- property PrevBuffer: PChar read FPrevBuffer;
- property ForceClose: Boolean read FForceClose;
- property CopyDateTimeAsString: Boolean read FCopyDateTimeAsString write FCopyDateTimeAsString;
- property UseFloatFields: Boolean read GetUseFloatFields write SetUseFloatFields;
- property DateTimeHandling: TDateTimeHandling read FDateTimeHandling write FDateTimeHandling;
- property OnIndexMissing: TDbfIndexMissingEvent read FOnIndexMissing write FOnIndexMissing;
- property OnLocaleError: TDbfLocaleErrorEvent read FOnLocaleError write FOnLocaleError;
- end;
- //====================================================================
- TDbfCursor = class(TVirtualCursor)
- protected
- FPhysicalRecNo: Integer;
- public
- constructor Create(DbfFile: TDbfFile);
- function Next: Boolean; override;
- function Prev: Boolean; override;
- procedure First; override;
- procedure Last; override;
- function GetPhysicalRecNo: Integer; override;
- procedure SetPhysicalRecNo(RecNo: Integer); override;
- function GetSequentialRecordCount: Integer; override;
- function GetSequentialRecNo: Integer; override;
- procedure SetSequentialRecNo(RecNo: Integer); override;
- end;
- //====================================================================
- TDbfGlobals = class
- protected
- FCodePages: TList;
- FCurrencyAsBCD: Boolean;
- FDefaultOpenCodePage: Integer;
- FDefaultCreateLangId: Byte;
- FUserName: string;
- FUserNameLen: DWORD;
- function GetDefaultCreateCodePage: Integer;
- procedure SetDefaultCreateCodePage(NewCodePage: Integer);
- procedure InitUserName;
- public
- constructor Create;
- destructor Destroy; override;
- function CodePageInstalled(ACodePage: Integer): Boolean;
- property CurrencyAsBCD: Boolean read FCurrencyAsBCD write FCurrencyAsBCD;
- property DefaultOpenCodePage: Integer read FDefaultOpenCodePage write FDefaultOpenCodePage;
- property DefaultCreateCodePage: Integer read GetDefaultCreateCodePage write SetDefaultCreateCodePage;
- property DefaultCreateLangId: Byte read FDefaultCreateLangId write FDefaultCreateLangId;
- property UserName: string read FUserName;
- property UserNameLen: DWORD read FUserNameLen;
- end;
- var
- DbfGlobals: TDbfGlobals;
- implementation
- uses
- {$ifndef WIN32}
- {$ifndef FPC}
- RTLConsts,
- {$else}
- BaseUnix,
- {$endif}
- {$endif}
- Dbf_Str, Dbf_Lang;
- const
- sDBF_DEC_SEP = '.';
- {$I Dbf_Struct.inc}
- //====================================================================
- // International separator
- // thanks to Bruno Depero from Italy
- // and Andreas Wöllenstein from Denmark
- //====================================================================
- function DbfStrToFloat(const Src: PChar; const Size: Integer): Extended;
- var
- iPos: PChar;
- eValue: extended;
- endChar: Char;
- begin
- // temp null-term string
- endChar := (Src + Size)^;
- (Src + Size)^ := #0;
- // we only have to convert if decimal separator different
- if DecimalSeparator <> sDBF_DEC_SEP then
- begin
- // search dec sep
- iPos := StrScan(Src, sDBF_DEC_SEP);
- // replace
- if iPos <> nil then
- iPos^ := DecimalSeparator;
- end else
- iPos := nil;
- // convert to double
- if TextToFloat(Src, eValue {$ifndef VER1_0}, fvExtended{$endif}) then
- Result := eValue
- else
- Result := 0;
- // restore dec sep
- if iPos <> nil then
- iPos^ := sDBF_DEC_SEP;
- // restore Char of null-term
- (Src + Size)^ := endChar;
- end;
- procedure FloatToDbfStr(const Val: Extended; const Size, Precision: Integer; const Dest: PChar);
- var
- Buffer: array [0..24] of Char;
- resLen: Integer;
- iPos: PChar;
- begin
- // convert to temporary buffer
- resLen := FloatToText(@Buffer[0], Val, {$ifndef FPC_VERSION}fvExtended,{$endif} ffFixed, Size, Precision);
- // null-terminate buffer
- Buffer[resLen] := #0;
- // we only have to convert if decimal separator different
- if DecimalSeparator <> sDBF_DEC_SEP then
- begin
- iPos := StrScan(@Buffer[0], DecimalSeparator);
- if iPos <> nil then
- iPos^ := sDBF_DEC_SEP;
- end;
- // fill destination with spaces
- FillChar(Dest^, Size, ' ');
- // now copy right-aligned to destination
- Move(Buffer[0], Dest[Size-resLen], resLen);
- end;
- function GetIntFromStrLength(Src: Pointer; Size: Integer; Default: Integer): Integer;
- var
- endChar: Char;
- Code: Integer;
- begin
- // save Char at pos term. null
- endChar := (PChar(Src) + Size)^;
- (PChar(Src) + Size)^ := #0;
- // convert
- Val(PChar(Src), Result, Code);
- // check success
- if Code <> 0 then
- Result := Default;
- // restore prev. ending Char
- (PChar(Src) + Size)^ := endChar;
- end;
- //====================================================================
- // TDbfFile
- //====================================================================
- constructor TDbfFile.Create;
- begin
- // init variables first
- FFieldDefs := TDbfFieldDefs.Create(nil);
- FIndexNames := TStringList.Create;
- FIndexFiles := TList.Create;
- FOnLocaleError := nil;
- FOnIndexMissing := nil;
- FMdxFile := nil;
- FForceClose := false;
- FCopyDateTimeAsString := false;
- // now initialize inherited
- inherited;
- end;
- destructor TDbfFile.Destroy;
- var
- I: Integer;
- begin
- // close file
- Close;
- // free files
- for I := 0 to Pred(FIndexFiles.Count) do
- TPagedFile(FIndexFiles.Items[I]).Free;
- // free lists
- FreeAndNil(FIndexFiles);
- FreeAndNil(FIndexNames);
- FreeAndNil(FFieldDefs);
- // call ancestor
- inherited;
- end;
- function TDbfFile.GetUseFloatFields: Boolean;
- begin
- Result := FFieldDefs.UseFloatFields;
- end;
- procedure TDbfFile.SetUseFloatFields(NewUse: Boolean);
- begin
- FFieldDefs.UseFloatFields := NewUse;
- end;
- procedure TDbfFile.Open;
- var
- lMemoFileName: string;
- lMdxFileName: string;
- MemoFileClass: TMemoFileClass;
- I: Integer;
- deleteLink: Boolean;
- LangStr: PChar;
- version: byte;
- begin
- // check if not already opened
- if not Active then
- begin
- // open requested file
- OpenFile;
- // check if we opened an already existing file
- if not FileCreated then
- begin
- HeaderSize := sizeof(rDbfHdr); // temporary
- // OH 2000-11-15 dBase7 support. I build dBase Tables with different
- // BDE dBase Level (1. without Memo, 2. with Memo)
- // Header Byte ($1d hex) (29 dec) -> Language driver ID.
- // $03,$83 xBaseIII Header Byte $1d=$00, Float -> N($13.$04) DateTime C($1E)
- // $03,$8B xBaseIV/V Header Byte $1d=$58, Float -> N($14.$04)
- // $04,$8C xBaseVII Header Byte $1d=$00 Float -> O($08) DateTime @($08)
- // $03,$F5 FoxPro Level 25 Header Byte $1d=$03, Float -> N($14.$04)
- // Access 97
- // $03,$83 dBaseIII Header Byte $1d=$00, Float -> N($13.$05) DateTime D($08)
- // $03,$8B dBaseIV/V Header Byte $1d=$00, Float -> N($14.$05) DateTime D($08)
- // $03,$F5 FoxPro Level 25 Header Byte $1d=$09, Float -> N($14.$05) DateTime D($08)
- version := PDbfHdr(Header).VerDBF;
- case (version and $07) of
- $03:
- if LanguageID = 0 then
- FDbfVersion := xBaseIII
- else
- FDbfVersion := xBaseIV;
- $04:
- FDbfVersion := xBaseVII;
- $02, $05:
- FDbfVersion := xFoxPro;
- else
- // check visual foxpro
- if ((version and $FE) = $30) or (version = $F5) or (version = $FB) then
- begin
- FDbfVersion := xFoxPro;
- end else begin
- // not a valid DBF file
- raise EDbfError.Create(STRING_INVALID_DBF_FILE);
- end;
- end;
- FFieldDefs.DbfVersion := FDbfVersion;
- RecordSize := PDbfHdr(Header).RecordSize;
- HeaderSize := PDbfHdr(Header).FullHdrSize;
- if (HeaderSize = 0) or (RecordSize = 0) then
- begin
- HeaderSize := 0;
- RecordSize := 0;
- RecordCount := 0;
- FForceClose := true;
- exit;
- end;
- // check if specified recordcount correct
- if PDbfHdr(Header).RecordCount <> RecordCount then
- begin
- // This message was annoying
- // and was not understood by most people
- // ShowMessage('Invalid Record Count,'+^M+
- // 'RecordCount in Hdr : '+IntToStr(PDbfHdr(Header).RecordCount)+^M+
- // 'expected : '+IntToStr(RecordCount));
- PDbfHdr(Header).RecordCount := RecordCount;
- WriteHeader; // Correct it
- end;
- // determine codepage
- if FDbfVersion >= xBaseVII then
- begin
- // cache language str
- LangStr := @PAfterHdrVII(PChar(Header) + SizeOf(rDbfHdr)).LanguageDriverName;
- // VdBase 7 Language strings
- // 'DBWIN...' -> Charset 1252 (ansi)
- // 'DB999...' -> Code page 999, 9 any digit
- // 'DBHEBREW' -> Code page 1255 ??
- // 'FOX..999' -> Code page 999, 9 any digit
- // 'FOX..WIN' -> Charset 1252 (ansi)
- if (LangStr[0] = 'D') and (LangStr[1] = 'B') then
- begin
- if StrLComp(LangStr+2, 'WIN', 3) = 0 then
- FFileCodePage := 1252
- else
- if StrLComp(LangStr+2, 'HEBREW', 6) = 0 then
- begin
- FFileCodePage := 1255;
- end else begin
- FFileCodePage := GetIntFromStrLength(LangStr+2, 3, 0);
- if (Ord(LangStr[5]) >= Ord('0')) and (Ord(LangStr[5]) <= Ord('9')) then
- FFileCodePage := FFileCodePage * 10 + Ord(LangStr[5]) - Ord('0');
- end;
- end else
- if StrLComp(LangStr, 'FOX', 3) = 0 then
- begin
- if StrLComp(LangStr+5, 'WIN', 3) = 0 then
- FFileCodePage := 1252
- else
- FFileCodePage := GetIntFromStrLength(LangStr+5, 3, 0)
- end else begin
- FFileCodePage := 0;
- end;
- FFileLangId := GetLangId_From_LangName(LanguageStr);
- end else begin
- // FDbfVersion <= xBaseV
- FFileLangId := PDbfHdr(Header).Language;
- FFileCodePage := LangId_To_CodePage[FFileLangId];
- end;
- // determine used codepage, if no codepage, then use default codepage
- FUseCodePage := FFileCodePage;
- if FUseCodePage = 0 then
- FUseCodePage := DbfGlobals.DefaultOpenCodePage;
- // get list of fields
- ConstructFieldDefs;
- // open blob file if present
- lMemoFileName := ChangeFileExt(FileName, GetMemoExt);
- if HasBlob then
- begin
- // open blob file
- if not FileExists(lMemoFileName) then
- MemoFileClass := TNullMemoFile
- else if FDbfVersion = xFoxPro then
- MemoFileClass := TFoxProMemoFile
- else
- MemoFileClass := TDbaseMemoFile;
- FMemoFile := MemoFileClass.Create(Self);
- FMemoFile.FileName := lMemoFileName;
- FMemoFile.Mode := Mode;
- FMemoFile.AutoCreate := false;
- FMemoFile.MemoRecordSize := 0;
- FMemoFile.DbfVersion := FDbfVersion;
- FMemoFile.Open;
- // set header blob flag corresponding to field list
- if FDbfVersion <> xFoxPro then
- PDbfHdr(Header).VerDBF := PDbfHdr(Header).VerDBF or $80;
- end else
- if FDbfVersion <> xFoxPro then
- PDbfHdr(Header).VerDBF := PDbfHdr(Header).VerDBF and $7F;
- // check if mdx flagged
- if (FDbfVersion <> xFoxPro) and (PDbfHdr(Header).MDXFlag <> 0) then
- begin
- // open mdx file if present
- lMdxFileName := ChangeFileExt(FileName, '.mdx');
- if FileExists(lMdxFileName) then
- begin
- // open file
- FMdxFile := TIndexFile.Create(Self);
- FMdxFile.FileName := lMdxFileName;
- FMdxFile.Mode := Mode;
- FMdxFile.AutoCreate := false;
- FMdxFile.OnLocaleError := FOnLocaleError;
- FMdxFile.CodePage := UseCodePage;
- FMdxFile.Open;
- // is index ready for use?
- if not FMdxFile.ForceClose then
- begin
- FIndexFiles.Add(FMdxFile);
- // get index tag names known
- FMdxFile.GetIndexNames(FIndexNames);
- end else begin
- // asked to close! close file
- FreeAndNil(FMdxFile);
- end;
- end else begin
- // ask user
- deleteLink := true;
- if Assigned(FOnIndexMissing) then
- FOnIndexMissing(deleteLink);
- // correct flag
- if deleteLink then
- PDbfHdr(Header).MDXFlag := 0
- else
- FForceClose := true;
- end;
- end;
- end;
- // open indexes
- for I := 0 to FIndexFiles.Count - 1 do
- TIndexFile(FIndexFiles.Items[I]).Open;
- end;
- end;
- procedure TDbfFile.Close;
- var
- MdxIndex, I: Integer;
- begin
- if Active then
- begin
- // close index files first
- MdxIndex := -1;
- for I := 0 to FIndexFiles.Count - 1 do
- begin
- TIndexFile(FIndexFiles.Items[I]).Close;
- if FIndexFiles.Items[I] = FMdxFile then
- MdxIndex := I;
- end;
- // free memo file if any
- FreeAndNil(FMemoFile);
- // now we can close physical dbf file
- CloseFile;
- // free FMdxFile, remove it from the FIndexFiles and Names lists
- if MdxIndex >= 0 then
- FIndexFiles.Delete(MdxIndex);
- I := 0;
- while I < FIndexNames.Count do
- begin
- if FIndexNames.Objects[I] = FMdxFile then
- begin
- FIndexNames.Delete(I);
- end else begin
- Inc(I);
- end;
- end;
- FreeAndNil(FMdxFile);
- if FPrevBuffer <> nil then
- FreeMemAndNil(Pointer(FPrevBuffer));
- // reset variables
- FFileLangId := 0;
- end;
- end;
- procedure TDbfFile.FinishCreate(FieldDefs: TDbfFieldDefs; MemoSize: Integer);
- var
- lFieldDescIII: rFieldDescIII;
- lFieldDescVII: rFieldDescVII;
- lFieldDescPtr: Pointer;
- lFieldDef: TDbfFieldDef;
- lMemoFileName: string;
- I, lFieldOffset, lSize, lPrec: Integer;
- lHasBlob: Boolean;
- lLocaleID: LCID;
- begin
- try
- // first reset file
- RecordCount := 0;
- lHasBlob := false;
- // determine codepage & locale
- if FFileLangId = 0 then
- FFileLangId := DbfGlobals.DefaultCreateLangId;
- FFileCodePage := LangId_To_CodePage[FFileLangId];
- lLocaleID := LangId_To_Locale[FFileLangId];
- FUseCodePage := FFileCodePage;
- // prepare header size
- if FDbfVersion = xBaseVII then
- begin
- // version xBaseVII without memo
- HeaderSize := SizeOf(rDbfHdr) + SizeOf(rAfterHdrVII);
- RecordSize := SizeOf(rFieldDescVII);
- FillChar(Header^, HeaderSize, #0);
- PDbfHdr(Header).VerDBF := $04;
- // write language string
- StrPLCopy(
- @PAfterHdrVII(PChar(Header)+SizeOf(rDbfHdr)).LanguageDriverName[32],
- ConstructLangName(FFileCodePage, lLocaleID, false),
- 63-32);
- lFieldDescPtr := @lFieldDescVII;
- end else begin
- // version xBaseIII/IV/V without memo
- HeaderSize := SizeOf(rDbfHdr) + SizeOf(rAfterHdrIII);
- RecordSize := SizeOf(rFieldDescIII);
- FillChar(Header^, HeaderSize, #0);
- if FDbfVersion = xFoxPro then
- begin
- PDbfHdr(Header).VerDBF := $02
- end else
- PDbfHdr(Header).VerDBF := $03;
- // standard language WE, dBase III no language support
- if FDbfVersion = xBaseIII then
- PDbfHdr(Header).Language := 0
- else
- PDbfHdr(Header).Language := FFileLangId;
- // init field ptr
- lFieldDescPtr := @lFieldDescIII;
- end;
- // begin writing fields
- FFieldDefs.Clear;
- // deleted mark 1 byte
- lFieldOffset := 1;
- for I := 1 to FieldDefs.Count do
- begin
- lFieldDef := FieldDefs.Items[I-1];
- // check if datetime conversion
- if FCopyDateTimeAsString then
- if lFieldDef.FieldType = ftDateTime then
- begin
- // convert to string
- lFieldDef.FieldType := ftString;
- lFieldDef.Size := 22;
- end;
- // update source
- lFieldDef.FieldName := AnsiUpperCase(lFieldDef.FieldName);
- lFieldDef.Offset := lFieldOffset;
- lHasBlob := lHasBlob or lFieldDef.IsBlob;
- // apply field transformation tricks
- lSize := lFieldDef.Size;
- lPrec := lFieldDef.Precision;
- if (FDbfVersion = xFoxPro) and (lFieldDef.NativeFieldType = 'C') then
- begin
- lPrec := lSize shr 8;
- lSize := lSize and $FF;
- end;
- // update temp field props
- if FDbfVersion = xBaseVII then
- begin
- FillChar(lFieldDescVII, SizeOf(lFieldDescVII), #0);
- StrPLCopy(lFieldDescVII.FieldName, lFieldDef.FieldName, SizeOf(lFieldDescVII.FieldName)-1);
- lFieldDescVII.FieldType := lFieldDef.NativeFieldType;
- lFieldDescVII.FieldSize := lSize;
- lFieldDescVII.FieldPrecision := lPrec;
- // TODO: bug-endianness
- lFieldDescVII.NextAutoInc := lFieldDef.AutoInc;
- //lFieldDescVII.MDXFlag := ???
- end else begin
- FillChar(lFieldDescIII, SizeOf(lFieldDescIII), #0);
- StrPLCopy(lFieldDescIII.FieldName, lFieldDef.FieldName, SizeOf(lFieldDescIII.FieldName)-1);
- lFieldDescIII.FieldType := lFieldDef.NativeFieldType;
- lFieldDescIII.FieldSize := lSize;
- lFieldDescIII.FieldPrecision := lPrec;
- // TODO: bug-endianness
- if FDbfVersion = xFoxPro then
- lFieldDescIII.FieldOffset := lFieldOffset;
- if (PDbfHdr(Header).VerDBF = $02) and (lFieldDef.NativeFieldType in ['0', 'Y', 'T', 'O', '+']) then
- PDbfHdr(Header).VerDBF := $30;
- if (PDbfHdr(Header).VerDBF = $30) and (lFieldDef.NativeFieldType = '+') then
- PDbfHdr(Header).VerDBF := $31;
- end;
- // update our field list
- with FFieldDefs.AddFieldDef do
- begin
- Assign(lFieldDef);
- Offset := lFieldOffset;
- AutoInc := 0;
- end;
- // save field props
- WriteRecord(I, lFieldDescPtr);
- Inc(lFieldOffset, lFieldDef.Size);
- end;
- // end of header
- WriteChar($0D);
- // write memo bit
- if lHasBlob then
- begin
- if FDbfVersion = xBaseIII then
- PDbfHdr(Header).VerDBF := PDbfHdr(Header).VerDBF or $80
- else
- if FDbfVersion = xFoxPro then
- begin
- if PDbfHdr(Header).VerDBF = $02 then
- PDbfHdr(Header).VerDBF := $F5;
- end else
- PDbfHdr(Header).VerDBF := PDbfHdr(Header).VerDBF or $88;
- end;
- // update header
- PDbfHdr(Header).RecordSize := lFieldOffset;
- PDbfHdr(Header).FullHdrSize := HeaderSize + RecordSize * FieldDefs.Count + 1;
- // add empty "back-link" info, whatever it is:
- { A 263-byte range that contains the backlink, which is the relative path of
- an associated database (.dbc) file, information. If the first byte is 0x00,
- the file is not associated with a database. Therefore, database files always
- contain 0x00. }
- if FDbfVersion = xFoxPro then
- Inc(PDbfHdr(Header).FullHdrSize, 263);
- // write dbf header to disk
- inherited WriteHeader;
- finally
- RecordSize := PDbfHdr(Header).RecordSize;
- HeaderSize := PDbfHdr(Header).FullHdrSize;
- // write full header to disk (dbf+fields)
- WriteHeader;
- end;
- if HasBlob and (FMemoFile=nil) then
- begin
- lMemoFileName := ChangeFileExt(FileName, GetMemoExt);
- if FDbfVersion = xFoxPro then
- FMemoFile := TFoxProMemoFile.Create(Self)
- else
- FMemoFile := TDbaseMemoFile.Create(Self);
- FMemoFile.FileName := lMemoFileName;
- FMemoFile.Mode := Mode;
- FMemoFile.AutoCreate := AutoCreate;
- FMemoFile.MemoRecordSize := MemoSize;
- FMemoFile.DbfVersion := FDbfVersion;
- FMemoFile.Open;
- end;
- end;
- function TDbfFile.HasBlob: Boolean;
- var
- I: Integer;
- HasBlob: Boolean;
- begin
- HasBlob := false;
- for I := 0 to FFieldDefs.Count-1 do
- begin
- if FFieldDefs.Items[I].IsBlob then HasBlob := true;
- end;
- Result := HasBlob;
- end;
- function TDbfFile.GetMemoExt: string;
- begin
- if FDbfVersion = xFoxPro then
- Result := '.fpt'
- else
- Result := '.dbt';
- end;
- procedure TDbfFile.Zap;
- begin
- // make recordcount zero
- RecordCount := 0;
- // update recordcount
- PDbfHdr(Header).RecordCount := RecordCount;
- // update disk header
- WriteHeader;
- // update indexes
- RegenerateIndexes;
- end;
- procedure TDbfFile.WriteHeader;
- var
- SystemTime: TSystemTime;
- lDataHdr: PDbfHdr;
- EofTerminator: Byte;
- begin
- if (HeaderSize=0) then
- exit;
- //FillHeader(0);
- lDataHdr := PDbfHdr(Header);
- GetLocalTime(SystemTime);
- lDataHdr.Year := SystemTime.wYear - 1900;
- lDataHdr.Month := SystemTime.wMonth;
- lDataHdr.Day := SystemTime.wDay;
- // lDataHdr.RecordCount := RecordCount;
- inherited WriteHeader;
- EofTerminator := $1A;
- WriteBlock(@EofTerminator, 1, CalcPageOffset(RecordCount+1));
- end;
- procedure TDbfFile.ConstructFieldDefs;
- var
- {lColumnCount,}lHeaderSize,lFieldSize: Integer;
- lPropHdrOffset, lFieldOffset: Integer;
- lFieldDescIII: rFieldDescIII;
- lFieldDescVII: rFieldDescVII;
- lFieldPropsHdr: rFieldPropsHdr;
- lStdProp: rStdPropEntry;
- TempFieldDef: TDbfFieldDef;
- lSize,lPrec,I, lColumnCount: Integer;
- lAutoInc: Cardinal;
- dataPtr: PChar;
- lNativeFieldType: Char;
- lFieldName: string;
- lCanHoldNull: boolean;
- lCurrentNullPosition: integer;
- begin
- FFieldDefs.Clear;
- if DbfVersion >= xBaseVII then
- begin
- lHeaderSize := SizeOf(rAfterHdrVII) + SizeOf(rDbfHdr);
- lFieldSize := SizeOf(rFieldDescVII);
- end else begin
- lHeaderSize := SizeOf(rAfterHdrIII) + SizeOf(rDbfHdr);
- lFieldSize := SizeOf(rFieldDescIII);
- end;
- HeaderSize := lHeaderSize;
- RecordSize := lFieldSize;
- FLockField := nil;
- FNullField := nil;
- FAutoIncPresent := false;
- lColumnCount := (PDbfHdr(Header).FullHdrSize - lHeaderSize) div lFieldSize;
- lFieldOffset := 1;
- lAutoInc := 0;
- I := 1;
- lCurrentNullPosition := 0;
- lCanHoldNull := false;
- try
- // there has to be minimum of one field
- repeat
- // version field info?
- if FDbfVersion >= xBaseVII then
- begin
- ReadRecord(I, @lFieldDescVII);
- lFieldName := AnsiUpperCase(PChar(@lFieldDescVII.FieldName[0]));
- lSize := lFieldDescVII.FieldSize;
- lPrec := lFieldDescVII.FieldPrecision;
- lNativeFieldType := lFieldDescVII.FieldType;
- // TODO: big-endianness
- lAutoInc := lFieldDescVII.NextAutoInc;
- if lNativeFieldType = '+' then
- FAutoIncPresent := true;
- end else begin
- ReadRecord(I, @lFieldDescIII);
- lFieldName := AnsiUpperCase(PChar(@lFieldDescIII.FieldName[0]));
- lSize := lFieldDescIII.FieldSize;
- lPrec := lFieldDescIII.FieldPrecision;
- lNativeFieldType := lFieldDescIII.FieldType;
- lCanHoldNull := (FDbfVersion = xFoxPro) and
- ((lFieldDescIII.FoxProFlags and $2) <> 0) and
- (lFieldName <> '_NULLFLAGS');
- end;
- // apply field transformation tricks
- if (lNativeFieldType = 'C') and (FDbfVersion = xFoxPro) then
- begin
- lSize := lSize + lPrec shl 8;
- lPrec := 0;
- end;
- // add field
- TempFieldDef := FFieldDefs.AddFieldDef;
- with TempFieldDef do
- begin
- FieldName := lFieldName;
- Offset := lFieldOffset;
- Size := lSize;
- Precision := lPrec;
- AutoInc := lAutoInc;
- NativeFieldType := lNativeFieldType;
- if lCanHoldNull then
- begin
- NullPosition := lCurrentNullPosition;
- inc(lCurrentNullPosition);
- end else
- NullPosition := -1;
- end;
- // check valid field:
- // 1) non-empty field name
- // 2) known field type
- // {3) no changes have to be made to precision or size}
- if (Length(lFieldName) = 0) or (TempFieldDef.FieldType = ftUnknown) then
- raise EDbfError.Create(STRING_INVALID_DBF_FILE);
- // determine if lock field present, if present, then store additional info
- if lFieldName = '_DBASELOCK' then
- begin
- FLockField := TempFieldDef;
- FLockUserLen := lSize - 8;
- if FLockUserLen > DbfGlobals.UserNameLen then
- FLockUserLen := DbfGlobals.UserNameLen;
- end else
- if UpperCase(lFieldName) = '_NULLFLAGS' then
- FNullField := TempFieldDef;
- // goto next field
- Inc(lFieldOffset, lSize);
- Inc(I);
- // continue until header termination character found
- // or end of header reached
- until (I > lColumnCount) or (ReadChar = $0D);
- // test if not too many fields
- if FFieldDefs.Count >= 4096 then
- raise EDbfError.CreateFmt(STRING_INVALID_FIELD_COUNT, [FFieldDefs.Count]);
- {
- // removed check because additional data could be present in record
- if (lFieldOffset <> PDbfHdr(Header).RecordSize) then
- begin
- // I removed the message because it confuses end-users.
- // Though there is a major problem if the value is wrong...
- // I try to fix it but it is likely to crash
- PDbfHdr(Header).RecordSize := lFieldOffset;
- end;
- }
- // get current position
- lPropHdrOffset := Stream.Position;
- // dBase 7 -> read field properties, test if enough space, maybe no header
- if (FDbfVersion = xBaseVII) and (lPropHdrOffset + Sizeof(lFieldPropsHdr) <
- PDbfHdr(Header).FullHdrSize) then
- begin
- // read in field properties header
- ReadBlock(@lFieldPropsHdr, SizeOf(lFieldPropsHdr), lPropHdrOffset);
- // read in standard properties
- lFieldOffset := lPropHdrOffset + lFieldPropsHdr.StartStdProps;
- for I := 0 to lFieldPropsHdr.NumStdProps - 1 do
- begin
- // read property data
- ReadBlock(@lStdProp, SizeOf(lStdProp), lFieldOffset+I*SizeOf(lStdProp));
- // is this a constraint?
- if lStdProp.FieldOffset = 0 then
- begin
- // this is a constraint...not implemented
- end else if lStdProp.FieldOffset <= FFieldDefs.Count then begin
- // get fielddef for this property
- TempFieldDef := FFieldDefs.Items[lStdProp.FieldOffset-1];
- // allocate space to store data
- TempFieldDef.AllocBuffers;
- // dataPtr = nil -> no data to retrieve
- dataPtr := nil;
- // store data
- case lStdProp.PropType of
- FieldPropType_Required: TempFieldDef.Required := true;
- FieldPropType_Default:
- begin
- dataPtr := TempFieldDef.DefaultBuf;
- TempFieldDef.HasDefault := true;
- end;
- FieldPropType_Min:
- begin
- dataPtr := TempFieldDef.MinBuf;
- TempFieldDef.HasMin := true;
- end;
- FieldPropType_Max:
- begin
- dataPtr := TempFieldDef.MaxBuf;
- TempFieldDef.HasMax := true;
- end;
- end;
- // get data for this property
- if dataPtr <> nil then
- ReadBlock(dataPtr, lStdProp.DataSize, lPropHdrOffset + lStdProp.DataOffset);
- end;
- end;
- // read custom properties...not implemented
- // read RI properties...not implemented
- end;
- finally
- HeaderSize := PDbfHdr(Header).FullHdrSize;
- RecordSize := PDbfHdr(Header).RecordSize;
- end;
- end;
- function TDbfFile.GetLanguageId: Integer;
- begin
- Result := PDbfHdr(Header).Language;
- end;
- function TDbfFile.GetLanguageStr: String;
- begin
- if FDbfVersion >= xBaseVII then
- Result := PAfterHdrVII(PChar(Header) + SizeOf(rDbfHdr)).LanguageDriverName;
- end;
- {
- I fill the holes with the last records.
- now we can do an 'in-place' pack
- }
- procedure TDbfFile.FastPackTable;
- var
- iDel,iNormal: Integer;
- pDel,pNormal: PChar;
- function FindFirstDel: Boolean;
- begin
- while iDel<=iNormal do
- begin
- ReadRecord(iDel, pDel);
- if (PChar(pDel)^ <> ' ') then
- begin
- Result := true;
- exit;
- end;
- Inc(iDel);
- end;
- Result := false;
- end;
- function FindLastNormal: Boolean;
- begin
- while iNormal>=iDel do
- begin
- ReadRecord(iNormal, pNormal);
- if (PChar(pNormal)^= ' ') then
- begin
- Result := true;
- exit;
- end;
- dec(iNormal);
- end;
- Result := false;
- end;
- begin
- if RecordSize < 1 then Exit;
- GetMem(pNormal, RecordSize);
- GetMem(pDel, RecordSize);
- try
- iDel := 1;
- iNormal := RecordCount;
- while FindFirstDel do
- begin
- // iDel is definitely deleted
- if FindLastNormal then
- begin
- // but is not anymore
- WriteRecord(iDel, pNormal);
- PChar(pNormal)^ := '*';
- WriteRecord(iNormal, pNormal);
- end else begin
- // Cannot found a record after iDel so iDel must be deleted
- dec(iDel);
- break;
- end;
- end;
- // FindFirstDel failed means than iDel is full
- RecordCount := iDel;
- RegenerateIndexes;
- // Pack Memofields
- finally
- FreeMem(pNormal);
- FreeMem(pDel);
- end;
- end;
- type
- TRestructFieldInfo = record
- SourceOffset: Integer;
- DestOffset: Integer;
- Size: Integer;
- end;
- procedure TDbfFile.RestructureTable(DbfFieldDefs: TDbfFieldDefs; Pack: Boolean);
- var
- DestDbfFile: TDbfFile;
- TempIndexDef: TDbfIndexDef;
- TempIndexFile: TIndexFile;
- DestFieldDefs: TDbfFieldDefs;
- TempDstDef, TempSrcDef: TDbfFieldDef;
- OldIndexFiles, NewIndexFiles: TStrings;
- IndexName, NewBaseName, OldBaseName: string;
- I, lRecNo, lFieldNo, lFieldSize, lBlobRecNo, lWRecNo, srcOffset, dstOffset: Integer;
- pBuff, pDestBuff: PChar;
- pBlobRecNoBuff: array[1..11] of Char;
- RestructFieldInfo: array of TRestructFieldInfo;
- BlobStream: TMemoryStream;
- begin
- // nothing to do?
- if (RecordSize < 1) or ((DbfFieldDefs = nil) and not Pack) then
- exit;
- // if no exclusive access, terrible things can happen!
- CheckExclusiveAccess;
- // make up some temporary filenames
- lRecNo := 0;
- FindNextName(FileName, NewBaseName, lRecNo);
- FindNextName(FileName, OldBaseName, lRecNo);
- // select final field definition list
- if DbfFieldDefs = nil then
- begin
- DestFieldDefs := FFieldDefs;
- end else begin
- DestFieldDefs := DbfFieldDefs;
- // copy autoinc values
- for I := 0 to DbfFieldDefs.Count - 1 do
- begin
- lFieldNo := DbfFieldDefs.Items[I].CopyFrom;
- if (lFieldNo >= 0) and (lFieldNo < FFieldDefs.Count) then
- DbfFieldDefs.Items[I].AutoInc := FFieldDefs.Items[lFieldNo].AutoInc;
- end;
- end;
- // create temporary dbf
- DestDbfFile := TDbfFile.Create;
- DestDbfFile.FileName := NewBaseName;
- DestDbfFile.AutoCreate := true;
- DestDbfFile.Mode := pfExclusiveCreate;
- DestDbfFile.UseFloatFields := UseFloatFields;
- DestDbfFile.OnIndexMissing := FOnIndexMissing;
- DestDbfFile.OnLocaleError := FOnLocaleError;
- DestDbfFile.DbfVersion := FDbfVersion;
- DestDbfFile.FileLangId := FileLangId;
- DestDbfFile.Open;
- // create dbf header
- if FMemoFile <> nil then
- DestDbfFile.FinishCreate(DestFieldDefs, FMemoFile.RecordSize)
- else
- DestDbfFile.FinishCreate(DestFieldDefs, 512);
- // adjust size and offsets of fields
- SetLength(RestructFieldInfo, DestFieldDefs.Count);
- for lFieldNo := 0 to DestFieldDefs.Count - 1 do
- begin
- TempDstDef := DestFieldDefs.Items[lFieldNo];
- if TempDstDef.CopyFrom >= 0 then
- begin
- TempSrcDef := FFieldDefs.Items[TempDstDef.CopyFrom];
- if TempDstDef.NativeFieldType in ['F', 'N'] then
- begin
- // get minimum field length
- lFieldSize := Min(TempSrcDef.Precision, TempDstDef.Precision) +
- Min(TempSrcDef.Size - TempSrcDef.Precision,
- TempDstDef.Size - TempDstDef.Precision);
- // if one has dec separator, but other not, we lose one digit
- if (TempDstDef.Precision > 0) xor
- ((TempSrcDef.NativeFieldType in ['F', 'N']) and (TempSrcDef.Precision > 0)) then
- Dec(lFieldSize);
- // should not happen, but check nevertheless (maybe corrupt data)
- if lFieldSize < 0 then
- lFieldSize := 0;
- srcOffset := TempSrcDef.Size - TempSrcDef.Precision -
- (TempDstDef.Size - TempDstDef.Precision);
- if srcOffset < 0 then
- begin
- dstOffset := -srcOffset;
- srcOffset := 0;
- end else begin
- dstOffset := 0;
- end;
- end else begin
- lFieldSize := Min(TempSrcDef.Size, TempDstDef.Size);
- srcOffset := 0;
- dstOffset := 0;
- end;
- with RestructFieldInfo[lFieldNo] do
- begin
- Size := lFieldSize;
- SourceOffset := TempSrcDef.Offset + srcOffset;
- DestOffset := TempDstDef.Offset + dstOffset;
- end;
- end;
- end;
- // add indexes
- TempIndexDef := TDbfIndexDef.Create(nil);
- for I := 0 to FIndexNames.Count - 1 do
- begin
- // get length of extension -> determines MDX or NDX
- IndexName := FIndexNames.Strings[I];
- TempIndexFile := TIndexFile(FIndexNames.Objects[I]);
- TempIndexFile.GetIndexInfo(IndexName, TempIndexDef);
- if Length(ExtractFileExt(IndexName)) > 0 then
- begin
- // NDX index, get unique file name
- lRecNo := 0;
- FindNextName(IndexName, IndexName, lRecNo);
- end;
- // add this index
- DestDbfFile.OpenIndex(IndexName, TempIndexDef.SortField, true, TempIndexDef.Options);
- end;
- TempIndexDef.Free;
- // get memory for index file list
- OldIndexFiles := TStringList.Create;
- NewIndexFiles := TStringList.Create;
- // get memory for record buffers
- GetMem(pBuff, RecordSize);
- BlobStream := TMemoryStream.Create;
- // if restructure, we need memory for dest buffer, otherwise use source
- if DbfFieldDefs = nil then
- pDestBuff := pBuff
- else
- GetMem(pDestBuff, DestDbfFile.RecordSize);
- // let the games begin!
- try
- {$ifdef USE_CACHE}
- BufferAhead := true;
- DestDbfFile.BufferAhead := true;
- {$endif}
- lWRecNo := 1;
- for lRecNo := 1 to RecordCount do
- begin
- // read record from original dbf
- ReadRecord(lRecNo, pBuff);
- // copy record?
- if (pBuff^ <> '*') or not Pack then
- begin
- // if restructure, initialize dest
- if DbfFieldDefs <> nil then
- DestDbfFile.InitRecord(pDestBuff);
- if (DbfFieldDefs <> nil) or (FMemoFile <> nil) then
- begin
- // copy fields
- for lFieldNo := 0 to DestFieldDefs.Count-1 do
- begin
- TempDstDef := DestFieldDefs.Items[lFieldNo];
- // handle blob fields differently
- // don't try to copy new blob fields!
- // DbfFieldDefs = nil -> pack only
- // TempDstDef.CopyFrom >= 0 -> copy existing (blob) field
- if TempDstDef.IsBlob and ((DbfFieldDefs = nil) or (TempDstDef.CopyFrom >= 0)) then
- begin
- // get current blob blockno
- GetFieldData(lFieldNo, ftString, pBuff, @pBlobRecNoBuff[1]);
- lBlobRecNo := StrToIntDef(pBlobRecNoBuff, -1);
- // valid blockno read?
- if lBlobRecNo >= 0 then
- begin
- BlobStream.Clear;
- FMemoFile.ReadMemo(lBlobRecNo, BlobStream);
- BlobStream.Position := 0;
- // always append
- DestDbfFile.FMemoFile.WriteMemo(lBlobRecNo, 0, BlobStream);
- end;
- // write new blockno
- DestDbfFile.SetFieldData(lFieldNo, ftInteger, @lBlobRecNo, pDestBuff);
- end else if (DbfFieldDefs <> nil) and (TempDstDef.CopyFrom >= 0) then
- begin
- // copy content of field
- with RestructFieldInfo[lFieldNo] do
- Move(pBuff[SourceOffset], pDestBuff[DestOffset], Size);
- end;
- end;
- end;
- // write record
- DestDbfFile.WriteRecord(lWRecNo, pDestBuff);
- // update indexes
- for I := 0 to DestDbfFile.IndexFiles.Count - 1 do
- TIndexFile(DestDbfFile.IndexFiles.Items[I]).Insert(lWRecNo, pDestBuff);
- // go to next record
- Inc(lWRecNo);
- end;
- end;
- {$ifdef USE_CACHE}
- BufferAhead := false;
- DestDbfFile.BufferAhead := false;
- {$endif}
- // save index filenames
- for I := 0 to FIndexFiles.Count - 1 do
- begin
- OldIndexFiles.Add(TIndexFile(FIndexFiles.Items[I]).FileName);
- NewIndexFiles.Add(TIndexFile(DestDbfFile.IndexFiles[I]).FileName);
- end;
- // close temp file
- DestDbfFile.Close;
- // close dbf
- Close;
- // if restructure -> rename the old dbf files
- // if pack only -> delete the old dbf files
- if Pack and (DbfFieldDefs = nil) then
- begin
- SysUtils.DeleteFile(FileName);
- SysUtils.DeleteFile(ChangeFileExt(FileName, GetMemoExt));
- end else begin
- SysUtils.RenameFile(FileName, OldBaseName);
- SysUtils.RenameFile(ChangeFileExt(FileName, GetMemoExt), ChangeFileExt(OldBaseName, GetMemoExt));
- end;
- // delete old index files
- for I := 0 to OldIndexFiles.Count - 1 do
- SysUtils.DeleteFile(OldIndexFiles.Strings[I]);
- // rename the new dbf files
- SysUtils.RenameFile(NewBaseName, FileName);
- SysUtils.RenameFile(ChangeFileExt(NewBaseName, GetMemoExt), ChangeFileExt(FileName, GetMemoExt));
- // rename new index files
- for I := 0 to OldIndexFiles.Count - 1 do
- SysUtils.RenameFile(NewIndexFiles.Strings[I], OldIndexFiles.Strings[I]);
- // we have to reinit fielddefs if restructured
- Open;
- // crop deleted records
- RecordCount := lWRecNo - 1;
- // update date/time stamp, recordcount
- PDbfHdr(Header).RecordCount := RecordCount;
- WriteHeader;
- finally
- // close temporary file
- FreeAndNil(DestDbfFile);
- // free mem
- OldIndexFiles.Free;
- NewIndexFiles.Free;
- FreeMem(pBuff);
- FreeAndNil(BlobStream);
- if DbfFieldDefs <> nil then
- FreeMem(pDestBuff);
- end;
- end;
- procedure TDbfFile.RegenerateIndexes;
- var
- lIndexNo: Integer;
- begin
- // recreate every index in every file
- for lIndexNo := 0 to FIndexFiles.Count-1 do
- begin
- PackIndex(TIndexFile(FIndexFiles.Items[lIndexNo]), EmptyStr);
- end;
- end;
- function TDbfFile.GetFieldInfo(FieldName: string): TDbfFieldDef;
- var
- I: Integer;
- lfi: TDbfFieldDef;
- begin
- FieldName := AnsiUpperCase(FieldName);
- for I := 0 to FFieldDefs.Count-1 do
- begin
- lfi := TDbfFieldDef(FFieldDefs.Items[I]);
- if lfi.fieldName = FieldName then
- begin
- Result := lfi;
- exit;
- end;
- end;
- Result := nil;
- end;
- // NOTE: Dst may be nil!
- function TDbfFile.GetFieldData(Column: Integer; DataType: TFieldType; Src, Dst: Pointer): Boolean;
- var
- TempFieldDef: TDbfFieldDef;
- begin
- TempFieldDef := TDbfFieldDef(FFieldDefs.Items[Column]);
- Result := GetFieldDataFromDef(TempFieldDef, DataType, Src, Dst);
- end;
- // NOTE: Dst may be nil!
- function TDbfFile.GetFieldDataFromDef(AFieldDef: TDbfFieldDef; DataType: TFieldType; Src, Dst: Pointer): Boolean;
- var
- FieldOffset, FieldSize: Integer;
- // s: string;
- ldd, ldm, ldy, lth, ltm, lts: Integer;
- date: TDateTime;
- timeStamp: TTimeStamp;
- {$ifdef SUPPORT_INT64}
- function GetInt64FromStrLength(Src: Pointer; Size: Integer; Default: Int64): Int64;
- var
- endChar: Char;
- Code: Integer;
- begin
- // save Char at pos term. null
- endChar := (PChar(Src) + Size)^;
- (PChar(Src) + Size)^ := #0;
- // convert
- Val(PChar(Src), Result, Code);
- // check success
- if Code <> 0 then Result := Default;
- // restore prev. ending Char
- (PChar(Src) + Size)^ := endChar;
- end;
- {$endif}
- procedure CorrectYear(var wYear: Integer);
- var wD, wM, wY, CenturyBase: Word;
- {$ifndef DELPHI_5}
- // 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;
- procedure SaveDateToDst;
- begin
- {$ifdef SUPPORT_NEW_FIELDDATA}
- // Delphi 5 requests a TDateTime
- PDateTime(Dst)^ := date;
- {$else}
- // Delphi 3 and 4 request a TDateTimeRec
- // date is TTimeStamp.date
- // datetime = msecs == BDE timestamp as we implemented it
- if DataType = ftDateTime then
- begin
- PDateTimeRec(Dst)^.DateTime := date;
- end else begin
- PLongInt(Dst)^ := DateTimeToTimeStamp(date).Date;
- end;
- {$endif}
- end;
- begin
- // test if non-nil source (record buffer)
- if Src = nil then
- begin
- Result := false;
- exit;
- end;
- // check Dst = nil, called with dst = nil to check empty field
- if (FNullField <> nil) and (Dst = nil) and (AFieldDef.NullPosition >= 0) then
- begin
- // go to byte with null flag of this field
- Src := PChar(Src) + FNullField.Offset + (AFieldDef.NullPosition shr 3);
- Result := (PByte(Src)^ and (1 shl (AFieldDef.NullPosition and $7))) <> 0;
- exit;
- end;
- FieldOffset := AFieldDef.Offset;
- FieldSize := AFieldDef.Size;
- Src := PChar(Src) + FieldOffset;
- // field types that are binary and of which the fieldsize should not be truncated
- case AFieldDef.NativeFieldType of
- '+', 'I':
- begin
- if FDbfVersion <> xFoxPro then
- begin
- Result := PDWord(Src)^ <> 0;
- if Result and (Dst <> nil) then
- begin
- PInteger(Dst)^ := SwapInt(PInteger(Src)^);
- if Result then
- PInteger(Dst)^ := Integer(PDWord(Dst)^ xor $80000000);
- end;
- end else begin
- Result := true;
- if Dst <> nil then
- PInteger(Dst)^ := PInteger(Src)^;
- end;
- end;
- 'O':
- begin
- {$ifdef SUPPORT_INT64}
- Result := (PInt64(Src)^ <> 0);
- if Result and (Dst <> nil) then
- begin
- SwapInt64(Src, Dst);
- if PInt64(Dst)^ > 0 then
- PInt64(Dst)^ := not PInt64(Dst)^
- else
- PDouble(Dst)^ := PDouble(Dst)^ * -1;
- end;
- {$endif}
- end;
- '@':
- begin
- Result := (PInteger(Src)^ <> 0) and (PInteger(PChar(Src)+4)^ <> 0);
- if Result and (Dst <> nil) then
- begin
- SwapInt64(Src, Dst);
- if FDateTimeHandling = dtBDETimeStamp then
- date := BDETimeStampToDateTime(PDouble(Dst)^)
- else
- date := PDateTime(Dst)^;
- SaveDateToDst;
- end;
- end;
- 'T':
- begin
- // all binary zeroes -> empty datetime
- Result := (PInteger(Src)^ <> 0) or (PInteger(PChar(Src)+4)^ <> 0);
- if Result and (Dst <> nil) then
- begin
- timeStamp.Date := PInteger(Src)^ - 1721425;
- timeStamp.Time := PInteger(PChar(Src)+4)^;
- date := TimeStampToDateTime(timeStamp);
- SaveDateToDst;
- end;
- end;
- 'Y':
- begin
- {$ifdef SUPPORT_INT64}
- Result := true;
- if Dst <> nil then
- begin
- // TODO: data is little endian;
- case DataType of
- ftCurrency:
- begin
- PDouble(Dst)^ := PInt64(Src)^ / 10000.0;
- end;
- ftBCD:
- begin
- PCurrency(Dst)^ := PCurrency(Src)^;
- end;
- end;
- end;
- {$endif}
- end;
- else
- // SetString(s, PChar(Src) + FieldOffset, FieldSize );
- // s := {TrimStr(s)} TrimRight(s);
- // truncate spaces at end by shortening fieldsize
- while (FieldSize > 0) and ((PChar(Src) + FieldSize - 1)^ = ' ') do
- dec(FieldSize);
- // if not string field, truncate spaces at beginning too
- if DataType <> ftString then
- while (FieldSize > 0) and (PChar(Src)^ = ' ') do
- begin
- inc(PChar(Src));
- dec(FieldSize);
- end;
- // return if field is empty
- Result := FieldSize > 0;
- 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 (PChar(Src)^ = 'T') or (PChar(Src)^ = 't') then
- PWord(Dst)^ := 1
- else
- PWord(Dst)^ := 0;
- end;
- ftSmallInt:
- PSmallInt(Dst)^ := GetIntFromStrLength(Src, FieldSize, 0);
- {$ifdef SUPPORT_INT64}
- ftLargeInt:
- PLargeInt(Dst)^ := GetInt64FromStrLength(Src, FieldSize, 0);
- {$endif}
- ftInteger:
- PInteger(Dst)^ := GetIntFromStrLength(Src, FieldSize, 0);
- ftFloat, ftCurrency:
- PDouble(Dst)^ := DbfStrToFloat(Src, FieldSize);
- ftDate, ftDateTime:
- begin
- // get year, month, day
- ldy := GetIntFromStrLength(PChar(Src) + 0, 4, 1);
- ldm := GetIntFromStrLength(PChar(Src) + 4, 2, 1);
- ldd := GetIntFromStrLength(PChar(Src) + 6, 2, 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 (ldy < 100) and (PChar(Src)[0] = #32) and (PChar(Src)[1] = #32) then
- CorrectYear(ldy);
- try
- date := EncodeDate(ldy, ldm, ldd);
- except
- date := 0;
- end;
- // time stored too?
- if (AFieldDef.FieldType = ftDateTime) and (DataType = ftDateTime) then
- begin
- // get hour, minute, second
- lth := GetIntFromStrLength(PChar(Src) + 8, 2, 1);
- ltm := GetIntFromStrLength(PChar(Src) + 10, 2, 1);
- lts := GetIntFromStrLength(PChar(Src) + 12, 2, 1);
- // encode
- try
- date := date + EncodeTime(lth, ltm, lts, 0);
- except
- date := 0;
- end;
- end;
- SaveDateToDst;
- end;
- ftString:
- StrLCopy(Dst, Src, FieldSize);
- end else begin
- case DataType of
- ftString:
- if Dst <> nil then
- PChar(Dst)[0] := #0;
- end;
- end;
- end;
- end;
- procedure TDbfFile.UpdateNullField(Buffer: Pointer; AFieldDef: TDbfFieldDef;
- Action: TUpdateNullField);
- var
- NullDst: pbyte;
- Mask: byte;
- begin
- // this field has null setting capability
- NullDst := PByte(PChar(Buffer) + FNullField.Offset + (AFieldDef.NullPosition shr 3));
- Mask := 1 shl (AFieldDef.NullPosition and $7);
- if Action = unSet then
- begin
- // clear the field, set null flag
- NullDst^ := NullDst^ or Mask;
- end else begin
- // set field data, clear null flag
- NullDst^ := NullDst^ and not Mask;
- end;
- end;
- procedure TDbfFile.SetFieldData(Column: Integer; DataType: TFieldType; Src, Dst: Pointer);
- const
- IsBlobFieldToPadChar: array[Boolean] of Char = (#32, '0');
- SrcNilToUpdateNullField: array[boolean] of TUpdateNullField = (unClear, unSet);
- var
- FieldSize,FieldPrec: Integer;
- TempFieldDef: TDbfFieldDef;
- Len, IntValue: Integer;
- year, month, day: Word;
- hour, minute, sec, msec: Word;
- date: TDateTime;
- timeStamp: TTimeStamp;
- procedure LoadDateFromSrc;
- begin
- {$ifdef SUPPORT_NEW_FIELDDATA}
- // Delphi 5 passes a TDateTime
- date := PDateTime(Src)^;
- {$else}
- // Delphi 3 and 4 pass a TDateTimeRec with a time stamp
- // date = integer
- // datetime = msecs == BDETimeStampToDateTime as we implemented it
- if DataType = ftDateTime then
- begin
- date := PDouble(Src)^;
- end else begin
- timeStamp.Time := 0;
- timeStamp.Date := PLongInt(Src)^;
- date := TimeStampToDateTime(timeStamp);
- end;
- {$endif}
- end;
- begin
- TempFieldDef := TDbfFieldDef(FFieldDefs.Items[Column]);
- FieldSize := TempFieldDef.Size;
- FieldPrec := TempFieldDef.Precision;
- // if src = nil then write empty field
- // symmetry with above
- // foxpro has special _nullfield for flagging fields as `null'
- if (FNullField <> nil) and (TempFieldDef.NullPosition >= 0) then
- UpdateNullField(Dst, TempFieldDef, SrcNilToUpdateNullField[Src = nil]);
- // copy field data to record buffer
- Dst := PChar(Dst) + TempFieldDef.Offset;
- case TempFieldDef.NativeFieldType of
- '+', 'I':
- begin
- if FDbfVersion <> xFoxPro then
- begin
- if Src = nil then
- IntValue := 0
- else
- IntValue := Integer(PDWord(Src)^ xor $80000000);
- PInteger(Dst)^ := SwapInt(IntValue);
- end else begin
- if Src = nil then
- PInteger(Dst)^ := 0
- else
- PInteger(Dst)^ := PInteger(Src)^;
- end;
- end;
- 'O':
- begin
- {$ifdef SUPPORT_INT64}
- if Src = nil then
- begin
- PInt64(Dst)^ := 0;
- end else begin
- if PDouble(Src)^ < 0 then
- PLargeInt(Dst)^ := not PLargeInt(Src)^
- else
- PDouble(Dst)^ := (PDouble(Src)^) * -1;
- SwapInt64(Dst, Dst);
- end;
- {$endif}
- end;
- '@':
- begin
- if Src = nil then
- begin
- PInteger(Dst)^ := 0;
- PInteger(PChar(Dst)+4)^ := 0;
- end else begin
- LoadDateFromSrc;
- if FDateTimeHandling = dtBDETimeStamp then
- date := DateTimeToBDETimeStamp(date);
- SwapInt64(@date, Dst);
- end;
- end;
- 'T':
- begin
- // all binary zeroes -> empty datetime
- if Src = nil then
- begin
- PInteger(Dst)^ := 0;
- PInteger(PChar(Dst)+4)^ := 0;
- end else begin
- LoadDateFromSrc;
- timeStamp := DateTimeToTimeStamp(date);
- PInteger(Dst)^ := timeStamp.Date + 1721425;
- PInteger(PChar(Dst)+4)^ := timeStamp.Time;
- end;
- end;
- 'Y':
- begin
- {$ifdef SUPPORT_INT64}
- if Src = nil then
- begin
- PInt64(Dst)^ := 0
- end else begin
- case DataType of
- ftCurrency:
- PInt64(Dst)^ := Trunc(PDouble(Src)^ * 10000);
- ftBCD:
- PCurrency(Dst)^ := PCurrency(Src)^;
- end;
- end;
- // TODO: data is little endian
- {$endif}
- end;
- else
- if Src = nil then
- begin
- FillChar(Dst^, FieldSize, ' ');
- end else begin
- case DataType of
- ftBoolean:
- begin
- if PWord(Src)^ <> 0 then
- PChar(Dst)^ := 'T'
- else
- PChar(Dst)^ := 'F';
- end;
- ftSmallInt:
- GetStrFromInt_Width(PSmallInt(Src)^, FieldSize, PChar(Dst), #32);
- {$ifdef SUPPORT_INT64}
- ftLargeInt:
- GetStrFromInt64_Width(PLargeInt(Src)^, FieldSize, PChar(Dst), #32);
- {$endif}
- ftFloat, ftCurrency:
- FloatToDbfStr(PDouble(Src)^, FieldSize, FieldPrec, PChar(Dst));
- ftInteger:
- GetStrFromInt_Width(PInteger(Src)^, FieldSize, PChar(Dst),
- IsBlobFieldToPadChar[TempFieldDef.IsBlob]);
- ftDate, ftDateTime:
- begin
- LoadDateFromSrc;
- // decode
- DecodeDate(date, year, month, day);
- // format is yyyymmdd
- GetStrFromInt_Width(year, 4, PChar(Dst), '0');
- GetStrFromInt_Width(month, 2, PChar(Dst)+4, '0');
- GetStrFromInt_Width(day, 2, PChar(Dst)+6, '0');
- // do time too if datetime
- if DataType = ftDateTime then
- begin
- DecodeTime(date, hour, minute, sec, msec);
- // format is hhmmss
- GetStrFromInt_Width(hour, 2, PChar(Dst)+8, '0');
- GetStrFromInt_Width(minute, 2, PChar(Dst)+10, '0');
- GetStrFromInt_Width(sec, 2, PChar(Dst)+12, '0');
- end;
- end;
- ftString:
- begin
- // copy data
- Len := StrLen(Src);
- if Len > FieldSize then
- Len := FieldSize;
- Move(Src^, Dst^, Len);
- // fill remaining space with spaces
- FillChar((PChar(Dst)+Len)^, FieldSize - Len, ' ');
- end;
- end; // case datatype
- end;
- end;
- end;
- procedure TDbfFile.InitRecord(DestBuf: PChar);
- var
- TempFieldDef: TDbfFieldDef;
- I: Integer;
- begin
- // clear buffer (assume all string, fix specific fields later)
- FillChar(DestBuf^, RecordSize,' ');
- // set nullflags field so that all fields are null
- if FNullField <> nil then
- FillChar(PChar(DestBuf+FNullField.Offset)^, FNullField.Size, $FF);
- // check binary and default fields
- for I := 0 to FFieldDefs.Count-1 do
- begin
- TempFieldDef := FFieldDefs.Items[I];
- // binary field?
- if TempFieldDef.NativeFieldType in ['I', 'O', '@', '+', '0', 'Y'] then
- FillChar(PChar(DestBuf+TempFieldDef.Offset)^, TempFieldDef.Size, 0);
- // copy default value?
- if TempFieldDef.HasDefault then
- begin
- Move(TempFieldDef.DefaultBuf[0], DestBuf[TempFieldDef.Offset], TempFieldDef.Size);
- // clear the null flag, this field has a value
- if FNullField <> nil then
- UpdateNullField(DestBuf, TempFieldDef, unClear);
- end;
- end;
- end;
- procedure TDbfFile.ApplyAutoIncToBuffer(DestBuf: PChar);
- var
- TempFieldDef: TDbfFieldDef;
- I, NextVal, lAutoIncOffset: {LongWord} Cardinal; {Delphi 3 does not know LongWord?}
- begin
- if FAutoIncPresent then
- begin
- // if shared, reread header to find new autoinc values
- if NeedLocks then
- begin
- // lock header so nobody else can use this value
- LockPage(0, true);
- end;
- // find autoinc fields
- for I := 0 to FFieldDefs.Count-1 do
- begin
- TempFieldDef := FFieldDefs.Items[I];
- if (TempFieldDef.NativeFieldType = '+') then
- begin
- // read current auto inc, from header or field, depending on sharing
- lAutoIncOffset := sizeof(rDbfHdr) + sizeof(rAfterHdrVII) +
- FieldDescVII_AutoIncOffset + I * sizeof(rFieldDescVII);
- // TODO: big-endianness
- if NeedLocks then
- ReadBlock(@NextVal, 4, lAutoIncOffset)
- else
- NextVal := TempFieldDef.AutoInc;
- // store to buffer, positive = high bit on, so flip it
- PCardinal(DestBuf+TempFieldDef.Offset)^ := SwapInt(NextVal or $80000000);
- // increase
- Inc(NextVal);
- TempFieldDef.AutoInc := NextVal;
- // write new value to header buffer
- PCardinal(FHeader+lAutoIncOffset)^ := NextVal;
- end;
- end;
- // write modified header (new autoinc values) to file
- WriteHeader;
- // release lock if locked
- if NeedLocks then
- UnlockPage(0);
- end;
- end;
- procedure TDbfFile.TryExclusive;
- var
- I: Integer;
- begin
- inherited;
- // exclusive succeeded? open index & memo exclusive too
- if Mode in [pfMemoryCreate..pfExclusiveOpen] then
- begin
- // indexes
- for I := 0 to FIndexFiles.Count - 1 do
- TPagedFile(FIndexFiles[I]).TryExclusive;
- // memo
- if FMemoFile <> nil then
- FMemoFile.TryExclusive;
- end;
- end;
- procedure TDbfFile.EndExclusive;
- var
- I: Integer;
- begin
- // end exclusive on index & memo too
- for I := 0 to FIndexFiles.Count - 1 do
- TPagedFile(FIndexFiles[I]).EndExclusive;
- // memo
- if FMemoFile <> nil then
- FMemoFile.EndExclusive;
- // dbf file
- inherited;
- end;
- procedure TDbfFile.OpenIndex(IndexName, IndexField: string; CreateIndex: Boolean; Options: TIndexOptions);
- //
- // assumes IndexName is not empty
- //
- const
- // memcr, memop, excr, exopen, rwcr, rwopen, rdonly
- IndexOpenMode: array[pfMemoryCreate..pfReadOnly] of TPagedFileMode =
- (pfMemoryCreate, pfMemoryCreate, pfExclusiveOpen, pfExclusiveOpen, pfReadWriteOpen, pfReadWriteOpen,
- pfReadOnly);
- var
- lIndexFile: TIndexFile;
- lIndexFileName: string;
- createMdxFile: Boolean;
- addedIndexFile: Integer;
- addedIndexName: Integer;
- begin
- // init
- addedIndexFile := -1;
- addedIndexName := -1;
- createMdxFile := false;
- // index already opened?
- lIndexFile := GetIndexByName(IndexName);
- if (lIndexFile <> nil) and (lIndexFile = FMdxFile) and CreateIndex then
- begin
- // index already exists in MDX file
- // delete it to save space, this causes a repage
- FMdxFile.DeleteIndex(IndexName);
- // index no longer exists
- lIndexFile := nil;
- end;
- if (lIndexFile = nil) and (IndexName <> EmptyStr) then
- begin
- // check if no extension, then create MDX index
- if Length(ExtractFileExt(IndexName)) = 0 then
- begin
- // check if mdx index already opened
- if FMdxFile <> nil then
- begin
- lIndexFileName := EmptyStr;
- lIndexFile := FMdxFile;
- end else begin
- lIndexFileName := ChangeFileExt(FileName, '.mdx');
- createMdxFile := true;
- end;
- end else begin
- lIndexFileName := IndexName;
- end;
- // do we need to open / create file?
- if lIndexFileName <> EmptyStr then
- begin
- // try to open / create the file
- lIndexFile := TIndexFile.Create(Self);
- lIndexFile.FileName := lIndexFileName;
- lIndexFile.Mode := IndexOpenMode[Mode];
- lIndexFile.AutoCreate := CreateIndex or (Length(IndexField) > 0);
- lIndexFile.CodePage := UseCodePage;
- lIndexFile.OnLocaleError := FOnLocaleError;
- lIndexFile.Open;
- // index file ready for use?
- if not lIndexFile.ForceClose then
- begin
- // if we had to create the index, store that info
- CreateIndex := lIndexFile.FileCreated;
- // check if trying to create empty index
- if CreateIndex and (IndexField = EmptyStr) then
- begin
- FreeAndNil(lIndexFile);
- CreateIndex := false;
- createMdxFile := false;
- end else begin
- // add new index file to list
- addedIndexFile := FIndexFiles.Add(lIndexFile);
- end;
- // created accompanying mdx file?
- if createMdxFile then
- FMdxFile := lIndexFile;
- end else begin
- // asked to close! close file
- FreeAndNil(lIndexFile);
- end;
- end;
- // check if file succesfully opened
- if lIndexFile <> nil then
- begin
- // add index to list
- addedIndexName := FIndexNames.AddObject(IndexName, lIndexFile);
- end;
- end;
- // create it or open it?
- if lIndexFile <> nil then
- begin
- if not CreateIndex then
- if lIndexFile = FMdxFile then
- CreateIndex := lIndexFile.IndexOf(IndexName) < 0;
- if CreateIndex then
- begin
- // try get exclusive mode
- if IsSharedAccess then TryExclusive;
- // always uppercase index expression
- IndexField := AnsiUpperCase(IndexField);
- try
- // create index if asked
- lIndexFile.CreateIndex(IndexField, IndexName, Options);
- // add all records
- PackIndex(lIndexFile, IndexName);
- // if we wanted to open index readonly, but we created it, then reopen
- if Mode = pfReadOnly then
- begin
- lIndexFile.CloseFile;
- lIndexFile.Mode := pfReadOnly;
- lIndexFile.OpenFile;
- end;
- // if mdx file just created, write changes to dbf header
- // set MDX flag to true
- PDbfHdr(Header).MDXFlag := 1;
- WriteHeader;
- except
- // :-( need to undo 'damage'....
- // remove index from list(s) if just added
- if addedIndexFile >= 0 then
- FIndexFiles.Delete(addedIndexFile);
- if addedIndexName >= 0 then
- FIndexNames.Delete(addedIndexName);
- // delete index file itself
- lIndexFile.DeleteIndex(IndexName);
- // if no file created, do not destroy!
- if addedIndexFile >= 0 then
- begin
- lIndexFile.Close;
- Sysutils.DeleteFile(lIndexFileName);
- if FMdxFile = lIndexFile then
- FMdxFile := nil;
- lIndexFile.Free;
- end;
- end;
- // return to previous mode
- if TempMode <> pfNone then EndExclusive;
- end;
- end;
- end;
- procedure TDbfFile.PackIndex(lIndexFile: TIndexFile; AIndexName: string);
- var
- prevMode: TIndexUpdateMode;
- prevIndex: string;
- cur, last: Integer;
- {$ifdef USE_CACHE}
- prevCache: Integer;
- {$endif}
- begin
- // save current mode in case we change it
- prevMode := lIndexFile.UpdateMode;
- prevIndex := lIndexFile.IndexName;
- // check if index specified
- if Length(AIndexName) > 0 then
- begin
- // only pack specified index, not all
- lIndexFile.IndexName := AIndexName;
- lIndexFile.ClearIndex;
- lIndexFile.UpdateMode := umCurrent;
- end else begin
- lIndexFile.IndexName := EmptyStr;
- lIndexFile.Clear;
- lIndexFile.UpdateMode := umAll;
- end;
- // prepare update
- cur := 1;
- last := RecordCount;
- {$ifdef USE_CACHE}
- BufferAhead := true;
- prevCache := lIndexFile.CacheSize;
- lIndexFile.CacheSize := GetFreeMemory;
- if lIndexFile.CacheSize < 16384 * 1024 then
- lIndexFile.CacheSize := 16384 * 1024;
- {$endif}
- while cur <= last do
- begin
- ReadRecord(cur, FPrevBuffer);
- lIndexFile.Insert(cur, FPrevBuffer);
- inc(cur);
- end;
- // restore previous mode
- {$ifdef USE_CACHE}
- BufferAhead := false;
- lIndexFile.BufferAhead := true;
- {$endif}
- lIndexFile.Flush;
- {$ifdef USE_CACHE}
- lIndexFile.BufferAhead := false;
- lIndexFile.CacheSize := prevCache;
- {$endif}
- lIndexFile.UpdateMode := prevMode;
- lIndexFile.IndexName := prevIndex;
- end;
- procedure TDbfFile.RepageIndex(AIndexFile: string);
- var
- lIndexNo: Integer;
- begin
- // DBF MDX index?
- if Length(AIndexFile) = 0 then
- begin
- if FMdxFile <> nil then
- begin
- // repage attached mdx
- FMdxFile.RepageFile;
- end;
- end else begin
- // search index file
- lIndexNo := FIndexNames.IndexOf(AIndexFile);
- // index found?
- if lIndexNo >= 0 then
- TIndexFile(FIndexNames.Objects[lIndexNo]).RepageFile;
- end;
- end;
- procedure TDbfFile.CompactIndex(AIndexFile: string);
- var
- lIndexNo: Integer;
- begin
- // DBF MDX index?
- if Length(AIndexFile) = 0 then
- begin
- if FMdxFile <> nil then
- begin
- // repage attached mdx
- FMdxFile.CompactFile;
- end;
- end else begin
- // search index file
- lIndexNo := FIndexNames.IndexOf(AIndexFile);
- // index found?
- if lIndexNo >= 0 then
- TIndexFile(FIndexNames.Objects[lIndexNo]).CompactFile;
- end;
- end;
- procedure TDbfFile.CloseIndex(AIndexName: string);
- var
- lIndexNo: Integer;
- lIndex: TIndexFile;
- begin
- // search index file
- lIndexNo := FIndexNames.IndexOf(AIndexName);
- // don't close mdx file
- if (lIndexNo >= 0) then
- begin
- // get index pointer
- lIndex := TIndexFile(FIndexNames.Objects[lIndexNo]);
- if (lIndex <> FMdxFile) then
- begin
- // close file
- lIndex.Free;
- // remove from lists
- FIndexFiles.Remove(lIndex);
- FIndexNames.Delete(lIndexNo);
- // was this the current index?
- if (FCurIndex = lIndexNo) then
- begin
- FCurIndex := -1;
- //FCursor := FDbfCursor;
- end;
- end;
- end;
- end;
- function TDbfFile.DeleteIndex(const AIndexName: string): Boolean;
- var
- lIndexNo: Integer;
- lIndex: TIndexFile;
- lFileName: string;
- begin
- // search index file
- lIndexNo := FIndexNames.IndexOf(AIndexName);
- Result := lIndexNo >= 0;
- // found index?
- if Result then
- begin
- // can only delete indexes from MDX files
- lIndex := TIndexFile(FIndexNames.Objects[lIndexNo]);
- if lIndex = FMdxFile then
- begin
- lIndex.DeleteIndex(AIndexName);
- // remove it from the list
- FIndexNames.Delete(lIndexNo);
- // no more MDX indexes?
- lIndexNo := FIndexNames.IndexOfObject(FMdxFile);
- if lIndexNo = -1 then
- begin
- // no MDX indexes left
- lIndexNo := FIndexFiles.IndexOf(FMdxFile);
- if lIndexNo >= 0 then
- FIndexFiles.Delete(lIndexNo);
- lFileName := FMdxFile.FileName;
- FreeAndNil(FMdxFile);
- // erase file
- Sysutils.DeleteFile(lFileName);
- // clear mdx flag
- PDbfHdr(Header).MDXFlag := 0;
- WriteHeader;
- end;
- end else begin
- // close index first
- CloseIndex(AIndexName);
- // delete file from disk
- SysUtils.DeleteFile(AIndexName);
- end;
- end;
- end;
- function TDbfFile.Insert(Buffer: PChar): integer;
- var
- newRecord: Integer;
- lIndex: TIndexFile;
- error: Boolean;
- procedure RollBackIndexesAndRaise(HighIndex: Integer; IndexError: Boolean);
- var
- errorMsg: string;
- I: Integer;
- begin
- // rollback committed indexes
- error := IndexError;
- for I := 0 to HighIndex do
- begin
- lIndex := TIndexFile(FIndexFiles.Items[I]);
- lIndex.Delete(newRecord, Buffer);
- if lIndex.WriteError then
- begin
- lIndex.ResetError;
- error := true;
- end;
- end;
- // reset any dbf file error
- ResetError;
- // if part of indexes committed -> always index error msg
- // if error while rolling back index -> index error msg
- if error then
- errorMsg := STRING_WRITE_INDEX_ERROR
- else
- errorMsg := STRING_WRITE_ERROR;
- raise EDbfWriteError.Create(errorMsg);
- end;
- var
- I: Integer;
- begin
- // get new record index
- Result := 0;
- newRecord := RecordCount+1;
- // lock record so we can write data
- while not LockPage(newRecord, false) do
- Inc(newRecord);
- // write autoinc value
- ApplyAutoIncToBuffer(Buffer);
- // check indexes -> possible key violation
- I := 0; error := false;
- while (I < FIndexFiles.Count) and not error do
- begin
- lIndex := TIndexFile(FIndexFiles.Items[I]);
- error := lIndex.CheckKeyViolation(Buffer);
- Inc(I);
- end;
- // error occured while inserting? -> abort
- if error then
- begin
- UnlockPage(newRecord);
- lIndex.InsertError;
- // don't have to exit -- unreachable code
- end;
- // no key violation, insert record into index(es)
- for I := 0 to FIndexFiles.Count-1 do
- begin
- lIndex := TIndexFile(FIndexFiles.Items[I]);
- lIndex.Insert(newRecord, Buffer);
- if lIndex.WriteError then
- begin
- // if there's an index write error, I shouldn't
- // try to write the dbf header and the new record,
- // but raise an exception right away
- RollBackIndexesAndRaise(I, True);
- end;
- end;
- // indexes ok -> continue inserting
- // update header record count
- LockPage(0, true);
- // read current header
- ReadHeader;
- // increase current record count
- Inc(PDbfHdr(Header).RecordCount);
- // write header to disk
- WriteHeader;
- // done with header
- UnlockPage(0);
- if WriteError then
- begin
- // couldn't write header, so I shouldn't
- // even try to write the record.
- //
- // At this point I should "roll back"
- // the already written index records.
- // if this fails, I'm in deep trouble!
- RollbackIndexesAndRaise(FIndexFiles.Count-1, False);
- end;
- // write locking info
- if FLockField <> nil then
- WriteLockInfo(Buffer);
- // write buffer to disk
- WriteRecord(newRecord, Buffer);
- // done updating, unlock
- UnlockPage(newRecord);
- // error occurred while writing?
- if WriteError then
- begin
- // -- Tobias --
- // The record couldn't be written, so
- // the written index records and the
- // change to the header have to be
- // rolled back
- LockPage(0, true);
- ReadHeader;
- Dec(PDbfHdr(Header).RecordCount);
- WriteHeader;
- UnlockPage(0);
- // roll back indexes too
- RollbackIndexesAndRaise(FIndexFiles.Count-1, False);
- end else
- Result := newRecord;
- end;
- procedure TDbfFile.WriteLockInfo(Buffer: PChar);
- //
- // *) assumes FHasLockField = true
- //
- var
- year, month, day, hour, minute, sec, msec: Word;
- lockoffset: integer;
- begin
- // increase change count
- lockoffset := FLockField.Offset;
- Inc(PWord(Buffer+lockoffset)^);
- // set time
- DecodeDate(Now(), year, month, day);
- DecodeTime(Now(), hour, minute, sec, msec);
- Buffer[lockoffset+2] := Char(hour);
- Buffer[lockoffset+3] := Char(minute);
- Buffer[lockoffset+4] := Char(sec);
- // set date
- Buffer[lockoffset+5] := Char(year - 1900);
- Buffer[lockoffset+6] := Char(month);
- Buffer[lockoffset+7] := Char(day);
- // set name
- FillChar(Buffer[lockoffset+8], FLockField.Size-8, ' ');
- Move(DbfGlobals.UserName[1], Buffer[lockoffset+8], FLockUserLen);
- end;
- procedure TDbfFile.LockRecord(RecNo: Integer; Buffer: PChar);
- begin
- if LockPage(RecNo, false) then
- begin
- // reread data
- ReadRecord(RecNo, Buffer);
- // store previous data for updating indexes
- Move(Buffer^, FPrevBuffer^, RecordSize);
- // lock succeeded, update lock info, if field present
- if FLockField <> nil then
- begin
- // update buffer
- WriteLockInfo(Buffer);
- // write to disk
- WriteRecord(RecNo, Buffer);
- end;
- end else
- raise EDbfError.Create(STRING_RECORD_LOCKED);
- end;
- procedure TDbfFile.UnlockRecord(RecNo: Integer; Buffer: PChar);
- var
- I: Integer;
- lIndex: TIndexFile;
- begin
- // update indexes, possible key violation
- for I := 0 to FIndexFiles.Count - 1 do
- begin
- lIndex := TIndexFile(FIndexFiles.Items[I]);
- lIndex.Update(RecNo, FPrevBuffer, Buffer);
- end;
- // write new record buffer, all keys ok
- WriteRecord(RecNo, Buffer);
- // done updating, unlock
- UnlockPage(RecNo);
- end;
- procedure TDbfFile.RecordDeleted(RecNo: Integer; Buffer: PChar);
- var
- I: Integer;
- lIndex: TIndexFile;
- begin
- // notify indexes: record deleted
- for I := 0 to FIndexFiles.Count - 1 do
- begin
- lIndex := TIndexFile(FIndexFiles.Items[I]);
- lIndex.RecordDeleted(RecNo, Buffer);
- end;
- end;
- procedure TDbfFile.RecordRecalled(RecNo: Integer; Buffer: PChar);
- var
- I: Integer;
- lIndex: TIndexFile;
- begin
- // notify indexes: record recalled
- for I := 0 to FIndexFiles.Count - 1 do
- begin
- lIndex := TIndexFile(FIndexFiles.Items[I]);
- lIndex.RecordRecalled(RecNo, Buffer);
- end;
- end;
- procedure TDbfFile.SetRecordSize(NewSize: Integer);
- begin
- if NewSize <> RecordSize then
- begin
- if FPrevBuffer <> nil then
- FreeMemAndNil(Pointer(FPrevBuffer));
- if NewSize > 0 then
- GetMem(FPrevBuffer, NewSize);
- end;
- inherited;
- end;
- function TDbfFile.GetIndexByName(AIndexName: string): TIndexFile;
- var
- I: Integer;
- begin
- I := FIndexNames.IndexOf(AIndexName);
- if I >= 0 then
- Result := TIndexFile(FIndexNames.Objects[I])
- else
- Result := nil;
- end;
- //====================================================================
- // TDbfCursor
- //====================================================================
- constructor TDbfCursor.Create(DbfFile: TDbfFile);
- begin
- inherited Create(DbfFile);
- end;
- function TDbfCursor.Next: Boolean;
- begin
- if TDbfFile(PagedFile).IsRecordPresent(FPhysicalRecNo) then
- begin
- inc(FPhysicalRecNo);
- Result := TDbfFile(PagedFile).IsRecordPresent(FPhysicalRecNo);
- end else begin
- FPhysicalRecNo := TDbfFile(PagedFile).CachedRecordCount + 1;
- Result := false;
- end;
- end;
- function TDbfCursor.Prev: Boolean;
- begin
- if FPhysicalRecNo > 0 then
- dec(FPhysicalRecNo)
- else
- FPhysicalRecNo := 0;
- Result := FPhysicalRecNo > 0;
- end;
- procedure TDbfCursor.First;
- begin
- FPhysicalRecNo := 0;
- end;
- procedure TDbfCursor.Last;
- var
- max: Integer;
- begin
- max := TDbfFile(PagedFile).RecordCount;
- if max = 0 then
- FPhysicalRecNo := 0
- else
- FPhysicalRecNo := max + 1;
- end;
- function TDbfCursor.GetPhysicalRecNo: Integer;
- begin
- Result := FPhysicalRecNo;
- end;
- procedure TDbfCursor.SetPhysicalRecNo(RecNo: Integer);
- begin
- FPhysicalRecNo := RecNo;
- end;
- function TDbfCursor.GetSequentialRecordCount: Integer;
- begin
- Result := TDbfFile(PagedFile).RecordCount;
- end;
- function TDbfCursor.GetSequentialRecNo: Integer;
- begin
- Result := FPhysicalRecNo;
- end;
- procedure TDbfCursor.SetSequentialRecNo(RecNo: Integer);
- begin
- FPhysicalRecNo := RecNo;
- end;
- // codepage enumeration procedure
- var
- TempCodePageList: TList;
- // LPTSTR = PChar ok?
- function CodePagesProc(CodePageString: PChar): Cardinal; stdcall;
- begin
- // add codepage to list
- TempCodePageList.Add(Pointer(GetIntFromStrLength(CodePageString, StrLen(CodePageString), -1)));
- // continue enumeration
- Result := 1;
- end;
- //====================================================================
- // TDbfGlobals
- //====================================================================
- constructor TDbfGlobals.Create;
- begin
- FCodePages := TList.Create;
- FDefaultOpenCodePage := GetACP;
- // the following sets FDefaultCreateLangId
- DefaultCreateCodePage := GetACP;
- FCurrencyAsBCD := true;
- // determine which code pages are installed
- TempCodePageList := FCodePages;
- EnumSystemCodePages(@CodePagesProc, {CP_SUPPORTED} CP_INSTALLED);
- TempCodePageList := nil;
- InitUserName;
- end;
- procedure TDbfGlobals.InitUserName;
- {$ifdef FPC}
- {$ifndef WIN32}
- var
- TempName: UTSName;
- {$endif}
- {$endif}
- begin
- {$ifdef WIN32}
- FUserNameLen := MAX_COMPUTERNAME_LENGTH+1;
- SetLength(FUserName, FUserNameLen);
- // Windows.GetUserName(@FUserName[0], FUserNameLen);
- Windows.GetComputerName(PChar(FUserName), FUserNameLen);
- SetLength(FUserName, FUserNameLen);
- {$else}
- {$ifdef FPC}
- FpUname(TempName);
- FUserName := TempName.machine;
- FUserNameLen := Length(FUserName);
- {$endif}
- {$endif}
- end;
- destructor TDbfGlobals.Destroy; {override;}
- begin
- FCodePages.Free;
- end;
- function TDbfGlobals.GetDefaultCreateCodePage: Integer;
- begin
- Result := LangId_To_CodePage[FDefaultCreateLangId];
- end;
- procedure TDbfGlobals.SetDefaultCreateCodePage(NewCodePage: Integer);
- begin
- FDefaultCreateLangId := ConstructLangId(NewCodePage, GetUserDefaultLCID, false);
- end;
- function TDbfGlobals.CodePageInstalled(ACodePage: Integer): Boolean;
- begin
- Result := FCodePages.IndexOf(Pointer(ACodePage)) >= 0;
- end;
- initialization
- finalization
- FreeAndNil(DbfGlobals);
- (*
- Stuffs non implemented yet
- TFoxCDXHeader = Record
- PointerRootNode : Integer;
- PointerFreeList : Integer;
- Reserved_8_11 : Cardinal;
- KeyLength : Word;
- IndexOption : Byte;
- IndexSignature : Byte;
- Reserved_Null : TFoxReservedNull;
- SortOrder : Word;
- TotalExpressionLen : Word;
- ForExpressionLen : Word;
- Reserved_506_507 : Word;
- KeyExpressionLen : Word;
- KeyForExpression : TKeyForExpression;
- End;
- PFoxCDXHeader = ^TFoxCDXHeader;
- TFoxCDXNodeCommon = Record
- NodeAttributes : Word;
- NumberOfKeys : Word;
- PointerLeftNode : Integer;
- PointerRightNode : Integer;
- End;
- TFoxCDXNodeNonLeaf = Record
- NodeCommon : TFoxCDXNodeCommon;
- TempBlock : Array [12..511] of Byte;
- End;
- PFoxCDXNodeNonLeaf = ^TFoxCDXNodeNonLeaf;
- TFoxCDXNodeLeaf = Packed Record
- NodeCommon : TFoxCDXNodeCommon;
- BlockFreeSpace : Word;
- RecordNumberMask : Integer;
- DuplicateCountMask : Byte;
- TrailByteCountMask : Byte;
- RecNoBytes : Byte;
- DuplicateCountBytes : Byte;
- TrailByteCountBytes : Byte;
- HoldingByteCount : Byte;
- DataBlock : TDataBlock;
- End;
- PFoxCDXNodeLeaf = ^TFoxCDXNodeLeaf;
- *)
- end.
|