123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071 |
- unit dbf_idxfile;
- interface
- {$I Dbf_Common.inc}
- uses
- {$ifdef WIN32}
- Windows,
- {$else}
- {$ifdef KYLIX}
- Libc,
- {$endif}
- Types, Dbf_Wtil,
- {$endif}
- SysUtils,
- Classes,
- Db,
- Dbf_PgFile,
- {$ifdef USE_CACHE}
- Dbf_PgcFile,
- {$endif}
- Dbf_Parser,
- Dbf_PrsDef,
- Dbf_Cursor,
- Dbf_Common;
- {$ifdef _DEBUG}
- {$define TDBF_INDEX_CHECK}
- {$endif}
- {$ifdef _ASSERTS}
- {$define TDBF_INDEX_CHECK}
- {$endif}
- const
- MaxIndexes = 47;
- type
- TIndexPage = class;
- TIndexTag = class;
- TIndexUpdateMode = (umAll, umCurrent);
- TLocaleError = (leNone, leUnknown, leTableIndexMismatch, leNotAvailable);
- TLocaleSolution = (lsNotOpen, lsNoEdit, lsBinary);
- TIndexUniqueType = (iuNormal, iuUnique, iuDistinct);
- TIndexModifyMode = (mmNormal, mmDeleteRecall);
- TDbfLocaleErrorEvent = procedure(var Error: TLocaleError; var Solution: TLocaleSolution) of object;
- TDbfCompareKeyEvent = function(Key: PChar): Integer of object;
- TDbfCompareKeysEvent = function(Key1, Key2: PChar): Integer of object;
- PDouble = ^Double;
- PInteger = ^Integer;
- //===========================================================================
- TDbfIndexDef = class;
- TDbfIndexDef = class(TCollectionItem)
- protected
- FIndexName: string;
- FExpression: string;
- FOptions: TIndexOptions;
- FTemporary: Boolean; // added at runtime
- procedure SetIndexName(NewName: string);
- procedure SetExpression(NewField: string);
- public
- constructor Create(Collection: TCollection); override;
- destructor Destroy; override;
- procedure Assign(Source: TPersistent); override;
- property Temporary: Boolean read FTemporary write FTemporary;
- property Name: string read FIndexName write SetIndexName;
- property Expression: string read FExpression write SetExpression;
- published
- property IndexFile: string read FIndexName write SetIndexName;
- property SortField: string read FExpression write SetExpression;
- property Options: TIndexOptions read FOptions write FOptions;
- end;
- //===========================================================================
- TIndexFile = class;
- TIndexPageClass = class of TIndexPage;
- TIndexPage = class(TObject)
- protected
- FIndexFile: TIndexFile;
- FLowerPage: TIndexPage;
- FUpperPage: TIndexPage;
- FPageBuffer: Pointer;
- FEntry: Pointer;
- FEntryNo: Integer;
- FLockCount: Integer;
- FModified: Boolean;
- FPageNo: Integer;
- FWeight: Integer;
- // bracket props
- FLowBracket: Integer; // = FLowIndex if FPageNo = FLowPage
- FLowIndex: Integer;
- FLowPage: Integer;
- FHighBracket: Integer; // = FHighIndex if FPageNo = FHighPage
- FHighIndex: Integer;
- FHighPage: Integer;
- procedure LocalInsert(RecNo: Integer; Buffer: PChar; LowerPageNo: Integer);
- procedure LocalDelete;
- procedure Delete;
- procedure SyncLowerPage;
- procedure WritePage;
- procedure Split;
- procedure LockPage;
- procedure UnlockPage;
- function RecurPrev: Boolean;
- function RecurNext: Boolean;
- procedure RecurFirst;
- procedure RecurLast;
- procedure SetEntry(RecNo: Integer; key: PChar; LowerPageNo: Integer);
- procedure SetEntryNo(value: Integer);
- procedure SetPageNo(NewPageNo: Integer);
- procedure SetLowPage(NewPage: Integer);
- procedure SetHighPage(NewPage: Integer);
- procedure SetUpperPage(NewPage: TIndexPage);
- procedure UpdateBounds(IsInnerNode: Boolean);
- protected
- function GetEntry(AEntryNo: Integer): Pointer; virtual; abstract;
- function GetLowerPageNo: Integer; virtual; abstract;
- function GetKeyData: PChar; virtual; abstract;
- function GetNumEntries: Integer; virtual; abstract;
- function GetKeyDataFromEntry(AEntry: Integer): PChar; virtual; abstract;
- function GetRecNo: Integer; virtual; abstract;
- function GetIsInnerNode: Boolean; virtual; abstract;
- procedure IncNumEntries; virtual; abstract;
- procedure SetNumEntries(NewNum: Integer); virtual; abstract;
- procedure SetRecLowerPageNo(NewRecNo, NewPageNo: Integer); virtual; abstract;
- procedure SetRecLowerPageNoOfEntry(AEntry, NewRecNo, NewPageNo: Integer); virtual; abstract;
- {$ifdef TDBF_UPDATE_FIRST_LAST_NODE}
- procedure SetPrevBlock(NewBlock: Integer); virtual;
- {$endif}
- public
- constructor Create(Parent: TIndexFile);
- destructor Destroy; override;
- function FindNearest(ARecNo: Integer): Integer;
- function PhysicalRecNo: Integer;
- function MatchKey: Integer;
- procedure GotoInsertEntry;
- procedure Clear;
- procedure GetNewPage;
- procedure Modified;
- procedure RecalcWeight;
- procedure UpdateWeight;
- procedure Flush;
- property Key: PChar read GetKeyData;
- property Entry: Pointer read FEntry;
- property EntryNo: Integer read FEntryNo write SetEntryNo;
- property IndexFile: TIndexFile read FIndexFile;
- property UpperPage: TIndexPage read FUpperPage write SetUpperPage;
- property LowerPage: TIndexPage read FLowerPage;
- // property LowerPageNo: Integer read GetLowerPageNo; // never used
- property PageBuffer: Pointer read FPageBuffer;
- property PageNo: Integer read FPageNo write SetPageNo;
- property Weight: Integer read FWeight;
- property NumEntries: Integer read GetNumEntries;
- property HighBracket: Integer read FHighBracket write FHighBracket;
- property HighIndex: Integer read FHighIndex;
- property HighPage: Integer read FHighPage write SetHighPage;
- property LowBracket: Integer read FLowBracket write FLowBracket;
- property LowIndex: Integer read FLowIndex;
- property LowPage: Integer read FLowPage write SetLowPage;
- end;
- //===========================================================================
- TIndexTag = class(TObject)
- private
- FTag: Pointer;
- protected
- function GetHeaderPageNo: Integer; virtual; abstract;
- function GetTagName: string; virtual; abstract;
- function GetKeyFormat: Byte; virtual; abstract;
- function GetForwardTag1: Byte; virtual; abstract;
- function GetForwardTag2: Byte; virtual; abstract;
- function GetBackwardTag: Byte; virtual; abstract;
- function GetReserved: Byte; virtual; abstract;
- function GetKeyType: Char; virtual; abstract;
- procedure SetHeaderPageNo(NewPageNo: Integer); virtual; abstract;
- procedure SetTagName(NewName: string); virtual; abstract;
- procedure SetKeyFormat(NewFormat: Byte); virtual; abstract;
- procedure SetForwardTag1(NewTag: Byte); virtual; abstract;
- procedure SetForwardTag2(NewTag: Byte); virtual; abstract;
- procedure SetBackwardTag(NewTag: Byte); virtual; abstract;
- procedure SetReserved(NewReserved: Byte); virtual; abstract;
- procedure SetKeyType(NewType: Char); virtual; abstract;
- public
- property HeaderPageNo: Integer read GetHeaderPageNo write SetHeaderPageNo;
- property TagName: string read GetTagName write SetTagName;
- property KeyFormat: Byte read GetKeyFormat write SetKeyFormat;
- property ForwardTag1: Byte read GetForwardTag1 write SetForwardTag1;
- property ForwardTag2: Byte read GetForwardTag2 write SetForwardTag2;
- property BackwardTag: Byte read GetBackwardTag write SetBackwardTag;
- property Reserved: Byte read GetReserved write SetReserved;
- property KeyType: Char read GetKeyType write SetKeyType;
- property Tag: Pointer read FTag write FTag;
- end;
- //===========================================================================
- {$ifdef USE_CACHE}
- TIndexFile = class(TCachedFile)
- {$else}
- TIndexFile = class(TPagedFile)
- {$endif}
- protected
- FIndexName: string;
- FParsers: array[0..MaxIndexes-1] of TDbfParser;
- FIndexHeaders: array[0..MaxIndexes-1] of Pointer;
- FHeaderModified: array[0..MaxIndexes-1] of Boolean;
- FIndexHeader: Pointer;
- FIndexVersion: TXBaseVersion;
- FRoots: array[0..MaxIndexes-1] of TIndexPage;
- FLeaves: array[0..MaxIndexes-1] of TIndexPage;
- FCurrentParser: TDbfParser;
- FRoot: TIndexPage;
- FLeaf: TIndexPage;
- FMdxTag: TIndexTag;
- FTempMdxTag: TIndexTag;
- FEntryHeaderSize: Integer;
- FPageHeaderSize: Integer;
- FTagSize: Integer;
- FTagOffset: Integer;
- FHeaderPageNo: Integer;
- FSelectedIndex: Integer;
- FIsDescending: Boolean;
- FUniqueMode: TIndexUniqueType;
- FModifyMode: TIndexModifyMode;
- FHeaderLocked: Integer; // used to remember which header page we have locked
- FKeyBuffer: array[0..100] of Char;
- FLowBuffer: array[0..100] of Char;
- FHighBuffer: array[0..100] of Char;
- FEntryBof: Pointer;
- FEntryEof: Pointer;
- FDbfFile: Pointer;
- FCanEdit: Boolean;
- FOpened: Boolean;
- FRangeActive: Boolean;
- FUpdateMode: TIndexUpdateMode;
- FUserKey: PChar; // find / insert key
- FUserRecNo: Integer; // find / insert recno
- FUserBCD: array[0..10] of Byte;
- FUserNumeric: Double;
- FForceClose: Boolean;
- FForceReadOnly: Boolean;
- FLocaleID: LCID;
- FLocaleCP: Integer;
- FCodePage: Integer;
- FCompareKey: TDbfCompareKeyEvent;
- FCompareKeys: TDbfCompareKeysEvent;
- FOnLocaleError: TDbfLocaleErrorEvent;
- function GetNewPageNo: Integer;
- procedure TouchHeader(AHeader: Pointer);
- function CreateTempFile(BaseName: string): TPagedFile;
- procedure WriteIndexHeader(AIndex: Integer);
- procedure SelectIndexVars(AIndex: Integer);
- procedure CalcKeyProperties;
- procedure UpdateIndexProperties;
- procedure ClearRoots;
- function CalcTagOffset(AIndex: Integer): Pointer;
- function FindKey(Insert: boolean): Integer;
- procedure InsertKey(Buffer: PChar);
- procedure DeleteKey(Buffer: PChar);
- procedure InsertCurrent;
- procedure DeleteCurrent;
- procedure UpdateCurrent(PrevBuffer, NewBuffer: PChar);
- procedure ReadIndexes;
- procedure Resync(Relative: boolean);
- procedure ResyncRoot;
- procedure ResyncTree;
- procedure ResyncRange(KeepPosition: boolean);
- procedure ResetRange;
- procedure SetBracketLow;
- procedure SetBracketHigh;
- procedure WalkFirst;
- procedure WalkLast;
- function WalkPrev: boolean;
- function WalkNext: boolean;
-
- procedure TranslateToANSI(Src, Dest: PChar);
- function CompareKeyNumericNDX(Key: PChar): Integer;
- function CompareKeyNumericMDX(Key: PChar): Integer;
- function CompareKeyString(Key: PChar): Integer;
- function CompareKeysNumericNDX(Key1, Key2: PChar): Integer;
- function CompareKeysNumericMDX(Key1, Key2: PChar): Integer;
- function CompareKeysString(Key1, Key2: PChar): Integer;
- // property functions
- function GetName: string;
- function GetDbfLanguageId: Byte;
- function GetKeyLen: Integer;
- function GetKeyType: Char;
- // function GetIndexCount Integer;
- function GetExpression: string;
- function GetPhysicalRecNo: Integer;
- function GetSequentialRecNo: Integer;
- function GetSequentialRecordCount: Integer;
- procedure SetSequentialRecNo(RecNo: Integer);
- procedure SetPhysicalRecNo(RecNo: Integer);
- procedure SetUpdateMode(NewMode: TIndexUpdateMode);
- procedure SetIndexName(const AIndexName: string);
- procedure SetLocaleID(const NewID: LCID);
- property InternalLocaleID: LCID read FLocaleID write SetLocaleID;
- public
- constructor Create(ADbfFile: Pointer);
- destructor Destroy; override;
- procedure Open;
- procedure Close;
- procedure Clear;
- procedure Flush; override;
- procedure ClearIndex;
- procedure AddNewLevel;
- procedure UnlockHeader;
- procedure InsertError;
- procedure Insert(RecNo: Integer; Buffer: PChar);
- procedure Update(RecNo: Integer; PrevBuffer, NewBuffer: PChar);
- procedure Delete(RecNo: Integer; Buffer: PChar);
- function CheckKeyViolation(Buffer: PChar): Boolean;
- procedure RecordDeleted(RecNo: Integer; Buffer: PChar);
- procedure RecordRecalled(RecNo: Integer; Buffer: PChar);
- procedure DeleteIndex(const AIndexName: string);
- procedure RepageFile;
- procedure CompactFile;
- procedure CreateIndex(FieldDesc, TagName: string; Options: TIndexOptions);
- function ExtractKeyFromBuffer(Buffer: PChar): PChar;
- function SearchKey(Key: PChar; SearchType: TSearchKeyType): Boolean;
- function Find(RecNo: Integer; Buffer: PChar): Integer;
- function IndexOf(const AIndexName: string): Integer;
- procedure GetIndexNames(const AList: TStrings);
- procedure GetIndexInfo(const AIndexName: string; IndexDef: TDbfIndexDef);
- procedure WriteHeader; override;
- procedure WriteFileHeader;
- procedure First;
- procedure Last;
- function Next: Boolean;
- function Prev: Boolean;
- procedure SetRange(LowRange, HighRange: PChar);
- procedure CancelRange;
- function MatchKey(UserKey: PChar): Integer;
- function CompareKey(Key: PChar): Integer;
- function CompareKeys(Key1, Key2: PChar): Integer;
- function PrepareKey(Buffer: PChar; ResultType: TExpressionType): PChar;
- property KeyLen: Integer read GetKeyLen;
- property IndexVersion: TXBaseVersion read FIndexVersion;
- property EntryHeaderSize: Integer read FEntryHeaderSize;
- property KeyType: Char read GetKeyType;
- property SequentialRecordCount: Integer read GetSequentialRecordCount;
- property SequentialRecNo: Integer read GetSequentialRecNo write SetSequentialRecNo;
- property PhysicalRecNo: Integer read GetPhysicalRecNo write SetPhysicalRecNo;
- property HeaderPageNo: Integer read FHeaderPageNo;
- property IndexHeader: Pointer read FIndexHeader;
- property EntryBof: Pointer read FEntryBof;
- property EntryEof: Pointer read FEntryEof;
- property UniqueMode: TIndexUniqueType read FUniqueMode;
- property IsDescending: Boolean read FIsDescending;
- property UpdateMode: TIndexUpdateMode read FUpdateMode write SetUpdateMode;
- property IndexName: string read FIndexName write SetIndexName;
- property Expression: string read GetExpression;
- // property Count: Integer read GetIndexCount;
- property ForceClose: Boolean read FForceClose;
- property ForceReadOnly: Boolean read FForceReadOnly;
- property LocaleID: LCID read FLocaleID;
- property CodePage: Integer read FCodePage write FCodePage;
- property OnLocaleError: TDbfLocaleErrorEvent read FOnLocaleError write FOnLocaleError;
- end;
- //------------------------------------------------------------------------------
- implementation
- uses
- Dbf_DbfFile,
- Dbf_Fields,
- Dbf_Str,
- Dbf_Lang;
- const
- RecBOF = 0;
- RecEOF = MaxInt;
- lcidBinary = $0A03;
- KeyFormat_Expression = $00;
- KeyFormat_Data = $10;
- KeyFormat_Descending = $08;
- KeyFormat_String = $10;
- KeyFormat_Distinct = $20;
- KeyFormat_Unique = $40;
- Unique_None = $00;
- Unique_Unique = $01;
- Unique_Distinct = $21;
- type
- TLCIDList = class(TList)
- public
- constructor Create;
- procedure Enumerate;
- end;
- PMdxHdr = ^rMdxHdr;
- rMdxHdr = record
- MdxVersion : Byte; // 0
- Year : Byte; // 1
- Month : Byte; // 2
- Day : Byte; // 3
- FileName : array[0..15] of Char; // 4..19
- BlockSize : Word; // 20..21
- BlockAdder : Word; // 22..23
- ProdFlag : Byte; // 24
- NumTags : Byte; // 25
- TagSize : Byte; // 26
- Dummy1 : Byte; // 27
- TagsUsed : Word; // 28..29
- Dummy2 : Byte; // 30
- Language : Byte; // 31
- NumPages : Integer; // 32..35
- FreePage : Integer; // 36..39
- BlockFree : Integer; // 40..43
- UpdYear : Byte; // 44
- UpdMonth : Byte; // 45
- UpdDay : Byte; // 46
- Reserved : array[0..481] of Byte; // 47..528
- TagFlag : Byte; // 529 // dunno what this means but it ought to be 1 :-)
- end;
- // Tags -> I don't know what to with them
- // KeyType -> Variable position, db7 different from db4
- PMdx4Tag = ^rMdx4Tag;
- rMdx4Tag = record
- HeaderPageNo : Integer; // 0..3
- TagName : array [0..10] of Char; // 4..14 of Byte
- KeyFormat : Byte; // 15 00h: Calculated
- // 10h: Data Field
- ForwardTag1 : Byte; // 16
- ForwardTag2 : Byte; // 17
- BackwardTag : Byte; // 18
- Reserved : Byte; // 19
- KeyType : Char; // 20 C : Character
- // N : Numerical
- // D : Date
- end;
- PMdx7Tag = ^rMdx7Tag;
- rMdx7Tag = record
- HeaderPageNo : Integer; // 0..3
- TagName : array [0..32] of Char; // 4..36 of Byte
- KeyFormat : Byte; // 37 00h: Calculated
- // 10h: Data Field
- ForwardTag1 : Byte; // 38
- ForwardTag2 : Byte; // 39
- BackwardTag : Byte; // 40
- Reserved : Byte; // 41
- KeyType : Char; // 42 C : Character
- // N : Numerical
- // D : Date
- end;
- PIndexHdr = ^rIndexHdr;
- rIndexHdr = record
- RootPage : Integer; // 0..3
- NumPages : Integer; // 4..7
- KeyFormat : Byte; // 8 00h: Right, Left, DTOC
- // 08h: Descending order
- // 10h: String
- // 20h: Distinct
- // 40h: Unique
- KeyType : Char; // 9 C : Character
- // N : Numerical
- // D : Date
- Dummy : Word; // 10..11
- KeyLen : Word; // 12..13
- NumKeys : Word; // 14..15
- sKeyType : Word; // 16..17 00h: DB4: C/N; DB3: C
- // 01h: DB4: D ; DB3: N/D
- KeyRecLen : Word; // 18..19 Length of key entry in page
- Version : Word; // 20..21
- Dummy2 : Byte; // 22
- Unique : Byte; // 23
- KeyDesc : array [0..219] of Char; // 24..243
- Dummy3 : Byte; // 244
- ForExist : Byte; // 245
- KeyExist : Byte; // 246
- FirstNode : Longint; // 248..251 first node that contains data
- LastNode : Longint; // 252..255 last node that contains data
- // MDX Header has here a 506 byte block reserved
- // and then the FILTER expression, which obviously doesn't
- // fit in a NDX page, so we'll skip it
- end;
- PMdxEntry = ^rMdxEntry;
- rMdxEntry = record
- RecBlockNo: Longint; // 0..3 either recno or blockno
- KeyData : Char; // 4.. first byte of data, context => length
- end;
- PMdxPage = ^rMdxPage;
- rMdxPage = record
- NumEntries : Integer;
- PrevBlock : Integer;
- FirstEntry : rMdxEntry;
- end;
- PNdxEntry = ^rNdxEntry;
- rNdxEntry = record
- LowerPageNo: Integer; // 0..3 lower page
- RecNo : Integer; // 4..7 recno
- KeyData : Char;
- end;
- PNdxPage = ^rNdxPage;
- rNdxPage = record
- NumEntries: Integer; // 0..3
- FirstEntry: rNdxEntry;
- end;
- //---------------------------------------------------------------------------
- TMdxPage = class(TIndexPage)
- protected
- function GetEntry(AEntryNo: Integer): Pointer; override;
- function GetLowerPageNo: Integer; override;
- function GetKeyData: PChar; override;
- function GetNumEntries: Integer; override;
- function GetKeyDataFromEntry(AEntry: Integer): PChar; override;
- function GetRecNo: Integer; override;
- function GetIsInnerNode: Boolean; override;
- procedure IncNumEntries; override;
- procedure SetNumEntries(NewNum: Integer); override;
- procedure SetRecLowerPageNo(NewRecNo, NewPageNo: Integer); override;
- procedure SetRecLowerPageNoOfEntry(AEntry, NewRecNo, NewPageNo: Integer); override;
- {$ifdef TDBF_UPDATE_FIRST_LAST_NODE}
- procedure SetPrevBlock(NewBlock: Integer); override;
- {$endif}
- end;
- //---------------------------------------------------------------------------
- TNdxPage = class(TIndexPage)
- protected
- function GetEntry(AEntryNo: Integer): Pointer; override;
- function GetLowerPageNo: Integer; override;
- function GetKeyData: PChar; override;
- function GetNumEntries: Integer; override;
- function GetKeyDataFromEntry(AEntry: Integer): PChar; override;
- function GetRecNo: Integer; override;
- function GetIsInnerNode: Boolean; override;
- procedure IncNumEntries; override;
- procedure SetNumEntries(NewNum: Integer); override;
- procedure SetRecLowerPageNo(NewRecNo, NewPageNo: Integer); override;
- procedure SetRecLowerPageNoOfEntry(AEntry, NewRecNo, NewPageNo: Integer); override;
- end;
- //---------------------------------------------------------------------------
- TMdx4Tag = class(TIndexTag)
- protected
- function GetHeaderPageNo: Integer; override;
- function GetTagName: string; override;
- function GetKeyFormat: Byte; override;
- function GetForwardTag1: Byte; override;
- function GetForwardTag2: Byte; override;
- function GetBackwardTag: Byte; override;
- function GetReserved: Byte; override;
- function GetKeyType: Char; override;
- procedure SetHeaderPageNo(NewPageNo: Integer); override;
- procedure SetTagName(NewName: string); override;
- procedure SetKeyFormat(NewFormat: Byte); override;
- procedure SetForwardTag1(NewTag: Byte); override;
- procedure SetForwardTag2(NewTag: Byte); override;
- procedure SetBackwardTag(NewTag: Byte); override;
- procedure SetReserved(NewReserved: Byte); override;
- procedure SetKeyType(NewType: Char); override;
- end;
- //---------------------------------------------------------------------------
- TMdx7Tag = class(TIndexTag)
- function GetHeaderPageNo: Integer; override;
- function GetTagName: string; override;
- function GetKeyFormat: Byte; override;
- function GetForwardTag1: Byte; override;
- function GetForwardTag2: Byte; override;
- function GetBackwardTag: Byte; override;
- function GetReserved: Byte; override;
- function GetKeyType: Char; override;
- procedure SetHeaderPageNo(NewPageNo: Integer); override;
- procedure SetTagName(NewName: string); override;
- procedure SetKeyFormat(NewFormat: Byte); override;
- procedure SetForwardTag1(NewTag: Byte); override;
- procedure SetForwardTag2(NewTag: Byte); override;
- procedure SetBackwardTag(NewTag: Byte); override;
- procedure SetReserved(NewReserved: Byte); override;
- procedure SetKeyType(NewType: Char); override;
- end;
- var
- Entry_Mdx_BOF: rMdxEntry; //(RecBOF, #0);
- Entry_Mdx_EOF: rMdxEntry; //(RecBOF, #0);
- Entry_Ndx_BOF: rNdxEntry; //(0, RecBOF, #0);
- Entry_Ndx_EOF: rNdxEntry; //(0, RecEOF, #0);
- LCIDList: TLCIDList;
- //==========================================================
- // Locale support for all versions of Delphi/C++Builder
- function LocaleCallBack(LocaleString: PChar): Integer; stdcall;
- begin
- LCIDList.Add(Pointer(StrToInt('$'+LocaleString)));
- Result := 1;
- end;
- constructor TLCIDList.Create;
- begin
- inherited;
- end;
- procedure TLCIDList.Enumerate;
- begin
- Clear;
- EnumSystemLocales(@LocaleCallBack, LCID_SUPPORTED);
- end;
- //==========================================================
- //============ TIndexPage
- //==========================================================
- constructor TIndexPage.Create(Parent: TIndexFile);
- begin
- FIndexFile := Parent;
- GetMem(FPageBuffer, FIndexFile.RecordSize);
- FLowerPage := nil;
- Clear;
- end;
- destructor TIndexPage.Destroy;
- begin
- // no locks anymore?
- assert(FLockCount = 0);
- if (FLowerPage<>nil) then
- LowerPage.Free;
- WritePage;
- FreeMemAndNil(FPageBuffer);
- inherited Destroy;
- end;
- procedure TIndexPage.Clear;
- begin
- FillChar(PChar(FPageBuffer)^, FIndexFile.RecordSize, 0);
- FreeAndNil(FLowerPage);
- FUpperPage := nil;
- FPageNo := -1;
- FEntryNo := -1;
- FWeight := 1;
- FModified := false;
- FEntry := FIndexFile.EntryBof;
- FLowPage := 0;
- FHighPage := 0;
- FLowIndex := 0;
- FHighIndex := -1;
- FLockCount := 0;
- end;
- procedure TIndexPage.GetNewPage;
- begin
- FPageNo := FIndexFile.GetNewPageNo;
- end;
- procedure TIndexPage.Modified;
- begin
- FModified := true;
- end;
- procedure TIndexPage.LockPage;
- begin
- // already locked?
- if FLockCount = 0 then
- FIndexFile.LockPage(FPageNo, true);
- // increase count
- inc(FLockCount);
- end;
- procedure TIndexPage.UnlockPage;
- begin
- // still in domain?
- assert(FLockCount > 0);
- dec(FLockCount);
- // unlock?
- if FLockCount = 0 then
- begin
- if FIndexFile.NeedLocks then
- WritePage;
- FIndexFile.UnlockPage(FPageNo);
- end;
- end;
- procedure TIndexPage.LocalInsert(RecNo: Integer; Buffer: PChar; LowerPageNo: Integer);
- // *) assumes there is at least one entry free
- var
- source, dest: Pointer;
- size, numEntries, numKeysAvail: Integer;
- begin
- // lock page if needed; wait if not available, anyone else updating?
- LockPage;
- // check assertions
- numEntries := GetNumEntries;
- // if this is inner node, we can only store one less than max entries
- numKeysAvail := PIndexHdr(FIndexFile.IndexHeader).NumKeys - numEntries;
- if FLowerPage <> nil then
- dec(numKeysAvail);
- // check if free space
- assert(numKeysAvail > 0);
- // first free up some space
- source := FEntry;
- dest := GetEntry(FEntryNo + 1);
- size := (numEntries - EntryNo) * PIndexHdr(FIndexFile.IndexHeader).KeyRecLen;
- // if 'rightmost' entry, copy pageno too
- if (FLowerPage <> nil) or (numKeysAvail > 1) then
- size := size + FIndexFile.EntryHeaderSize;
- Move(source^, dest^, size);
- // one entry added
- Inc(FHighIndex);
- IncNumEntries;
- // numEntries not valid from here
- SetEntry(RecNo, Buffer, LowerPageNo);
- // done!
- UnlockPage;
- end;
- procedure TIndexPage.LocalDelete;
- function IsOnlyEntry(Page: TIndexPage): boolean;
- begin
- Result := true;
- repeat
- if Page.HighIndex > 0 then
- Result := false;
- Page := Page.UpperPage;
- until not Result or (Page = nil);
- end;
- var
- source, dest: Pointer;
- size, numEntries: Integer;
- begin
- // get num entries
- numEntries := GetNumEntries;
- // is this last entry? if it's not move entries after current one
- if EntryNo < FHighIndex then
- begin
- source := GetEntry(EntryNo + 1);
- dest := FEntry;
- size := (FHighIndex - EntryNo) * PIndexHdr(FIndexFile.IndexHeader).KeyRecLen;
- Move(source^, dest^, size);
- end else
- // no need to update when we're about to remove the only entry
- if (UpperPage <> nil) and (FHighIndex > FLowIndex) then
- begin
- // we are about to remove the last on this page, so update search
- // key data of parent
- EntryNo := FHighIndex - 1;
- UpperPage.SetEntry(0, GetKeyData, FPageNo);
- end;
- // one entry less now
- dec(numEntries);
- dec(FHighIndex);
- SetNumEntries(numEntries);
- // zero last one out to not get confused about internal or leaf pages
- // note: need to decrease numEntries and HighIndex first, otherwise
- // check on page key consistency will fail
- SetRecLowerPageNoOfEntry(FHighIndex+1, 0, 0);
- // update bracket indexes
- if FHighPage = FPageNo then
- dec(FHighBracket);
- // check if range violated
- if EntryNo > FHighIndex then
- EntryNo := FHighIndex;
- // check if still entries left, otherwise remove page from parent
- if FHighIndex = -1 then
- begin
- if UpperPage <> nil then
- if not IsOnlyEntry(UpperPage) then
- UpperPage.LocalDelete;
- end;
- // go to valid record in lowerpage
- if FLowerPage <> nil then
- SyncLowerPage;
- // flag modified page
- FModified := true;
- // success!
- end;
- function TIndexPage.MatchKey: Integer;
- // assumes Buffer <> nil
- var
- keyData: PChar;
- begin
- // get key data
- keyData := GetKeyData;
- // use locale dependant compare
- Result := FIndexFile.CompareKey(keyData);
- end;
- function TIndexPage.FindNearest(ARecNo: Integer): Integer;
- // pre:
- // assumes Key <> nil
- // assumes FLowIndex <= FHighIndex + 1
- // ARecNo = -2 -> search first key matching Key
- // ARecNo = -3 -> search first key greater than Key
- // ARecNo > 0 -> search key matching Key and its recno = ARecNo
- // post:
- // Result < 0 -> key,recno smaller than current entry
- // Result = 0 -> key,recno found, FEntryNo = found key entryno
- // Result > 0 -> key,recno larger than current entry
- var
- low, high, current: Integer;
- begin
- // implement binary search, keys are sorted
- low := FLowIndex;
- high := GetNumEntries;
- // always true: Entry(FEntryNo) = FEntry
- // FHighIndex >= 0 because no-entry cases in leaves have been filtered out
- // entry HighIndex may not be bigger than rest (in inner node)
- // ARecNo = -3 -> search last recno matching key
- // need to have: low <= high
- // define low - 1 = neg.inf.
- // define high = pos.inf
- // inv1: (ARecNo<>-3) -> Entry(low-1).Key < Key <= Entry(high).Key
- // inv2: (ARecNo =-3) -> Entry(low-1).Key <= Key < Entry(high).Key
- // vf: high + 1 - low
- while low < high do
- begin
- current := (low + high) div 2;
- FEntry := GetEntry(current);
- // calc diff
- Result := MatchKey;
- // test if we need to go lower or higher
- // result < 0 implies key smaller than tested entry
- // result = 0 implies key equal to tested entry
- // result > 0 implies key greater than tested entry
- if (Result < 0) or ((ARecNo<>-3) and (Result=0)) then
- high := current
- else
- low := current+1;
- end;
- // high will contain first greater-or-equal key
- // ARecNo <> -3 -> Entry(high).Key will contain first key that matches -> go to high
- // ARecNo = -3 -> Entry(high).Key will contain first key that is greater -> go to high
- FEntryNo := -1;
- EntryNo := high;
- // calc end result: can't inspect high if lowerpage <> nil
- // if this is a leaf, we need to find specific recno
- if (LowerPage = nil) then
- begin
- if high > FHighIndex then
- begin
- Result := 1;
- end else begin
- Result := MatchKey;
- // test if we need to find a specific recno
- // result < 0 -> current key greater -> nothing found -> don't search
- if (ARecNo > 0) then
- begin
- // BLS to RecNo
- high := FHighIndex + 1;
- low := FEntryNo;
- // inv: FLowIndex <= FEntryNo <= high <= FHighIndex + 1 /\
- // (Ai: FLowIndex <= i < FEntryNo: Entry(i).RecNo <> ARecNo)
- while FEntryNo <> high do
- begin
- // FEntryNo < high, get new entry
- if low <> FEntryNo then
- begin
- FEntry := GetEntry(FEntryNo);
- // check if entry key still ok
- Result := MatchKey;
- end;
- // test if out of range or found recno
- if (Result <> 0) or (GetRecNo = ARecNo) then
- high := FEntryNo
- else begin
- // default to EOF
- inc(FEntryNo);
- Result := 1;
- end;
- end;
- end;
- end;
- end else begin
- // FLowerPage <> nil -> high contains entry, can not have empty range
- Result := 0;
- end;
- end;
- procedure TIndexPage.GotoInsertEntry;
- // assures we really can insert here
- begin
- if FEntry = FIndexFile.EntryEof then
- FEntry := GetEntry(FEntryNo);
- end;
- procedure TIndexPage.SetEntry(RecNo: Integer; Key: PChar; LowerPageNo: Integer);
- var
- keyData: PChar;
- {$ifdef TDBF_INDEX_CHECK}
- prevKeyData, curKeyData, nextKeyData: PChar;
- {$endif}
- begin
- // get num entries
- keyData := GetKeyData;
- // check valid entryno: we should be able to insert entries!
- assert((EntryNo >= 0) and (EntryNo <= FHighIndex));
- if (UpperPage <> nil) and (FEntryNo = FHighIndex) then
- UpperPage.SetEntry(0, Key, FPageNo);
- { if PIndexHdr(FIndexFile.IndexHeader).KeyType = 'C' then }
- if Key <> nil then
- Move(Key^, keyData^, PIndexHdr(FIndexFile.IndexHeader).KeyLen)
- else
- PChar(keyData)^ := #0;
- {
- else
- if Key <> nil then
- PDouble(keyData)^ := PDouble(Key)^
- else
- PDouble(keyData)^ := 0.0;
- }
- // set entry info
- SetRecLowerPageNo(RecNo, LowerPageNo);
- // flag we modified the page
- FModified := true;
- {$ifdef TDBF_INDEX_CHECK}
- // check sorted entry sequence
- prevKeyData := GetKeyDataFromEntry(FEntryNo-1);
- curKeyData := GetKeyDataFromEntry(FEntryNo+0);
- nextKeyData := GetKeyDataFromEntry(FEntryNo+1);
- // check if prior entry not greater, 'rightmost' key does not have to match
- if (FEntryNo > 0) and ((FLowerPage = nil) or (FEntryNo < FHighIndex)) then
- begin
- if FIndexFile.CompareKeys(prevKeyData, curKeyData) > 0 then
- assert(false);
- end;
- // check if next entry not smaller
- if ((FLowerPage = nil) and (FEntryNo < FHighIndex)) or
- ((FLowerPage <> nil) and (FEntryNo < (FHighIndex - 1))) then
- begin
- if FIndexFile.CompareKeys(curKeyData, nextKeyData) > 0 then
- assert(false);
- end;
- {$endif}
- end;
- {$ifdef TDBF_UPDATE_FIRST_LAST_NODE}
- procedure TIndexPage.SetPrevBlock(NewBlock: Integer);
- begin
- end;
- {$endif}
- procedure TIndexPage.Split;
- // *) assumes this page is `nearly' full
- var
- NewPage: TIndexPage;
- source, dest: Pointer;
- paKeyData: PChar;
- size, oldEntryNo: Integer;
- splitRight, numEntries, numEntriesNew: Integer;
- saveLow, saveHigh: Integer;
- newRoot: Boolean;
- begin
- // assure parent exists, if not -> create & lock, else lock it
- newRoot := FUpperPage = nil;
- if newRoot then
- FIndexFile.AddNewLevel
- else
- FUpperPage.LockPage;
- // lock this page for updates
- LockPage;
- // get num entries
- numEntries := GetNumEntries;
- // calc split pos: split in half
- splitRight := numEntries div 2;
- if (FLowerPage <> nil) and (numEntries mod 2 = 1) then
- inc(splitRight);
- numEntriesNew := numEntries - splitRight;
- // check if place to insert has least entries
- if (numEntriesNew > splitRight) and (EntryNo > splitRight) then
- begin
- inc(splitRight);
- dec(numEntriesNew);
- end else if (numEntriesNew < splitRight) and (EntryNo < splitRight) then
- begin
- dec(splitRight);
- inc(numEntriesNew);
- end;
- // save current entryno
- oldEntryNo := EntryNo;
- // check if we need to save high / low bound
- if FLowPage = FPageNo then
- saveLow := FLowIndex
- else
- saveLow := -1;
- if FHighPage = FPageNo then
- saveHigh := FHighIndex
- else
- saveHigh := -1;
- // create new page
- NewPage := TIndexPageClass(ClassType).Create(FIndexFile);
- try
- // get page
- NewPage.GetNewPage;
- {$ifdef TDBF_UPDATE_FIRSTLAST_NODE}
- NewPage.SetPrevBlock(NewPage.PageNo - FIndexFile.PagesPerRecord);
- {$endif}
- // set modified
- FModified := true;
- NewPage.FModified := true;
- // compute source, dest
- dest := NewPage.GetEntry(0);
- source := GetEntry(splitRight);
- size := numEntriesNew * PIndexHdr(FIndexFile.IndexHeader).KeyRecLen;
- // if inner node, copy rightmost entry too
- if FLowerPage <> nil then
- size := size + FIndexFile.EntryHeaderSize;
- // copy bytes
- Move(source^, dest^, size);
- // if not inner node, clear possible 'rightmost' entry
- if (FLowerPage = nil) then
- SetRecLowerPageNoOfEntry(splitRight, 0, 0);
- // calc new number of entries of this page
- numEntries := numEntries - numEntriesNew;
- // if lower level, then we need adjust for new 'rightmost' node
- if FLowerPage <> nil then
- begin
- // right split, so we need 'new' rightmost node
- dec(numEntries);
- end;
- // store new number of nodes
- // new page is right page, so update parent to point to new right page
- NewPage.SetNumEntries(numEntriesNew);
- SetNumEntries(numEntries);
- // update highindex
- FHighIndex := numEntries;
- if FLowerPage = nil then
- dec(FHighIndex);
- // get data of last entry on this page
- paKeyData := GetKeyDataFromEntry(splitRight - 1);
- // reinsert ourself into parent
- // FUpperPage.RecurInsert(0, paKeyData, FPageNo);
- // we can do this via a localinsert now: we know there is at least one entry
- // free in this page and higher up
- FUpperPage.LocalInsert(0, paKeyData, FPageNo);
- // new page is right page, so update parent to point to new right page
- // we can't do this earlier: we will get lost in tree!
- FUpperPage.SetRecLowerPageNoOfEntry(FUpperPage.EntryNo+1, 0, NewPage.PageNo);
- // NOTE: UpperPage.LowerPage = Self <= inserted FPageNo, not NewPage.PageNo
- finally
- NewPage.Free;
- end;
- // done updating: unlock page
- UnlockPage;
- // save changes to parent
- FUpperPage.UnlockPage;
- // unlock new root, unlock header too
- FIndexFile.UnlockHeader;
- // go to entry we left on
- if oldEntryNo >= splitRight then
- begin
- // sync upperpage with right page
- FUpperPage.EntryNo := FUpperPage.EntryNo + 1;
- FEntryNo := oldEntryNo - splitRight;
- FEntry := GetEntry(FEntryNo);
- end else begin
- // in left page = this page
- EntryNo := oldEntryNo;
- end;
- // check if we have to save high / low bound
- // seen the fact that FHighPage = FPageNo -> EntryNo <= FHighIndex, it can in
- // theory not happen that page is advanced to right page and high bound remains
- // on left page, but we won't check for that here
- if saveLow >= splitRight then
- begin
- FLowPage := FPageNo;
- FLowIndex := saveLow - splitRight;
- end;
- if saveHigh >= splitRight then
- begin
- FHighPage := FPageNo;
- FHighIndex := saveHigh - splitRight;
- end;
- end;
- procedure TIndexPage.Delete;
- begin
- LocalDelete;
- end;
- procedure TIndexPage.WritePage;
- begin
- // check if we modified current page
- if FModified and (FPageNo > 0) then
- begin
- FIndexFile.WriteRecord(FPageNo, FPageBuffer);
- FModified := false;
- end;
- end;
- procedure TIndexPage.Flush;
- begin
- WritePage;
- if FLowerPage <> nil then
- FLowerPage.Flush;
- end;
- procedure TIndexPage.RecalcWeight;
- begin
- if FLowerPage <> nil then
- begin
- FWeight := FLowerPage.Weight * PIndexHdr(FIndexFile.IndexHeader).NumKeys;
- end else begin
- FWeight := 1;
- end;
- if FUpperPage <> nil then
- FUpperPage.RecalcWeight;
- end;
- procedure TIndexPage.UpdateWeight;
- begin
- if FLowerPage <> nil then
- FLowerPage.UpdateWeight
- else
- RecalcWeight;
- end;
- procedure TIndexPage.SetUpperPage(NewPage: TIndexPage);
- begin
- if FUpperPage <> NewPage then
- begin
- // root height changed: update weights
- FUpperPage := NewPage;
- UpdateWeight;
- end;
- end;
- procedure TIndexPage.SetLowPage(NewPage: Integer);
- begin
- if FLowPage <> NewPage then
- begin
- FLowPage := NewPage;
- UpdateBounds(FLowerPage <> nil);
- end;
- end;
- procedure TIndexPage.SetHighPage(NewPage: Integer);
- begin
- if FHighPage <> NewPage then
- begin
- FHighPage := NewPage;
- UpdateBounds(FLowerPage <> nil);
- end;
- end;
- procedure TIndexPage.UpdateBounds(IsInnerNode: Boolean);
- begin
- // update low / high index range
- if FPageNo = FLowPage then
- FLowIndex := FLowBracket
- else
- FLowIndex := 0;
- if FPageNo = FHighPage then
- FHighIndex := FHighBracket
- else begin
- FHighIndex := GetNumEntries;
- if not IsInnerNode then
- dec(FHighIndex);
- end;
- end;
- function TMdxPage.GetIsInnerNode: Boolean;
- begin
- Result := PMdxPage(FPageBuffer).NumEntries < PIndexHdr(FIndexFile.IndexHeader).NumKeys;
- // if there is still an entry after the last one, this has to be an inner node
- if Result then
- Result := PMdxEntry(GetEntry(PMdxPage(FPageBuffer).NumEntries)).RecBlockNo <> 0;
- end;
- function TNdxPage.GetIsInnerNode: Boolean;
- begin
- Result := PNdxEntry(GetEntry(0)).LowerPageNo <> 0;
- end;
- procedure TIndexPage.SetPageNo(NewPageNo: Integer);
- var
- isInnerNode: Boolean;
- begin
- if (NewPageNo <> FPageNo) or FIndexFile.NeedLocks then
- begin
- // save changes
- WritePage;
- // no locks
- assert(FLockCount = 0);
- // goto new page
- FPageNo := NewPageNo;
- // remind ourselves we need to load new entry when page loaded
- FEntryNo := -1;
- if (NewPageNo > 0) and (NewPageNo <= FIndexFile.RecordCount) then
- begin
- // read page from disk
- FIndexFile.ReadRecord(NewPageNo, FPageBuffer);
- // fixup descending tree
- isInnerNode := GetIsInnerNode;
- // update low / high index range
- UpdateBounds(isInnerNode);
- // read inner node if any
- if isInnerNode then
- begin
- if FLowerPage = nil then
- begin
- FLowerPage := TIndexPageClass(ClassType).Create(FIndexFile);
- FLowerPage.UpperPage := Self;
- end;
- // read first entry, don't do this sooner, not created lowerpage yet
- // don't recursively resync all lower pages
- {$ifdef TDBF_INDEX_CHECK}
- end else if FLowerPage <> nil then
- begin
- // FLowerPage.Free;
- // FLowerPage := nil;
- assert(false);
- {$endif}
- end else begin
- // we don't have to check autoresync here because we're already at lowest level
- EntryNo := FLowIndex;
- end;
- end;
- end;
- end;
- procedure TIndexPage.SyncLowerPage;
- // *) assumes FLowerPage <> nil!
- begin
- FLowerPage.PageNo := GetLowerPageNo;
- end;
- procedure TIndexPage.SetEntryNo(value: Integer);
- begin
- // do not bother if no change
- if value <> FEntryNo then
- begin
- // check if out of range
- if (value < FLowIndex) then
- begin
- if FLowerPage = nil then
- FEntryNo := FLowIndex - 1;
- FEntry := FIndexFile.EntryBof;
- end else if value > FHighIndex then begin
- FEntryNo := FHighIndex + 1;
- FEntry := FIndexFile.EntryEof;
- end else begin
- FEntryNo := value;
- FEntry := GetEntry(value);
- // sync lowerpage with entry
- if (FLowerPage <> nil) then
- SyncLowerPage;
- end;
- end;
- end;
- function TIndexPage.PhysicalRecNo: Integer;
- var
- entryRec: Integer;
- begin
- // get num entries
- entryRec := GetRecNo;
- // check if in range
- if (FEntryNo >= FLowIndex) and (FEntryNo <= FHighIndex) then
- Result := entryRec
- else
- Result := -1;
- end;
- function TIndexPage.RecurPrev: Boolean;
- begin
- EntryNo := EntryNo - 1;
- Result := Entry <> FIndexFile.EntryBof;
- if Result then
- begin
- if FLowerPage <> nil then
- begin
- FLowerPage.RecurLast;
- end;
- end else begin
- if FUpperPage<>nil then
- begin
- Result := FUpperPage.RecurPrev;
- end;
- end;
- end;
- function TIndexPage.RecurNext: Boolean;
- begin
- EntryNo := EntryNo + 1;
- Result := Entry <> FIndexFile.EntryEof;
- if Result then
- begin
- if FLowerPage <> nil then
- begin
- FLowerPage.RecurFirst;
- end;
- end else begin
- if FUpperPage<>nil then
- begin
- Result := FUpperPage.RecurNext;
- end;
- end;
- end;
- procedure TIndexPage.RecurFirst;
- begin
- EntryNo := FLowIndex;
- if (FLowerPage<>nil) then
- FLowerPage.RecurFirst;
- end;
- procedure TIndexPage.RecurLast;
- begin
- EntryNo := FHighIndex;
- if (FLowerPage<>nil) then
- FLowerPage.RecurLast;
- end;
- //==============================================================================
- //============ Mdx specific access routines
- //==============================================================================
- function TMdxPage.GetEntry(AEntryNo: Integer): Pointer;
- begin
- // get base + offset
- Result := PChar(@PMdxPage(PageBuffer).FirstEntry) + (PIndexHdr(IndexFile.IndexHeader).KeyRecLen * AEntryNo);
- end;
- function TMdxPage.GetLowerPageNo: Integer;
- // *) assumes LowerPage <> nil
- begin
- // if LowerPage = nil then
- // Result := 0
- // else
- Result := PMdxEntry(Entry).RecBlockNo;
- end;
- function TMdxPage.GetKeyData: PChar;
- begin
- Result := @PMdxEntry(Entry).KeyData;
- end;
- function TMdxPage.GetNumEntries: Integer;
- begin
- Result := PMdxPage(PageBuffer).NumEntries;
- end;
- function TMdxPage.GetKeyDataFromEntry(AEntry: Integer): PChar;
- begin
- Result := @PMdxEntry(GetEntry(AEntry)).KeyData;
- end;
- function TMdxPage.GetRecNo: Integer;
- begin
- Result := PMdxEntry(Entry).RecBlockNo;
- end;
- procedure TMdxPage.SetNumEntries(NewNum: Integer);
- begin
- PMdxPage(PageBuffer).NumEntries := NewNum;
- end;
- procedure TMdxPage.IncNumEntries;
- begin
- Inc(PMdxPage(PageBuffer).NumEntries);
- end;
- procedure TMdxPage.SetRecLowerPageNo(NewRecNo, NewPageNo: Integer);
- begin
- if FLowerPage = nil then
- PMdxEntry(Entry).RecBlockNo := NewRecNo
- else
- PMdxEntry(Entry).RecBlockNo := NewPageNo;
- end;
- procedure TMdxPage.SetRecLowerPageNoOfEntry(AEntry, NewRecNo, NewPageNo: Integer);
- begin
- if FLowerPage = nil then
- PMdxEntry(GetEntry(AEntry)).RecBlockNo := NewRecNo
- else
- PMdxEntry(GetEntry(AEntry)).RecBlockNo := NewPageNo;
- end;
- {$ifdef TDBF_UPDATE_FIRST_LAST_NODE}
- procedure TMdxPage.SetPrevBlock(NewBlock: Integer);
- begin
- PMdxPage(PageBuffer).PrevBlock := NewBlock;
- end;
- {$endif}
- //==============================================================================
- //============ Ndx specific access routines
- //==============================================================================
- function TNdxPage.GetEntry(AEntryNo: Integer): Pointer;
- begin
- // get base + offset
- Result := PChar(@PNdxPage(PageBuffer).FirstEntry) + (PIndexHdr(FIndexFile.IndexHeader).KeyRecLen * AEntryNo);
- end;
- function TNdxPage.GetLowerPageNo: Integer;
- // *) assumes LowerPage <> nil
- begin
- // if LowerPage = nil then
- // Result := 0
- // else
- Result := PNdxEntry(Entry).LowerPageNo
- end;
- function TNdxPage.GetRecNo: Integer;
- begin
- Result := PNdxEntry(Entry).RecNo;
- end;
- function TNdxPage.GetKeyData: PChar;
- begin
- Result := @PNdxEntry(Entry).KeyData;
- end;
- function TNdxPage.GetKeyDataFromEntry(AEntry: Integer): PChar;
- begin
- Result := @PNdxEntry(GetEntry(AEntry)).KeyData;
- end;
- function TNdxPage.GetNumEntries: Integer;
- begin
- Result := PNdxPage(PageBuffer).NumEntries;
- end;
- procedure TNdxPage.IncNumEntries;
- begin
- Inc(PNdxPage(PageBuffer).NumEntries);
- end;
- procedure TNdxPage.SetNumEntries(NewNum: Integer);
- begin
- PNdxPage(PageBuffer).NumEntries := NewNum;
- end;
- procedure TNdxPage.SetRecLowerPageNo(NewRecNo, NewPageNo: Integer);
- begin
- PNdxEntry(Entry).RecNo := NewRecNo;
- PNdxEntry(Entry).LowerPageNo := NewPageNo;
- end;
- procedure TNdxPage.SetRecLowerPageNoOfEntry(AEntry, NewRecNo, NewPageNo: Integer);
- begin
- PNdxEntry(GetEntry(AEntry)).RecNo := NewRecNo;
- PNdxEntry(GetEntry(AEntry)).LowerPageNo := NewPageNo;
- end;
- //==============================================================================
- //============ MDX version 4 header access routines
- //==============================================================================
- function TMdx4Tag.GetHeaderPageNo: Integer;
- begin
- Result := PMdx4Tag(Tag).HeaderPageNo;
- end;
- function TMdx4Tag.GetTagName: string;
- begin
- Result := PMdx4Tag(Tag).TagName;
- end;
- function TMdx4Tag.GetKeyFormat: Byte;
- begin
- Result := PMdx4Tag(Tag).KeyFormat;
- end;
- function TMdx4Tag.GetForwardTag1: Byte;
- begin
- Result := PMdx4Tag(Tag).ForwardTag1;
- end;
- function TMdx4Tag.GetForwardTag2: Byte;
- begin
- Result := PMdx4Tag(Tag).ForwardTag2;
- end;
- function TMdx4Tag.GetBackwardTag: Byte;
- begin
- Result := PMdx4Tag(Tag).BackwardTag;
- end;
- function TMdx4Tag.GetReserved: Byte;
- begin
- Result := PMdx4Tag(Tag).Reserved;
- end;
- function TMdx4Tag.GetKeyType: Char;
- begin
- Result := PMdx4Tag(Tag).KeyType;
- end;
- procedure TMdx4Tag.SetHeaderPageNo(NewPageNo: Integer);
- begin
- PMdx4Tag(Tag).HeaderPageNo := NewPageNo;
- end;
- procedure TMdx4Tag.SetTagName(NewName: string);
- begin
- StrPLCopy(PMdx4Tag(Tag).TagName, NewName, 10);
- PMdx4Tag(Tag).TagName[10] := #0;
- end;
- procedure TMdx4Tag.SetKeyFormat(NewFormat: Byte);
- begin
- PMdx4Tag(Tag).KeyFormat := NewFormat;
- end;
- procedure TMdx4Tag.SetForwardTag1(NewTag: Byte);
- begin
- PMdx4Tag(Tag).ForwardTag1 := NewTag;
- end;
- procedure TMdx4Tag.SetForwardTag2(NewTag: Byte);
- begin
- PMdx4Tag(Tag).ForwardTag2 := NewTag;
- end;
- procedure TMdx4Tag.SetBackwardTag(NewTag: Byte);
- begin
- PMdx4Tag(Tag).BackwardTag := NewTag;
- end;
- procedure TMdx4Tag.SetReserved(NewReserved: Byte);
- begin
- PMdx4Tag(Tag).Reserved := NewReserved;
- end;
- procedure TMdx4Tag.SetKeyType(NewType: Char);
- begin
- PMdx4Tag(Tag).KeyType := NewType;
- end;
- //==============================================================================
- //============ MDX version 7 headertag access routines
- //==============================================================================
- function TMdx7Tag.GetHeaderPageNo: Integer;
- begin
- Result := PMdx7Tag(Tag).HeaderPageNo;
- end;
- function TMdx7Tag.GetTagName: string;
- begin
- Result := PMdx7Tag(Tag).TagName;
- end;
- function TMdx7Tag.GetKeyFormat: Byte;
- begin
- Result := PMdx7Tag(Tag).KeyFormat;
- end;
- function TMdx7Tag.GetForwardTag1: Byte;
- begin
- Result := PMdx7Tag(Tag).ForwardTag1;
- end;
- function TMdx7Tag.GetForwardTag2: Byte;
- begin
- Result := PMdx7Tag(Tag).ForwardTag2;
- end;
- function TMdx7Tag.GetBackwardTag: Byte;
- begin
- Result := PMdx7Tag(Tag).BackwardTag;
- end;
- function TMdx7Tag.GetReserved: Byte;
- begin
- Result := PMdx7Tag(Tag).Reserved;
- end;
- function TMdx7Tag.GetKeyType: Char;
- begin
- Result := PMdx7Tag(Tag).KeyType;
- end;
- procedure TMdx7Tag.SetHeaderPageNo(NewPageNo: Integer);
- begin
- PMdx7Tag(Tag).HeaderPageNo := NewPageNo;
- end;
- procedure TMdx7Tag.SetTagName(NewName: string);
- begin
- StrPLCopy(PMdx7Tag(Tag).TagName, NewName, 32);
- PMdx7Tag(Tag).TagName[32] := #0;
- end;
- procedure TMdx7Tag.SetKeyFormat(NewFormat: Byte);
- begin
- PMdx7Tag(Tag).KeyFormat := NewFormat;
- end;
- procedure TMdx7Tag.SetForwardTag1(NewTag: Byte);
- begin
- PMdx7Tag(Tag).ForwardTag1 := NewTag;
- end;
- procedure TMdx7Tag.SetForwardTag2(NewTag: Byte);
- begin
- PMdx7Tag(Tag).ForwardTag2 := NewTag;
- end;
- procedure TMdx7Tag.SetBackwardTag(NewTag: Byte);
- begin
- PMdx7Tag(Tag).BackwardTag := NewTag;
- end;
- procedure TMdx7Tag.SetReserved(NewReserved: Byte);
- begin
- PMdx7Tag(Tag).Reserved := NewReserved;
- end;
- procedure TMdx7Tag.SetKeyType(NewType: Char);
- begin
- PMdx7Tag(Tag).KeyType := NewType;
- end;
- //==============================================================================
- //============ TIndexFile
- //==============================================================================
- constructor TIndexFile.Create(ADbfFile: Pointer);
- var
- I: Integer;
- begin
- inherited Create;
- // clear variables
- FOpened := false;
- FRangeActive := false;
- FUpdateMode := umCurrent;
- FModifyMode := mmNormal;
- FTempMode := TDbfFile(ADbfFile).TempMode;
- SelectIndexVars(-1);
- for I := 0 to MaxIndexes - 1 do
- begin
- FParsers[I] := nil;
- FRoots[I] := nil;
- FLeaves[I] := nil;
- FHeaderModified[I] := false;
- end;
- // store pointer to `parent' dbf file
- FDbfFile := ADbfFile;
- end;
- destructor TIndexFile.Destroy;
- begin
- // close file
- Close;
- // call ancestor
- inherited Destroy;
- end;
- procedure TIndexFile.Open;
- var
- I: Integer;
- ext: string;
- localeError: TLocaleError;
- localeSolution: TLocaleSolution;
- DbfLangId: Byte;
- begin
- if not FOpened then
- begin
- // open physical file
- OpenFile;
- // page offsets are not related to header length
- PageOffsetByHeader := false;
- // we need physical page locks
- VirtualLocks := false;
- // not selected index expression => can't edit yet
- FCanEdit := false;
- FUserKey := nil;
- FUserRecNo := -1;
- FHeaderLocked := -1;
- FHeaderPageNo := 0;
- FForceClose := false;
- FForceReadOnly := false;
- FMdxTag := nil;
- // get index type
- ext := UpperCase(ExtractFileExt(FileName));
- if (ext = '.MDX') then
- begin
- FEntryHeaderSize := 4;
- FPageHeaderSize := 8;
- FEntryBof := @Entry_Mdx_BOF;
- FEntryEof := @Entry_Mdx_EOF;
- HeaderSize := 2048;
- RecordSize := 1024;
- PageSize := 512;
- if FileCreated then
- begin
- FIndexVersion := TDbfFile(FDbfFile).DbfVersion;
- if FIndexVersion = xBaseIII then
- FIndexVersion := xBaseIV;
- end else begin
- case PMdxHdr(Header).MdxVersion of
- 3: FIndexVersion := xBaseVII;
- else
- FIndexVersion := xBaseIV;
- end;
- end;
- case FIndexVersion of
- xBaseVII:
- begin
- FMdxTag := TMdx7Tag.Create;
- FTempMdxTag := TMdx7Tag.Create;
- end;
- else
- FMdxTag := TMdx4Tag.Create;
- FTempMdxTag := TMdx4Tag.Create;
- end;
- // get mem for all index headers..we're going to cache these
- for I := 0 to MaxIndexes - 1 do
- begin
- GetMem(FIndexHeaders[I], RecordSize);
- FillChar(FIndexHeaders[I]^, RecordSize, 0);
- end;
- // set pointers to first index
- FIndexHeader := FIndexHeaders[0];
- end else begin
- // don't waste memory on another header block: we can just use
- // the pagedfile one, there is only one index in this file
- FIndexVersion := xBaseIII;
- FEntryHeaderSize := 8;
- FPageHeaderSize := 4;
- FEntryBof := @Entry_Ndx_BOF;
- FEntryEof := @Entry_Ndx_EOF;
- HeaderSize := 512;
- RecordSize := 512;
- // have to read header first before we can assign following vars
- FIndexHeaders[0] := Header;
- FIndexHeader := Header;
- // create default root
- FParsers[0] := TDbfParser.Create(FDbfFile);
- FRoots[0] := TNdxPage.Create(Self);
- FCurrentParser := FParsers[0];
- FRoot := FRoots[0];
- FSelectedIndex := 0;
- // parse index expression
- FCurrentParser.ParseExpression(PIndexHdr(FIndexHeader).KeyDesc);
- // set index locale
- InternalLocaleID := LCID(lcidBinary);
- end;
- // determine how to open file
- if FileCreated then
- begin
- FillChar(Header^, HeaderSize, 0);
- Clear;
- end else begin
- // determine locale type
- localeError := leNone;
- if (FIndexVersion >= xBaseIV) then
- begin
- // get parent language id
- DbfLangId := GetDbfLanguageId;
- // no ID?
- if (DbfLangId = 0) { and (TDbfFile(FDbfFile).DbfVersion = xBaseIII)} then
- begin
- // if dbf is version 3, no language id, if no MDX language, use binary
- if PMdxHdr(Header).Language = 0 then
- InternalLocaleID := lcidBinary
- else
- InternalLocaleID := LangId_To_Locale[PMdxHdr(Header).Language];
- end else begin
- // check if MDX - DBF language id's match
- if (PMdxHdr(Header).Language = 0) or (PMdxHdr(Header).Language = DbfLangId) then
- InternalLocaleID := LangId_To_Locale[DbfLangId]
- else
- localeError := leTableIndexMismatch;
- end;
- // don't overwrite previous error
- if (FLocaleID = DbfLocale_NotFound) and (localeError = leNone) then
- localeError := leUnknown;
- end else begin
- // dbase III always binary?
- InternalLocaleID := lcidBinary;
- end;
- // check if selected locale is available, binary is always available...
- if (localeError <> leNone) and (FLocaleID <> LCID(lcidBinary)) then
- begin
- if LCIDList.IndexOf(Pointer(FLocaleID)) < 0 then
- localeError := leNotAvailable;
- end;
- // check if locale error detected
- if localeError <> leNone then
- begin
- // provide solution, well, solution...
- localeSolution := lsNotOpen;
- // call error handler
- if Assigned(FOnLocaleError) then
- FOnLocaleError(localeError, localeSolution);
- // act to solution
- case localeSolution of
- lsNotOpen: FForceClose := true;
- lsNoEdit: FForceReadOnly := true;
- else
- // `trust' user knows correct locale
- InternalLocaleID := LCID(localeSolution);
- end;
- end;
- // now read info
- if not ForceClose then
- ReadIndexes;
- end;
- // default to update all
- UpdateMode := umAll;
- // flag open
- FOpened := true;
- end;
- end;
- procedure TIndexFile.Close;
- var
- I: Integer;
- begin
- if FOpened then
- begin
- // save headers
- Flush;
- // remove parser reference
- FCurrentParser := nil;
- // free roots
- if FIndexVersion >= xBaseIV then
- begin
- for I := 0 to MaxIndexes - 1 do
- begin
- FreeMemAndNil(FIndexHeaders[I]);
- FreeAndNil(FParsers[I]);
- FreeAndNil(FRoots[I]);
- end;
- end else begin
- FreeAndNil(FRoot);
- end;
- // free mem
- FMdxTag.Free;
- FTempMdxTag.Free;
- // close physical file
- CloseFile;
- // not opened any more
- FOpened := false;
- end;
- end;
- procedure TIndexFile.ClearRoots;
- //
- // *) assumes FIndexVersion >= xBaseIV
- //
- var
- I, prevIndex: Integer;
- begin
- prevIndex := FSelectedIndex;
- for I := 0 to MaxIndexes - 1 do
- begin
- SelectIndexVars(I);
- if FRoot <> nil then
- begin
- // clear this entry
- ClearIndex;
- FLeaves[I] := FRoots[I];
- end;
- FHeaderModified[I] := false;
- end;
- // reselect previously selected index
- SelectIndexVars(prevIndex);
- // deselect index
- end;
- procedure TIndexFile.Clear;
- var
- year, month, day: Word;
- HdrFileName, HdrFileExt: string;
- pos, prevSelIndex: Integer;
- DbfLangId: Byte;
- begin
- // flush cache to prevent reading corrupted data
- Flush;
- // completely erase index
- if FIndexVersion >= xBaseIV then
- begin
- DecodeDate(Now, year, month, day);
- PMdxHdr(Header).MdxVersion := 2;
- PMdxHdr(Header).Year := year - 1900;
- PMdxHdr(Header).Month := month;
- PMdxHdr(Header).Day := day;
- HdrFileName := ExtractFileName(FileName);
- HdrFileExt := ExtractFileExt(HdrFileName);
- if Length(HdrFileExt) > 0 then
- begin
- pos := System.Pos(HdrFileExt, HdrFileName);
- if pos > 0 then
- SetLength(HdrFileName, pos - 1);
- end;
- if Length(HdrFileName) > 15 then
- SetLength(HdrFileName, 15);
- StrPCopy(PMdxHdr(Header).FileName, HdrFileName);
- PMdxHdr(Header).BlockSize := 2;
- PMdxHdr(Header).BlockAdder := 1024;
- PMdxHdr(Header).ProdFlag := 1;
- PMdxHdr(Header).NumTags := 48;
- PMdxHdr(Header).TagSize := 32;
- // PMdxHdr(Header).TagsUsed := 0;
- PMdxHdr(Header).Dummy2 := 0;
- PMdxHdr(Header).Language := GetDbfLanguageID;
- PMdxHdr(Header).NumPages := HeaderSize div PageSize; // = 4
- TouchHeader(Header);
- PMdxHdr(Header).TagFlag := 1;
- // use locale id of parent
- DbfLangId := GetDbfLanguageId;
- if DbfLangId = 0 then
- InternalLocaleID := lcidBinary
- else
- InternalLocaleID := LangID_To_Locale[DbfLangId];
- WriteFileHeader;
- // write index headers
- prevSelIndex := FSelectedIndex;
- for pos := 0 to PMdxHdr(Header).TagsUsed - 1 do
- begin
- SelectIndexVars(pos);
- FMdxTag.HeaderPageNo := GetNewPageNo;
- WriteRecord(FMdxTag.HeaderPageNo, FIndexHeader);
- end;
- // reselect previously selected index
- SelectIndexVars(prevSelIndex);
- // clear roots
- ClearRoots;
- // init vars
- FTagSize := 32;
- FTagOffset := 544;
- // clear entries
- RecordCount := PMdxHdr(Header).NumPages;
- end else begin
- // clear single index entry
- ClearIndex;
- RecordCount := PIndexHdr(FIndexHeader).NumPages;
- end;
- end;
- procedure TIndexFile.ClearIndex;
- var
- prevHeaderLocked: Integer;
- needHeaderLock: Boolean;
- begin
- // flush cache to prevent reading corrupted data
- Flush;
- // modifying header: lock page
- needHeaderLock := FHeaderLocked <> 0;
- prevHeaderLocked := FHeaderLocked;
- if needHeaderLock then
- begin
- LockPage(0, true);
- FHeaderLocked := 0;
- end;
- // initially, we have 1 page: header
- PIndexHdr(FIndexHeader).NumPages := HeaderSize div PageSize;
- // clear memory of root
- FRoot.Clear;
- // get new page for root
- FRoot.GetNewPage;
- // store new root page
- PIndexHdr(FIndexHeader).RootPage := FRoot.PageNo;
- {$ifdef TDBF_UPDATE_FIRSTLAST_NODE}
- PIndexHdr(FIndexHeader).FirstNode := FRoot.PageNo;
- {$endif}
- // update leaf pointers
- FLeaves[FSelectedIndex] := FRoot;
- FLeaf := FRoot;
- // write new header
- WriteHeader;
- FRoot.Modified;
- FRoot.WritePage;
- // done updating: unlock header
- if needHeaderLock then
- begin
- UnlockPage(0);
- FHeaderLocked := prevHeaderLocked;
- end;
- end;
- procedure TIndexFile.CalcKeyProperties;
- // given KeyLen, this func calcs KeyRecLen and NumEntries
- var
- remainder: Integer;
- begin
- // now adjust keylen to align on DWORD boundaries
- PIndexHdr(FIndexHeader).KeyRecLen := PIndexHdr(FIndexHeader).KeyLen + FEntryHeaderSize;
- remainder := (PIndexHdr(FIndexHeader).KeyRecLen) mod 4;
- if (remainder > 0) then
- PIndexHdr(FIndexHeader).KeyRecLen := PIndexHdr(FIndexHeader).KeyRecLen + 4 - remainder;
- PIndexHdr(FIndexHeader).NumKeys := (RecordSize - FPageHeaderSize) div PIndexHdr(FIndexHeader).KeyRecLen;
- end;
- function TIndexFile.GetName: string;
- begin
- // get suitable name of index: if tag name defined use that otherwise filename
- if FIndexVersion >= xBaseIV then
- Result := FIndexName
- else
- Result := FileName;
- end;
- procedure TIndexFile.CreateIndex(FieldDesc, TagName: string; Options: TIndexOptions);
- var
- tagNo: Integer;
- fieldType: Char;
- TempParser: TDbfParser;
- begin
- // check if we have exclusive access to table
- TDbfFile(FDbfFile).CheckExclusiveAccess;
- // parse index expression; if it cannot be parsed, why bother making index?
- TempParser := TDbfParser.Create(FDbfFile);
- try
- TempParser.ParseExpression(FieldDesc);
- // check if result type is correct
- case TempParser.ResultType of
- etString: fieldType := 'C';
- etInteger, etLargeInt, etFloat: fieldType := 'N';
- else
- raise EDbfError.Create(STRING_INVALID_INDEX_TYPE);
- end;
- finally
- TempParser.Free;
- end;
- // select empty index
- if FIndexVersion >= xBaseIV then
- begin
- // get next entry no
- tagNo := PMdxHdr(Header).TagsUsed;
- // check if too many indexes
- if tagNo = MaxIndexes then
- raise EDbfError.Create(STRING_TOO_MANY_INDEXES);
- // get memory for root
- if FRoots[tagNo] = nil then
- begin
- FParsers[tagNo] := TDbfParser.Create(FDbfFile);
- FRoots[tagNo] := TMdxPage.Create(Self)
- end else begin
- FreeAndNil(FRoots[tagNo].FLowerPage);
- end;
- // set leaves pointer
- FLeaves[tagNo] := FRoots[tagNo];
- // get pointer to index header
- FIndexHeader := FIndexHeaders[tagNo];
- // load root + leaf
- FCurrentParser := FParsers[tagNo];
- FRoot := FRoots[tagNo];
- FLeaf := FLeaves[tagNo];
- // create new tag
- FTempMdxTag.Tag := CalcTagOffset(tagNo);
- FTempMdxTag.TagName := UpperCase(TagName);
- // if expression then calculate
- FTempMdxTag.KeyFormat := KeyFormat_Data;
- if ixExpression in Options then
- FTempMdxTag.KeyFormat := KeyFormat_Expression;
- // what use have these reference tags?
- FTempMdxTag.ForwardTag1 := 0;
- FTempMdxTag.ForwardTag2 := 0;
- FTempMdxTag.BackwardTag := 0;
- FTempMdxTag.Reserved := 2;
- FTempMdxTag.KeyType := fieldType;
- // save this part of tag, need to save before GetNewPageNo,
- // it will reread header
- WriteFileHeader;
- // store selected index
- FSelectedIndex := tagNo;
- FIndexName := TagName;
- // store new headerno
- FHeaderPageNo := GetNewPageNo;
- FTempMdxTag.HeaderPageNo := FHeaderPageNo;
- // increase number of indexes active
- inc(PMdxHdr(Header).TagsUsed);
- // update updatemode
- UpdateMode := umAll;
- // index header updated
- WriteFileHeader;
- end;
- // clear index
- ClearIndex;
- // parse expression, we know it's parseable, we've checked that
- FCurrentParser.ParseExpression(FieldDesc);
- // looked up index expression: now we can edit
- // FIsExpression := ixExpression in Options;
- FCanEdit := not FForceReadOnly;
- // init key variables
- PIndexHdr(FIndexHeader).KeyFormat := 0;
- // descending
- if ixDescending in Options then
- PIndexHdr(FIndexHeader).KeyFormat := PIndexHdr(FIndexHeader).KeyFormat or KeyFormat_Descending;
- // key type
- if fieldType = 'C' then
- PIndexHdr(FIndexHeader).KeyFormat := PIndexHdr(FIndexHeader).KeyFormat or KeyFormat_String;
- PIndexHdr(FIndexHeader).KeyType := fieldType;
- // uniqueness
- PIndexHdr(FIndexHeader).Unique := Unique_None;
- if ixPrimary in Options then
- begin
- PIndexHdr(FIndexHeader).KeyFormat := PIndexHdr(FIndexHeader).KeyFormat or KeyFormat_Distinct or KeyFormat_Unique;
- PIndexHdr(FIndexHeader).Unique := Unique_Distinct;
- end else if ixUnique in Options then
- begin
- PIndexHdr(FIndexHeader).KeyFormat := PIndexHdr(FIndexHeader).KeyFormat or KeyFormat_Unique;
- PIndexHdr(FIndexHeader).Unique := Unique_Unique;
- end;
- // keylen is exact length of field
- if fieldType = 'C' then
- PIndexHdr(FIndexHeader).KeyLen := FCurrentParser.ResultLen
- else if FIndexVersion >= xBaseIV then
- PIndexHdr(FIndexHeader).KeyLen := 12
- else
- PIndexHdr(FIndexHeader).KeyLen := 8;
- CalcKeyProperties;
- // key desc
- StrPLCopy(PIndexHdr(FIndexHeader).KeyDesc, FieldDesc, 219);
- PIndexHdr(FIndexHeader).KeyDesc[219] := #0;
- // init various
- if FIndexVersion >= xBaseIV then
- PIndexHdr(FIndexHeader).Dummy := 0 // MDX -> language driver
- else
- PIndexHdr(FIndexHeader).Dummy := $5800; // NDX -> same ???
- case fieldType of
- 'C':
- PIndexHdr(FIndexHeader).sKeyType := 0;
- 'D':
- PIndexHdr(FIndexHeader).sKeyType := 1;
- 'N', 'F':
- if FIndexVersion >= xBaseIV then
- PIndexHdr(FIndexHeader).sKeyType := 0
- else
- PIndexHdr(FIndexHeader).sKeyType := 1;
- else
- PIndexHdr(FIndexHeader).sKeyType := 0;
- end;
- PIndexHdr(FIndexHeader).Version := 2; // this is what DB4 writes into file
- PIndexHdr(FIndexHeader).Dummy2 := 0;
- PIndexHdr(FIndexHeader).Dummy3 := 0;
- PIndexHdr(FIndexHeader).ForExist := 0; // false
- PIndexHdr(FIndexHeader).KeyExist := 1; // true
- {$ifndef TDBF_UPDATE_FIRSTLAST_NODE}
- // if not defined, init to zero
- PIndexHdr(FIndexHeader).FirstNode := 0;
- PIndexHdr(FIndexHeader).LastNode := 0;
- {$endif}
- WriteHeader;
- // update internal properties
- UpdateIndexProperties;
- // for searches / inserts / deletes
- FKeyBuffer[PIndexHdr(FIndexHeader).KeyLen] := #0;
- end;
- procedure TIndexFile.ReadIndexes;
- var
- I: Integer;
- procedure CheckHeaderIntegrity;
- begin
- if PIndexHdr(FIndexHeader).NumKeys * PIndexHdr(FIndexHeader).KeyRecLen > RecordSize then
- begin
- // adjust index header so that integrity is correct
- // WARNING: we can't be sure this gives a correct result, but at
- // least we won't AV (as easily). user will probably have to regenerate this index
- if PIndexHdr(FIndexHeader).KeyLen > 100 then
- PIndexHdr(FIndexHeader).KeyLen := 100;
- CalcKeyProperties;
- end;
- end;
- begin
- // force header reread
- inherited ReadHeader;
- // examine all indexes
- if FIndexVersion >= xBaseIV then
- begin
- // clear all roots
- ClearRoots;
- // tags are extended at beginning?
- FTagSize := PMdxHdr(Header).TagSize;
- FTagOffset := 544 + FTagSize - 32;
- for I := 0 to PMdxHdr(Header).TagsUsed - 1 do
- begin
- // read page header
- FTempMdxTag.Tag := CalcTagOffset(I);
- ReadRecord(FTempMdxTag.HeaderPageNo, FIndexHeaders[I]);
- // select it
- FIndexHeader := FIndexHeaders[I];
- // create root if needed
- if FRoots[I] = nil then
- begin
- FParsers[I] := TDbfParser.Create(FDbfFile);
- FRoots[I] := TMdxPage.Create(Self);
- end;
- // check header integrity
- CheckHeaderIntegrity;
- // read tree
- FRoots[I].PageNo := PIndexHdr(FIndexHeader).RootPage;
- // go to first record
- FRoots[I].RecurFirst;
- // store leaf
- FLeaves[I] := FRoots[I];
- while FLeaves[I].LowerPage <> nil do
- FLeaves[I] := FLeaves[I].LowerPage;
- // parse expression
- FParsers[I].ParseExpression(PIndexHdr(FIndexHeader).KeyDesc);
- end;
- end else begin
- // clear root
- FRoot.Clear;
- // check recordsize constraint
- CheckHeaderIntegrity;
- // just one index: read tree
- FRoot.PageNo := PIndexHdr(FIndexHeader).RootPage;
- // go to first valid record
- FRoot.RecurFirst;
- // get leaf page
- FLeaf := FRoot;
- while FLeaf.LowerPage <> nil do
- FLeaf := FLeaf.LowerPage;
- // write leaf pointer to first index
- FLeaves[0] := FLeaf;
- // get index properties -> internal props
- UpdateIndexProperties;
- end;
- end;
- procedure TIndexFile.DeleteIndex(const AIndexName: string);
- var
- I, found, numTags, moveItems: Integer;
- tempHeader: Pointer;
- tempRoot, tempLeaf: TIndexPage;
- tempParser: TDbfParser;
- begin
- // check if we have exclusive access to table
- TDbfFile(FDbfFile).CheckExclusiveAccess;
- if FIndexVersion = xBaseIII then
- begin
- Close;
- DeleteFile;
- end else if FIndexVersion >= xBaseIV then
- begin
- // find index
- found := IndexOf(AIndexName);
- if found >= 0 then
- begin
- // just remove this tag by copying memory over it
- numTags := PMdxHdr(Header).TagsUsed;
- moveItems := numTags - found - 1;
- // anything to move?
- if moveItems > 0 then
- begin
- // move entries after found one
- Move((Header + FTagOffset + (found+1) * FTagSize)^,
- (Header + FTagOffset + found * FTagSize)^, moveItems * FTagSize);
- // nullify last entry
- FillChar((Header + FTagOffset + numTags * FTagSize)^, FTagSize, 0);
- // index headers, roots, leaves
- tempHeader := FIndexHeaders[found];
- tempParser := FParsers[found];
- tempRoot := FRoots[found];
- tempLeaf := FLeaves[found];
- for I := 0 to moveItems - 1 do
- begin
- FIndexHeaders[found + I] := FIndexHeaders[found + I + 1];
- FParsers[found + I] := FParsers[found + I + 1];
- FRoots[found + I] := FRoots[found + I + 1];
- FLeaves[found + I] := FLeaves[found + I + 1];
- FHeaderModified[found + I] := true;
- end;
- FIndexHeaders[found + moveItems] := tempHeader;
- FParsers[found + moveItems] := tempParser;
- FRoots[found + moveItems] := tempRoot;
- FLeaves[found + moveItems] := tempLeaf;
- FHeaderModified[found + moveItems] := false; // non-existant header
- end;
- // one entry less left
- dec(PMdxHdr(Header).TagsUsed);
- // ---*** numTags not valid from here ***---
- // file header changed
- WriteFileHeader;
- // repage index to free space used by deleted index
- // RepageFile;
- end;
- end;
- end;
- procedure TIndexFile.TouchHeader(AHeader: Pointer);
- var
- year, month, day: Word;
- begin
- DecodeDate(Now, year, month, day);
- PMdxHdr(AHeader).UpdYear := year - 1900;
- PMdxHdr(AHeader).UpdMonth := month;
- PMdxHdr(AHeader).UpdDay := day;
- end;
- function TIndexFile.CreateTempFile(BaseName: string): TPagedFile;
- var
- lModifier: Integer;
- begin
- // create temporary in-memory index file
- lModifier := 0;
- FindNextName(BaseName, BaseName, lModifier);
- Result := TPagedFile.Create;
- Result.FileName := BaseName;
- Result.Mode := pfExclusiveCreate;
- Result.AutoCreate := true;
- Result.OpenFile;
- Result.HeaderSize := HeaderSize;
- Result.RecordSize := RecordSize;
- Result.PageSize := PageSize;
- Result.PageOffsetByHeader := false;
- end;
- procedure TIndexFile.RepageFile;
- var
- TempFile: TPagedFile;
- TempIdxHeader: PIndexHdr;
- I, newPageNo: Integer;
- prevIndex: Integer;
- function GetNewPageNo: Integer;
- begin
- Result := newPageNo;
- Inc(newPageNo, PagesPerRecord);
- if FIndexVersion >= xBaseIV then
- Inc(PMdxHdr(TempFile.Header).NumPages, PagesPerRecord);
- Inc(TempIdxHeader.NumPages, PagesPerRecord);
- end;
- function WriteTree(NewPage: TIndexPage): Integer;
- var
- J: Integer;
- begin
- // get us a page so that page no's are more logically ordered
- Result := GetNewPageNo;
- // use postorder visiting, first do all children
- if NewPage.LowerPage <> nil then
- begin
- for J := 0 to NewPage.HighIndex do
- begin
- NewPage.EntryNo := J;
- WriteTree(NewPage.LowerPage);
- end;
- end;
- // now create new page for ourselves and write
- // update page pointer in parent
- if NewPage.UpperPage <> nil then
- begin
- if FIndexVersion >= xBaseIV then
- begin
- PMdxEntry(NewPage.UpperPage.Entry).RecBlockNo := Result;
- {$ifdef TDBF_UPDATE_FIRSTLAST_NODE}
- // write previous node
- if FRoot = NewPage then
- PMdxPage(NewPage.PageBuffer).PrevBlock := 0
- else
- PMdxPage(NewPage.PageBuffer).PrevBlock := Result - PagesPerRecord;
- {$endif}
- end else begin
- PNdxEntry(NewPage.UpperPage.Entry).LowerPageNo := Result;
- end;
- end;
- // store page
- TempFile.WriteRecord(Result, NewPage.PageBuffer);
- end;
- procedure CopySelectedIndex;
- var
- hdrPageNo: Integer;
- begin
- // copy current index settings
- Move(FIndexHeader^, TempIdxHeader^, RecordSize);
- // clear number of pages
- TempIdxHeader.NumPages := PagesPerRecord;
- // allocate a page no for header
- hdrPageNo := GetNewPageNo;
- // use recursive function to write all pages
- TempIdxHeader.RootPage := WriteTree(FRoot);
- {$ifdef TDBF_UPDATE_FIRSTLAST_NODE}
- TempIdxHeader.FirstNode := TempIdxHeader.RootPage;
- {$endif}
- // write index header now we know the root page
- TempFile.WriteRecord(hdrPageNo, TempIdxHeader);
- if FIndexVersion >= xBaseIV then
- begin
- // calculate tag offset in tempfile header
- FTempMdxTag.Tag := PChar(TempFile.Header) + (PChar(CalcTagOffset(I)) - Header);
- FTempMdxTag.HeaderPageNo := hdrPageNo;
- end;
- end;
- begin
- CheckExclusiveAccess;
- prevIndex := FSelectedIndex;
- newPageNo := HeaderSize div PageSize;
- TempFile := CreateTempFile(FileName);
- if FIndexVersion >= xBaseIV then
- begin
- // copy header
- Move(Header^, TempFile.Header^, HeaderSize);
- TouchHeader(TempFile.Header);
- // reset header
- PMdxHdr(TempFile.Header).NumPages := HeaderSize div PageSize;
- TempFile.WriteHeader;
- GetMem(TempIdxHeader, RecordSize);
- // now recreate indexes to that file
- for I := 0 to PMdxHdr(Header).TagsUsed - 1 do
- begin
- // select this index
- SelectIndexVars(I);
- CopySelectedIndex;
- end;
- FreeMem(TempIdxHeader);
- end else begin
- // indexversion = xBaseIII
- TempIdxHeader := PIndexHdr(TempFile.Header);
- CopySelectedIndex;
- end;
- TempFile.WriteHeader;
- TempFile.CloseFile;
- CloseFile;
- // rename temporary file if all went successfull
- if not TempFile.WriteError then
- begin
- SysUtils.DeleteFile(FileName);
- SysUtils.RenameFile(TempFile.FileName, FileName);
- end;
- TempFile.Free;
- DisableForceCreate;
- OpenFile;
- ReadIndexes;
- SelectIndexVars(prevIndex);
- end;
- procedure TIndexFile.CompactFile;
- var
- TempFile: TPagedFile;
- TempIdxHeader: PIndexHdr;
- I, newPageNo: Integer;
- prevIndex: Integer;
- function GetNewPageNo: Integer;
- begin
- Result := newPageNo;
- Inc(newPageNo, PagesPerRecord);
- if FIndexVersion >= xBaseIV then
- Inc(PMdxHdr(TempFile.Header).NumPages, PagesPerRecord);
- Inc(TempIdxHeader.NumPages, PagesPerRecord);
- end;
- function CreateNewPage: TIndexPage;
- begin
- // create new page + space
- if FIndexVersion >= xBaseIV then
- Result := TMdxPage.Create(Self)
- else
- Result := TNdxPage.Create(Self);
- Result.FPageNo := GetNewPageNo;
- // set new page properties
- Result.SetNumEntries(0);
- end;
- procedure GetNewEntry(APage: TIndexPage);
- // makes a new entry available and positions current 'pos' on it
- // NOTES: uses TIndexPage *very* carefully
- // - may not read from self (tindexfile)
- // - page.FLowerPage is assigned -> SyncLowerPage may *not* be called
- // - do not set PageNo (= SetPageNo)
- // - do not set EntryNo
- begin
- if APage.HighIndex >= PIndexHdr(FIndexHeader).NumKeys-1 then
- begin
- if APage.UpperPage = nil then
- begin
- // add new upperlevel to page
- APage.FUpperPage := CreateNewPage;
- APage.UpperPage.FLowerPage := APage;
- APage.UpperPage.FEntryNo := 0;
- APage.UpperPage.FEntry := EntryEof;
- APage.UpperPage.GotoInsertEntry;
- APage.UpperPage.LocalInsert(0, APage.Key, APage.PageNo);
- // non-leaf pages need 'rightmost' key; numentries = real# - 1
- APage.UpperPage.SetNumEntries(0);
- end;
- // page done, store
- TempFile.WriteRecord(APage.FPageNo, APage.PageBuffer);
- // allocate new page
- APage.FPageNo := GetNewPageNo;
- // clear
- APage.SetNumEntries(0);
- APage.FHighIndex := -1;
- APage.FLowIndex := 0;
- // clear 'right-most' blockno
- APage.SetRecLowerPageNoOfEntry(0, 0, 0);
- // get new entry in upper page for current new apage
- GetNewEntry(APage.UpperPage);
- APage.UpperPage.LocalInsert(0, nil, 0);
- // non-leaf pages need 'rightmost' key; numentries = real# - 1
- if APage.UpperPage.EntryNo = 0 then
- APage.UpperPage.SetNumEntries(0);
- end;
- APage.FEntryNo := APage.HighIndex+1;
- APage.FEntry := EntryEof;
- APage.GotoInsertEntry;
- end;
- procedure CopySelectedIndex;
- var
- APage: TIndexPage;
- hdrPageNo: Integer;
- begin
- // copy current index settings
- Move(FIndexHeader^, TempIdxHeader^, RecordSize);
- // clear number of pages
- TempIdxHeader.NumPages := PagesPerRecord;
- // allocate a page no for header
- hdrPageNo := GetNewPageNo;
- // copy all records
- APage := CreateNewPage;
- FLeaf.RecurFirst;
- while not (FRoot.Entry = FEntryEof) do
- begin
- GetNewEntry(APage);
- APage.LocalInsert(FLeaf.PhysicalRecNo, FLeaf.Key, 0);
- FLeaf.RecurNext;
- end;
- // flush remaining (partially filled) pages
- repeat
- TempFile.WriteRecord(APage.FPageNo, APage.PageBuffer);
- if APage.UpperPage <> nil then
- APage := APage.UpperPage
- else break;
- until false;
- // copy index header + root page
- TempIdxHeader.RootPage := APage.PageNo;
- {$ifdef TDBF_UPDATE_FIRSTLAST_NODE}
- TempIdxHeader.FirstNode := APage.PageNo;
- {$endif}
- // write index header now we know the root page
- TempFile.WriteRecord(hdrPageNo, TempIdxHeader);
- if FIndexVersion >= xBaseIV then
- begin
- // calculate tag offset in tempfile header
- FTempMdxTag.Tag := PChar(TempFile.Header) + (PChar(CalcTagOffset(I)) - Header);
- FTempMdxTag.HeaderPageNo := hdrPageNo;
- end;
- end;
- begin
- CheckExclusiveAccess;
- prevIndex := FSelectedIndex;
- newPageNo := HeaderSize div PageSize;
- TempFile := CreateTempFile(FileName);
- if FIndexVersion >= xBaseIV then
- begin
- // copy header
- Move(Header^, TempFile.Header^, HeaderSize);
- TouchHeader(TempFile.Header);
- // reset header
- PMdxHdr(TempFile.Header).NumPages := HeaderSize div PageSize;
- TempFile.WriteHeader;
- GetMem(TempIdxHeader, RecordSize);
- // now recreate indexes to that file
- for I := 0 to PMdxHdr(Header).TagsUsed - 1 do
- begin
- // select this index
- SelectIndexVars(I);
- CopySelectedIndex;
- end;
- FreeMem(TempIdxHeader);
- end else begin
- // indexversion = xBaseIII
- TempIdxHeader := PIndexHdr(TempFile.Header);
- CopySelectedIndex;
- end;
- TempFile.WriteHeader;
- TempFile.CloseFile;
- CloseFile;
- // rename temporary file if all went successfull
- if not TempFile.WriteError then
- begin
- SysUtils.DeleteFile(FileName);
- SysUtils.RenameFile(TempFile.FileName, FileName);
- end;
- TempFile.Free;
- DisableForceCreate;
- OpenFile;
- ReadIndexes;
- SelectIndexVars(prevIndex);
- end;
- function TIndexFile.GetNewPageNo: Integer;
- var
- needLockHeader: Boolean;
- begin
- // update header -> lock it if not already locked
- needLockHeader := FHeaderLocked <> 0;
- if needLockHeader then
- begin
- // lock header page
- LockPage(0, true);
- // someone else could be inserting records at the same moment
- if NeedLocks then
- inherited ReadHeader;
- end;
- if FIndexVersion >= xBaseIV then
- begin
- Result := PMdxHdr(Header).NumPages;
- PMdxHdr(Header).NumPages := PMdxHdr(Header).NumPages + PagesPerRecord;
- {$ifdef TDBF_UPDATE_FIRSTLAST_NODE}
- // adjust high page
- PIndexHdr(FIndexHeader).LastNode := Result;
- {$endif}
- WriteFileHeader;
- end else begin
- Result := PIndexHdr(FIndexHeader).NumPages;
- end;
- PIndexHdr(FIndexHeader).NumPages := PIndexHdr(FIndexHeader).NumPages + PagesPerRecord;
- WriteHeader;
- // done updating header -> unlock if locked
- if needLockHeader then
- UnlockPage(0);
- end;
- procedure TIndexFile.Insert(RecNo: Integer; Buffer: PChar); {override;}
- var
- I, curSel: Integer;
- begin
- // check if updating all or only current
- FUserRecNo := RecNo;
- if (FUpdateMode = umAll) or (FSelectedIndex = -1) then
- begin
- // remember currently selected index
- curSel := FSelectedIndex;
- for I := 0 to PMdxHdr(Header).TagsUsed - 1 do
- begin
- SelectIndexVars(I);
- InsertKey(Buffer);
- end;
- // restore previous selected index
- SelectIndexVars(curSel);
- end else begin
- InsertKey(Buffer);
- end;
- // check range, disabled by insert
- ResyncRange(true);
- end;
- function TIndexFile.CheckKeyViolation(Buffer: PChar): Boolean;
- var
- I, curSel: Integer;
- begin
- Result := false;
- FUserRecNo := -2;
- if FIndexVersion = xBaseIV then
- begin
- curSel := FSelectedIndex;
- I := 0;
- while (I < PMdxHdr(Header).TagsUsed) and not Result do
- begin
- SelectIndexVars(I);
- if FUniqueMode = iuDistinct then
- begin
- FUserKey := ExtractKeyFromBuffer(Buffer);
- Result := FindKey(false) = 0;
- end;
- Inc(I);
- end;
- SelectIndexVars(curSel);
- end else begin
- if FUniqueMode = iuDistinct then
- begin
- FUserKey := ExtractKeyFromBuffer(Buffer);
- Result := FindKey(false) = 0;
- end;
- end;
- end;
- function TIndexFile.PrepareKey(Buffer: PChar; ResultType: TExpressionType): PChar;
- var
- FloatRec: TFloatRec;
- I, IntSrc, NumDecimals: Integer;
- ExtValue: Extended;
- BCDdigit: Byte;
- {$ifdef SUPPORT_INT64}
- Int64Src: Int64;
- {$endif}
- begin
- // need to convert numeric?
- Result := Buffer;
- if PIndexHdr(FIndexHeader).KeyType in ['N', 'F'] then
- begin
- if FIndexVersion = xBaseIII then
- begin
- // DB3 -> index always 8 byte float, if original integer, convert to double
- case ResultType of
- etInteger:
- begin
- FUserNumeric := PInteger(Result)^;
- Result := PChar(@FUserNumeric);
- end;
- {$ifdef SUPPORT_INT64}
- etLargeInt:
- begin
- FUserNumeric := PLargeInt(Result)^;
- Result := PChar(@FUserNumeric);
- end;
- {$endif}
- end;
- end else begin
- // DB4 MDX
- NumDecimals := 0;
- case ResultType of
- etInteger:
- begin
- IntSrc := PInteger(Result)^;
- // handle zero differently: no decimals
- if IntSrc <> 0 then
- NumDecimals := GetStrFromInt(IntSrc, @FloatRec.Digits[0])
- else
- NumDecimals := 0;
- FloatRec.Negative := IntSrc < 0;
- end;
- {$ifdef SUPPORT_INT64}
- etLargeInt:
- begin
- Int64Src := PLargeInt(Result)^;
- if Int64Src <> 0 then
- NumDecimals := GetStrFromInt64(Int64Src, @FloatRec.Digits[0])
- else
- NumDecimals := 0;
- FloatRec.Negative := Int64Src < 0;
- end;
- {$endif}
- etFloat:
- begin
- ExtValue := PDouble(Result)^;
- FloatToDecimal(FloatRec, ExtValue, {$ifndef FPC_VERSION}fvExtended,{$endif} 9999, 15);
- if ExtValue <> 0.0 then
- NumDecimals := StrLen(@FloatRec.Digits[0])
- else
- NumDecimals := 0;
- // maximum number of decimals possible to encode in BCD is 16
- if NumDecimals > 16 then
- NumDecimals := 16;
- end;
- end;
- case ResultType of
- etInteger {$ifdef SUPPORT_INT64}, etLargeInt{$endif}:
- begin
- FloatRec.Exponent := NumDecimals;
- // MDX-BCD does not count ending zeroes as `data' space length
- while (NumDecimals > 0) and (FloatRec.Digits[NumDecimals-1] = '0') do
- Dec(NumDecimals);
- // null-terminate string
- FloatRec.Digits[NumDecimals] := #0;
- end;
- end;
- // write 'header', contains number of digits before decimal separator
- FUserBCD[0] := $34 + FloatRec.Exponent;
- // clear rest of BCD
- FillChar(FUserBCD[1], SizeOf(FUserBCD)-1, 0);
- // store number of bytes used (in number of bits + 1)
- FUserBCD[1] := (((NumDecimals+1) div 2) * 8) + 1;
- // where to store decimal dot position? now implicitly in first byte
- // store negative sign
- if FloatRec.Negative then
- FUserBCD[1] := FUserBCD[1] or $80;
- // convert string to BCD
- I := 0;
- while I < NumDecimals do
- begin
- // only one byte left?
- if FloatRec.Digits[I+1] = #0 then
- BCDdigit := 0
- else
- BCDdigit := Byte(FloatRec.Digits[I+1]) - Byte('0');
- // pack two bytes into bcd
- FUserBCD[2+(I div 2)] := ((Byte(FloatRec.Digits[I]) - Byte('0')) shl 4) or BCDdigit;
- // goto next 2 bytes
- Inc(I, 2);
- end;
- // set result pointer to BCD
- Result := PChar(@FUserBCD[0]);
- end;
- end;
- end;
- function TIndexFile.ExtractKeyFromBuffer(Buffer: PChar): PChar;
- begin
- // execute expression to get key
- Result := PrepareKey(FCurrentParser.ExtractFromBuffer(Buffer), FCurrentParser.ResultType);
- end;
- procedure TIndexFile.InsertKey(Buffer: PChar);
- begin
- // check proper index and modifiability
- if FCanEdit and (PIndexHdr(FIndexHeader).KeyLen <> 0) then
- begin
- // get key from buffer
- FUserKey := ExtractKeyFromBuffer(Buffer);
- // patch through
- InsertCurrent;
- end;
- end;
- procedure TIndexFile.InsertCurrent;
- // insert in current index
- // assumes: FUserKey is an OEM key
- var
- SearchKey: array[0..100] of Char;
- OemKey: PChar;
- begin
- // only insert if not recalling or mode = distinct
- // modify = mmDeleteRecall /\ unique <> distinct -> key already present
- if (FModifyMode <> mmDeleteRecall) or (FUniqueMode = iuDistinct) then
- begin
- // translate OEM key to ANSI key for searching
- OemKey := FUserKey;
- if KeyType = 'C' then
- begin
- FUserKey := @SearchKey[0];
- TranslateToANSI(OemKey, FUserKey);
- end;
- // temporarily remove range to find correct location of key
- ResetRange;
- // find this record as closely as possible
- // if result = 0 then key already exists
- // if unique index, then don't insert key if already present
- if (FindKey(true) <> 0) or (FUniqueMode = iuNormal) then
- begin
- // switch to oem key
- FUserKey := OemKey;
- // if we found eof, write to pagebuffer
- FLeaf.GotoInsertEntry;
- // insert requested entry, we know there is an entry available
- FLeaf.LocalInsert(FUserRecNo, FUserKey, 0);
- end else begin
- // key already exists -> test possible key violation
- if FUniqueMode = iuDistinct then
- begin
- // raising -> reset modify mode
- FModifyMode := mmNormal;
- InsertError;
- end;
- end;
- end;
- end;
- procedure TIndexFile.InsertError;
- var
- InfoKey: string;
- begin
- // prepare info for user
- InfoKey := FUserKey;
- SetLength(InfoKey, KeyLen);
- raise EDbfError.CreateFmt(STRING_KEY_VIOLATION, [GetName, PhysicalRecNo, TrimRight(InfoKey)]);
- end;
- procedure TIndexFile.Delete(RecNo: Integer; Buffer: PChar);
- var
- I, curSel: Integer;
- begin
- // check if updating all or only current
- FUserRecNo := RecNo;
- if (FUpdateMode = umAll) or (FSelectedIndex = -1) then
- begin
- // remember currently selected index
- curSel := FSelectedIndex;
- for I := 0 to PMdxHdr(Header).TagsUsed - 1 do
- begin
- SelectIndexVars(I);
- DeleteKey(Buffer);
- end;
- // restore previous selected index
- SelectIndexVars(curSel);
- end else begin
- DeleteKey(Buffer);
- end;
- // range may be changed
- ResyncRange(true);
- end;
- procedure TIndexFile.DeleteKey(Buffer: PChar);
- begin
- if FCanEdit and (PIndexHdr(FIndexHeader).KeyLen <> 0) then
- begin
- // get key from record buffer
- FUserKey := ExtractKeyFromBuffer(Buffer);
- // call function
- DeleteCurrent;
- end;
- end;
- procedure TIndexFile.DeleteCurrent;
- // deletes from current index
- var
- SearchKey: array[0..100] of Char;
- OemKey: PChar;
- begin
- // only delete if not delete record or mode = distinct
- // modify = mmDeleteRecall /\ unique = distinct -> key needs to be deleted from index
- if (FModifyMode <> mmDeleteRecall) or (FUniqueMode = iuDistinct) then
- begin
- // prevent "confined" view of index while deleting
- ResetRange;
- // search correct entry to delete
- if FLeaf.PhysicalRecNo <> FUserRecNo then
- begin
- // translate OEM key to ANSI key for searching
- OemKey := FUserKey;
- if KeyType = 'C' then
- begin
- FUserKey := @SearchKey[0];
- TranslateToANSI(OemKey, FUserKey);
- end;
- FindKey(false);
- end;
- // delete selected entry
- FLeaf.Delete;
- end;
- end;
- procedure TIndexFile.Update(RecNo: Integer; PrevBuffer, NewBuffer: PChar);
- var
- I, curSel: Integer;
- begin
- // check if updating all or only current
- FUserRecNo := RecNo;
- if (FUpdateMode = umAll) or (FSelectedIndex = -1) then
- begin
- // remember currently selected index
- curSel := FSelectedIndex;
- for I := 0 to PMdxHdr(Header).TagsUsed - 1 do
- begin
- SelectIndexVars(I);
- UpdateCurrent(PrevBuffer, NewBuffer);
- end;
- // restore previous selected index
- SelectIndexVars(curSel);
- end else begin
- UpdateCurrent(PrevBuffer, NewBuffer);
- end;
- end;
- procedure TIndexFile.UpdateCurrent(PrevBuffer, NewBuffer: PChar);
- var
- TempBuffer: array [0..100] of Char;
- begin
- if FCanEdit and (PIndexHdr(FIndexHeader).KeyLen <> 0) then
- begin
- // get key from newbuffer
- FUserKey := ExtractKeyFromBuffer(NewBuffer);
- Move(FUserKey^, TempBuffer, PIndexHdr(FIndexHeader).KeyLen);
- // get key from prevbuffer
- FUserKey := ExtractKeyFromBuffer(PrevBuffer);
- // compare to see if anything changed
- if CompareKeys(@TempBuffer[0], FUserKey) <> 0 then
- begin
- // first set userkey to key to delete
- // FUserKey = KeyFrom(PrevBuffer)
- DeleteCurrent;
- // now set userkey to key to insert
- FUserKey := @TempBuffer[0];
- InsertCurrent;
- // check range, disabled by delete/insert
- ResyncRange(true);
- end;
- end;
- end;
- procedure TIndexFile.AddNewLevel;
- var
- lNewPage: TIndexPage;
- pKeyData: PChar;
- begin
- // create new page + space
- if FIndexVersion >= xBaseIV then
- lNewPage := TMdxPage.Create(Self)
- else
- lNewPage := TNdxPage.Create(Self);
- lNewPage.GetNewPage;
- // lock this new page; will be unlocked by caller
- lNewPage.LockPage;
- // lock index header; will be unlocked by caller
- LockPage(FHeaderPageNo, true);
- FHeaderLocked := FHeaderPageNo;
- // modify header
- PIndexHdr(FIndexHeader).RootPage := lNewPage.PageNo;
- // set new page properties
- lNewPage.SetNumEntries(0);
- lNewPage.EntryNo := 0;
- lNewPage.GotoInsertEntry;
- {$ifdef TDBF_UPDATE_FIRST_LAST_NODE}
- lNewPage.SetPrevBlock(lNewPage.PageNo - PagesPerRecord);
- {$endif}
- pKeyData := FRoot.GetKeyDataFromEntry(0);
- lNewPage.FLowerPage := FRoot;
- lNewPage.FHighIndex := 0;
- lNewPage.SetEntry(0, pKeyData, FRoot.PageNo);
- // update root pointer
- FRoot.UpperPage := lNewPage;
- FRoots[FSelectedIndex] := lNewPage;
- FRoot := lNewPage;
- // write new header
- WriteRecord(FHeaderPageNo, FIndexHeader);
- end;
- procedure TIndexFile.UnlockHeader;
- begin
- if FHeaderLocked <> -1 then
- begin
- UnlockPage(FHeaderLocked);
- FHeaderLocked := -1;
- end;
- end;
- procedure TIndexFile.ResyncRoot;
- begin
- if FIndexVersion >= xBaseIV then
- begin
- // read header page
- inherited ReadRecord(FHeaderPageNo, FIndexHeader);
- end else
- inherited ReadHeader;
- // reread tree
- FRoot.PageNo := PIndexHdr(FIndexHeader).RootPage;
- end;
- function TIndexFile.SearchKey(Key: PChar; SearchType: TSearchKeyType): Boolean;
- var
- findres, currRecNo: Integer;
- begin
- // save current position
- currRecNo := SequentialRecNo;
- // search, these are always from the root: no need for first
- findres := Find(-2, Key);
- // test result
- case SearchType of
- stEqual:
- Result := findres = 0;
- stGreaterEqual:
- Result := findres <= 0;
- stGreater:
- begin
- if findres = 0 then
- begin
- // find next record that is greater
- // NOTE: MatchKey assumes key to search for is already specified
- // in FUserKey, it is because we have called Find
- repeat
- Result := WalkNext;
- until not Result or (MatchKey(Key) <> 0);
- end else
- Result := findres < 0;
- end;
- else
- Result := false;
- end;
- // search failed -> restore previous position
- if not Result then
- SequentialRecNo := currRecNo;
- end;
- function TIndexFile.Find(RecNo: Integer; Buffer: PChar): Integer;
- begin
- // execute find
- FUserRecNo := RecNo;
- FUserKey := Buffer;
- Result := FindKey(false);
- end;
- function TIndexFile.FindKey(Insert: boolean): Integer;
- //
- // if you set Insert = true, you need to re-enable range after insert!!
- //
- var
- TempPage, NextPage: TIndexPage;
- numEntries, numKeysAvail, done, searchRecNo: Integer;
- begin
- // reread index header (to discover whether root page changed)
- if NeedLocks then
- ResyncRoot;
- // if distinct or unique index -> every entry only occurs once ->
- // does not matter which recno we search -> search recno = -2 ->
- // extra info = recno
- if (FUniqueMode = iuNormal) then
- begin
- // if inserting, search last entry matching key
- if Insert then
- searchRecNo := -3
- else
- searchRecNo := FUserRecNo
- end else begin
- searchRecNo := -2;
- end;
- // start from root
- TempPage := FRoot;
- repeat
- // find key
- done := 0;
- Result := TempPage.FindNearest(searchRecNo);
- if TempPage.LowerPage = nil then
- begin
- // if key greater than last, try next leaf
- if (Result > 0) and (searchRecNo > 0) then
- begin
- // find first parent in tree so we can advance to next item
- NextPage := TempPage;
- repeat
- NextPage := NextPage.UpperPage;
- until (NextPage = nil) or (NextPage.EntryNo < NextPage.HighIndex);
- // found page?
- if NextPage <> nil then
- begin
- // go to parent
- TempPage := NextPage;
- TempPage.EntryNo := TempPage.EntryNo + 1;
- // resync rest of tree
- TempPage.LowerPage.RecurFirst;
- // go to lower page to continue search
- TempPage := TempPage.LowerPage;
- // check if still more lowerpages
- if TempPage.LowerPage <> nil then
- begin
- // flag we need to traverse down further
- done := 2;
- end else begin
- // this is next child, we don't know if found
- done := 1;
- end;
- end;
- end;
- end else begin
- // need to traverse lower down
- done := 2;
- end;
- // check if we need to split page
- // done = 1 -> not found entry on insert path yet
- if Insert and (done <> 1) then
- begin
- // now we are on our path to destination where entry is to be inserted
- // check if this page is full, then split it
- numEntries := TempPage.NumEntries;
- // if this is inner node, we can only store one less than max entries
- numKeysAvail := PIndexHdr(FIndexHeader).NumKeys - numEntries;
- if TempPage.LowerPage <> nil then
- dec(numKeysAvail);
- // too few available -> split
- if numKeysAvail = 0 then
- TempPage.Split;
- end;
- // do we need to go lower down?
- if done = 2 then
- TempPage := TempPage.LowerPage;
- until done = 0;
- end;
- function TIndexFile.MatchKey(UserKey: PChar): Integer;
- begin
- // BOF and EOF always false
- if FLeaf.Entry = FEntryBof then
- Result := 1
- else
- if FLeaf.Entry = FEntryEof then
- Result := -1
- else begin
- FUserKey := UserKey;
- Result := FLeaf.MatchKey;
- end;
- end;
- procedure TIndexFile.SetRange(LowRange, HighRange: PChar);
- begin
- Move(LowRange^, FLowBuffer[0], KeyLen);
- Move(HighRange^, FHighBuffer[0], KeyLen);
- FRangeActive := true;
- ResyncRange(true);
- end;
- procedure TIndexFile.RecordDeleted(RecNo: Integer; Buffer: PChar);
- begin
- // are we distinct -> then delete record from index
- FModifyMode := mmDeleteRecall;
- Delete(RecNo, Buffer);
- FModifyMode := mmNormal;
- end;
- procedure TIndexFile.RecordRecalled(RecNo: Integer; Buffer: PChar);
- begin
- // are we distinct -> then reinsert record in index
- FModifyMode := mmDeleteRecall;
- Insert(RecNo, Buffer);
- FModifyMode := mmNormal;
- end;
- procedure TIndexFile.SetLocaleID(const NewID: LCID);
- {$ifdef WIN32}
- var
- InfoStr: array[0..7] of Char;
- {$endif}
- begin
- FLocaleID := NewID;
- if NewID = lcidBinary then
- begin
- // no conversion on binary sort order
- FLocaleCP := FCodePage;
- end else begin
- // get default ansi codepage for comparestring
- {$ifdef WIN32}
- GetLocaleInfo(NewID, LOCALE_IDEFAULTANSICODEPAGE, InfoStr, 8);
- FLocaleCP := StrToIntDef(InfoStr, GetACP);
- {$else}
- FLocaleCP := GetACP;
- {$endif}
- end;
- end;
- procedure TIndexFile.SetPhysicalRecNo(RecNo: Integer);
- begin
- // check if already at specified recno
- if FLeaf.PhysicalRecNo = RecNo then
- exit;
- // check record actually exists
- if TDbfFile(FDbfFile).IsRecordPresent(RecNo) then
- begin
- // read buffer of this RecNo
- TDbfFile(FDbfFile).ReadRecord(RecNo, TDbfFile(FDbfFile).PrevBuffer);
- // extract key
- FUserKey := ExtractKeyFromBuffer(TDbfFile(FDbfFile).PrevBuffer);
- // translate to a search key
- if KeyType = 'C' then
- TranslateToANSI(FUserKey, FUserKey);
- // find this key
- FUserRecNo := RecNo;
- FindKey(false);
- end;
- end;
- procedure TIndexFile.SetUpdateMode(NewMode: TIndexUpdateMode);
- begin
- // if there is only one index, don't waste time and just set single
- if (FIndexVersion = xBaseIII) or (PMdxHdr(Header).TagsUsed <= 1) then
- FUpdateMode := umCurrent
- else
- FUpdateMode := NewMode;
- end;
- procedure TIndexFile.WalkFirst;
- begin
- // search first node
- FRoot.RecurFirst;
- // out of index - BOF
- FLeaf.EntryNo := FLeaf.EntryNo - 1;
- end;
- procedure TIndexFile.WalkLast;
- begin
- // search last node
- FRoot.RecurLast;
- // out of index - EOF
- // we need to skip two entries to go out-of-bound
- FLeaf.EntryNo := FLeaf.EntryNo + 2;
- end;
- procedure TIndexFile.First;
- begin
- // resync tree
- Resync(false);
- WalkFirst;
- end;
- procedure TIndexFile.Last;
- begin
- // resync tree
- Resync(false);
- WalkLast;
- end;
- procedure TIndexFile.ResyncRange(KeepPosition: boolean);
- var
- Result: Boolean;
- currRecNo: integer;
- begin
- if not FRangeActive then
- exit;
- // disable current range if any
- if KeepPosition then
- currRecNo := SequentialRecNo;
- ResetRange;
- // search lower bound
- Result := SearchKey(FLowBuffer, stGreaterEqual);
- if not Result then
- begin
- // not found? -> make empty range
- WalkLast;
- end;
- // set lower bound
- SetBracketLow;
- // search upper bound
- Result := SearchKey(FHighBuffer, stGreater);
- // if result true, then need to get previous item <=>
- // last of equal/lower than key
- if Result then
- begin
- Result := WalkPrev;
- if not Result then
- begin
- // cannot go prev -> empty range
- WalkFirst;
- end;
- end else begin
- // not found -> EOF found, go EOF, then to last record
- WalkLast;
- WalkPrev;
- end;
- // set upper bound
- SetBracketHigh;
- if KeepPosition then
- SequentialRecNo := currRecNo;
- end;
- procedure TIndexFile.Resync(Relative: boolean);
- begin
- if NeedLocks then
- begin
- if not Relative then
- begin
- ResyncRoot;
- ResyncRange(false);
- end else begin
- // resyncing tree implies resyncing range
- ResyncTree;
- end;
- end;
- end;
- procedure TIndexFile.ResyncTree;
- var
- action, recno: integer;
- begin
- // if at BOF or EOF, then we need to resync by first or last
- // remember where the cursor was
- if FLeaf.Entry = FEntryBof then
- begin
- action := 0;
- end else if FLeaf.Entry = FEntryEof then begin
- action := 1;
- end else begin
- // read current key into buffer
- Move(FLeaf.Key^, FKeyBuffer, PIndexHdr(FIndexHeader).KeyLen);
- // translate to searchable key
- if KeyType = 'C' then
- TranslateToANSI(FKeyBuffer, FKeyBuffer);
- recno := FLeaf.PhysicalRecNo;
- action := 2;
- end;
- // we now know cursor position, resync possible range
- ResyncRange(false);
-
- // go to cursor position
- case action of
- 0: WalkFirst;
- 1: WalkLast;
- 2:
- begin
- // search current in-mem key on disk
- if (Find(recno, FKeyBuffer) <> 0) then
- begin
- // houston, we've got a problem!
- // our `current' record has gone. we need to find it
- // find it by using physical recno
- PhysicalRecNo := recno;
- end;
- end;
- end;
- end;
- function TIndexFile.WalkPrev: boolean;
- var
- curRecNo: Integer;
- begin
- // save current recno, find different next!
- curRecNo := FLeaf.PhysicalRecNo;
- repeat
- // return false if we are at first entry
- Result := FLeaf.RecurPrev;
- until not Result or (curRecNo <> FLeaf.PhysicalRecNo);
- end;
- function TIndexFile.WalkNext: boolean;
- var
- curRecNo: Integer;
- begin
- // save current recno, find different prev!
- curRecNo := FLeaf.PhysicalRecNo;
- repeat
- // return false if we are at last entry
- Result := FLeaf.RecurNext;
- until not Result or (curRecNo <> FLeaf.PhysicalRecNo);
- end;
- function TIndexFile.Prev: Boolean;
- begin
- // resync in-mem tree with tree on disk
- Resync(true);
- Result := WalkPrev;
- end;
- function TIndexFile.Next: Boolean;
- begin
- // resync in-mem tree with tree on disk
- Resync(true);
- Result := WalkNext;
- end;
- function TIndexFile.GetKeyLen: Integer;
- begin
- Result := PIndexHdr(FIndexHeader).KeyLen;
- end;
- function TIndexFile.GetKeyType: Char;
- begin
- Result := PIndexHdr(FIndexHeader).KeyType;
- end;
- function TIndexFile.GetPhysicalRecNo: Integer;
- begin
- Result := FLeaf.PhysicalRecNo;
- end;
- function TIndexFile.GetSequentialRecordCount: Integer;
- begin
- Result := FRoot.Weight * (FRoot.HighIndex + 1);
- end;
- function TIndexFile.GetSequentialRecNo: Integer;
- var
- TempPage: TIndexPage;
- begin
- // check if at BOF or EOF, special values
- if FLeaf.EntryNo < FLeaf.LowIndex then begin
- Result := RecBOF;
- end else if FLeaf.EntryNo > FLeaf.HighIndex then begin
- Result := RecEOF;
- end else begin
- // first record is record 1
- Result := 1;
- TempPage := FRoot;
- repeat
- inc(Result, TempPage.EntryNo * TempPage.Weight);
- TempPage := TempPage.LowerPage;
- until TempPage = nil;
- end;
- end;
- procedure TIndexFile.SetSequentialRecNo(RecNo: Integer);
- var
- TempPage: TIndexPage;
- gotoEntry: Integer;
- begin
- // use our weighting system to quickly go to a seq recno
- // recno starts at 1, entries at zero
- Dec(RecNo);
- TempPage := FRoot;
- repeat
- // don't div by zero
- assert(TempPage.Weight > 0);
- gotoEntry := RecNo div TempPage.Weight;
- RecNo := RecNo mod TempPage.Weight;
- // do we have this much entries?
- if (TempPage.HighIndex < gotoEntry) then
- begin
- // goto next entry in upper page if not
- // if recurnext fails, we have come at the end of the index
- if (TempPage.UpperPage <> nil) and TempPage.UpperPage.RecurNext then
- begin
- // lower recno to get because we skipped an entry
- TempPage.EntryNo := TempPage.LowIndex;
- RecNo := 0;
- end else begin
- // this can only happen if too big RecNo was entered, go to last
- TempPage.RecurLast;
- // terminate immediately
- TempPage := FLeaf;
- end;
- end else begin
- TempPage.EntryNo := gotoEntry;
- end;
- // get lower node
- TempPage := TempPage.LowerPage;
- until TempPage = nil;
- end;
- procedure TIndexFile.SetBracketLow;
- var
- TempPage: TIndexPage;
- begin
- // set current record as lower bound
- TempPage := FRoot;
- repeat
- TempPage.LowBracket := TempPage.EntryNo;
- TempPage.LowPage := TempPage.PageNo;
- TempPage := TempPage.LowerPage;
- until TempPage = nil;
- end;
- procedure TIndexFile.SetBracketHigh;
- var
- TempPage: TIndexPage;
- begin
- // set current record as lower bound
- TempPage := FRoot;
- repeat
- TempPage.HighBracket := TempPage.EntryNo;
- TempPage.HighPage := TempPage.PageNo;
- TempPage := TempPage.LowerPage;
- until TempPage = nil;
- end;
- procedure TIndexFile.CancelRange;
- begin
- FRangeActive := false;
- ResetRange;
- end;
- procedure TIndexFile.ResetRange;
- var
- TempPage: TIndexPage;
- begin
- // disable lower + upper bound
- TempPage := FRoot;
- repeat
- // set a page the index should never reach
- TempPage.LowPage := 0;
- TempPage.HighPage := 0;
- TempPage := TempPage.LowerPage;
- until TempPage = nil;
- end;
- function MemComp(P1, P2: Pointer; const Length: Integer): Integer;
- var
- I: Integer;
- begin
- for I := 0 to Length - 1 do
- begin
- // still equal?
- if PByte(P1)^ <> PByte(P2)^ then
- begin
- Result := Integer(PByte(P1)^) - Integer(PByte(P2)^);
- exit;
- end;
- // go to next byte
- Inc(PChar(P1));
- Inc(PChar(P2));
- end;
- // memory equal
- Result := 0;
- end;
- function TIndexFile.CompareKeys(Key1, Key2: PChar): Integer;
- begin
- // call compare routine
- Result := FCompareKeys(Key1, Key2);
- // if descending then reverse order
- if FIsDescending then
- Result := -Result;
- end;
- function TIndexFile.CompareKeysNumericNDX(Key1, Key2: PChar): Integer;
- var
- v1,v2: Double;
- begin
- v1 := PDouble(Key1)^;
- v2 := PDouble(Key2)^;
- if v1 > v2 then Result := 1
- else if v1 < v2 then Result := -1
- else Result := 0;
- end;
- function TIndexFile.CompareKeysNumericMDX(Key1, Key2: PChar): Integer;
- var
- neg1, neg2: Boolean;
- begin
- // first byte - $34 contains dot position
- neg1 := (Byte(Key1[1]) and $80) <> 0;
- neg2 := (Byte(Key2[1]) and $80) <> 0;
- // check if both negative or both positive
- if neg1 = neg2 then
- begin
- // check alignment
- if Key1[0] = Key2[0] then
- begin
- // no alignment needed -> have same alignment
- Result := MemComp(Key1+2, Key2+2, 10-2);
- end else begin
- // greater 10-power implies bigger number except for zero
- if (Byte(Key1[0]) = $01) and (Byte(Key1[1]) = $34) then
- Result := -1
- else
- if (Byte(Key2[0]) = $01) and (Byte(Key2[1]) = $34) then
- Result := 1
- else
- Result := Byte(Key1[0]) - Byte(Key2[0]);
- end;
- // negate result if both negative
- if neg1 and neg2 then
- Result := -Result;
- end else if neg1 {-> not neg2} then
- Result := -1
- else { not neg1 and neg2 }
- Result := 1;
- end;
- function TIndexFile.CompareKeysString(Key1, Key2: PChar): Integer;
- var
- Key1T, Key2T: array [0..100] of Char;
- FromCP, ToCP: Integer;
- begin
- if FLocaleID = LCID(lcidBinary) then
- begin
- Result := StrLComp(Key1, Key2, KeyLen)
- end else begin
- FromCP := FCodePage;
- ToCP := FLocaleCP;
- TranslateString(FromCP, ToCP, Key1, Key1T, KeyLen);
- TranslateString(FromCP, ToCP, Key2, Key2T, KeyLen);
- Result := CompareString(FLocaleID, 0, Key1T, KeyLen, Key2T, KeyLen);
- if Result > 0 then
- Dec(Result, 2);
- end
- end;
- function TIndexFile.CompareKey(Key: PChar): Integer;
- begin
- // call compare routine
- Result := FCompareKey(Key);
- // if descending then reverse order
- if FIsDescending then
- Result := -Result;
- end;
- function TIndexFile.CompareKeyNumericNDX(Key: PChar): Integer;
- begin
- Result := CompareKeysNumericNDX(FUserKey, Key);
- end;
- function TIndexFile.CompareKeyNumericMDX(Key: PChar): Integer;
- begin
- Result := CompareKeysNumericMDX(FUserKey, Key);
- end;
- procedure TIndexFile.TranslateToANSI(Src, Dest: PChar);
- begin
- { FromCP = FCodePage; }
- { ToCP = FLocaleCP; }
- TranslateString(FCodePage, FLocaleCP, Src, Dest, KeyLen);
- end;
- function TIndexFile.CompareKeyString(Key: PChar): Integer;
- var
- KeyT: array [0..100] of Char;
- begin
- if FLocaleID = LCID(lcidBinary) then
- begin
- Result := StrLComp(FUserKey, Key, KeyLen)
- end else begin
- TranslateToANSI(Key, KeyT);
- Result := CompareString(FLocaleID, 0, FUserKey, KeyLen, KeyT, KeyLen);
- if Result > 0 then
- Dec(Result, 2);
- end
- end;
- function TIndexFile.IndexOf(const AIndexName: string): Integer;
- // *) assumes FIndexVersion >= xBaseIV
- var
- I: Integer;
- begin
- // get index of this index :-)
- Result := -1;
- I := 0;
- while (I < PMdxHdr(Header).TagsUsed) and (Result < 0) do
- begin
- FTempMdxTag.Tag := CalcTagOffset(I);
- if AnsiCompareText(AIndexName, FTempMdxTag.TagName) = 0 then
- Result := I;
- inc(I);
- end;
- end;
- procedure TIndexFile.SetIndexName(const AIndexName: string);
- var
- found: Integer;
- begin
- // we can only select a different index if we are MDX
- if FIndexVersion >= xBaseIV then
- begin
- // find index
- found := IndexOf(AIndexName);
- end else
- found := 0;
- // we can now select by index
- if found >= 0 then
- SelectIndexVars(found);
- end;
- function TIndexFile.CalcTagOffset(AIndex: Integer): Pointer;
- begin
- Result := PChar(Header) + FTagOffset + AIndex * FTagSize;
- end;
- procedure TIndexFile.SelectIndexVars(AIndex: Integer);
- // *) assumes index is in range
- begin
- if AIndex >= 0 then
- begin
- // get pointer to index header
- FIndexHeader := FIndexHeaders[AIndex];
- // load root + leaf
- FCurrentParser := FParsers[AIndex];
- FRoot := FRoots[AIndex];
- FLeaf := FLeaves[AIndex];
- // if xBaseIV then we need to store where pageno of current header
- if FIndexVersion >= xBaseIV then
- begin
- FMdxTag.Tag := CalcTagOffset(AIndex);
- FIndexName := FMdxTag.TagName;
- FHeaderPageNo := FMdxTag.HeaderPageNo;
- // does dBase actually use this flag?
- // FIsExpression := FMdxTag.KeyFormat = KeyFormat_Expression;
- end else begin
- // how does dBase III store whether it is expression?
- // FIsExpression := true;
- end;
- // retrieve properties
- UpdateIndexProperties;
- end else begin
- // not a valid index
- FIndexName := EmptyStr;
- end;
- // store selected index
- FSelectedIndex := AIndex;
- FCanEdit := not FForceReadOnly;
- end;
- procedure TIndexFile.UpdateIndexProperties;
- begin
- // get properties
- FIsDescending := (PIndexHdr(FIndexHeader).KeyFormat and KeyFormat_Descending) <> 0;
- FUniqueMode := iuNormal;
- if (PIndexHdr(FIndexHeader).KeyFormat and KeyFormat_Unique) <> 0 then
- FUniqueMode := iuUnique;
- if (PIndexHdr(FIndexHeader).KeyFormat and KeyFormat_Distinct) <> 0 then
- FUniqueMode := iuDistinct;
- // select key compare routine
- if PIndexHdr(FIndexHeader).KeyType = 'C' then
- begin
- FCompareKeys := CompareKeysString;
- FCompareKey := CompareKeyString;
- end else
- if FIndexVersion >= xBaseIV then
- begin
- FCompareKeys := CompareKeysNumericMDX;
- FCompareKey := CompareKeyNumericMDX;
- end else begin
- FCompareKeys := CompareKeysNumericNDX;
- FCompareKey := CompareKeyNumericNDX;
- end;
- end;
- procedure TIndexFile.Flush;
- var
- I: Integer;
- begin
- // save changes to pages
- if FIndexVersion >= xBaseIV then
- begin
- for I := 0 to MaxIndexes - 1 do
- begin
- if FHeaderModified[I] then
- WriteIndexHeader(I);
- if FRoots[I] <> nil then
- FRoots[I].Flush
- end;
- end else begin
- if FRoot <> nil then
- FRoot.Flush;
- end;
- // save changes to header
- FlushHeader;
- inherited;
- end;
- (*
- function TIndexFile.GetIndexCount: Integer;
- begin
- if FIndexVersion = xBaseIII then
- Result := 1
- else
- if FIndexVersion = xBaseIV then
- Result := PMdxHdr(Header).TagsUsed;
- else
- Result := 0;
- end;
- *)
- procedure TIndexFile.GetIndexNames(const AList: TStrings);
- var
- I: Integer;
- begin
- // only applicable to MDX files
- if FIndexVersion >= xBaseIV then
- begin
- for I := 0 to PMdxHdr(Header).TagsUsed - 1 do
- begin
- FTempMdxTag.Tag := CalcTagOffset(I);
- AList.AddObject(FTempMdxTag.TagName, Self);
- end;
- end;
- end;
- procedure TIndexFile.GetIndexInfo(const AIndexName: string; IndexDef: TDbfIndexDef);
- var
- SaveIndexName: string;
- begin
- // remember current index
- SaveIndexName := IndexName;
- // select index
- IndexName := AIndexName;
- // copy properties
- IndexDef.IndexFile := AIndexName;
- IndexDef.Expression := PIndexHdr(FIndexHeader).KeyDesc;
- IndexDef.Options := [];
- IndexDef.Temporary := true;
- if FIsDescending then
- IndexDef.Options := IndexDef.Options + [ixDescending];
- IndexDef.Options := IndexDef.Options + [ixExpression];
- case FUniqueMode of
- iuUnique: IndexDef.Options := IndexDef.Options + [ixUnique];
- iuDistinct: IndexDef.Options := IndexDef.Options + [ixPrimary];
- end;
- // reselect previous index
- IndexName := SaveIndexName;
- end;
- function TIndexFile.GetExpression: string;
- begin
- if FCurrentParser <> nil then
- Result := FCurrentParser.Expression
- else
- Result := EmptyStr;
- end;
- function TIndexFile.GetDbfLanguageId: Byte;
- begin
- // check if parent DBF version 7, get language id
- if (TDbfFile(FDbfFile).DbfVersion = xBaseVII) then
- begin
- // get language id of parent dbf
- Result := GetLangId_From_LangName(TDbfFile(FDbfFile).LanguageStr);
- end else begin
- // dBase IV has language id in header
- Result := TDbfFile(FDbfFile).LanguageID;
- end;
- end;
- procedure TIndexFile.WriteHeader; {override;}
- begin
- // if NDX, then this means file header
- if FIndexVersion >= xBaseIV then
- if NeedLocks then
- WriteIndexHeader(FSelectedIndex)
- else
- FHeaderModified[FSelectedIndex] := true
- else
- WriteFileHeader;
- end;
- procedure TIndexFile.WriteFileHeader;
- begin
- inherited WriteHeader;
- end;
- procedure TIndexFile.WriteIndexHeader(AIndex: Integer);
- begin
- FTempMdxTag.Tag := CalcTagOffset(AIndex);
- WriteRecord(FTempMdxTag.HeaderPageNo, FIndexHeaders[AIndex]);
- FHeaderModified[AIndex] := false;
- end;
- //==========================================================
- //============ TDbfIndexDef
- //==========================================================
- constructor TDbfIndexDef.Create(Collection: TCollection); {override;}
- begin
- inherited Create(Collection);
- FTemporary := false;
- end;
- destructor TDbfIndexDef.Destroy; {override;}
- begin
- inherited Destroy;
- end;
- procedure TDbfIndexDef.Assign(Source: TPersistent);
- begin
- // we can't do anything with it if not a TDbfIndexDef
- if Source is TDbfIndexDef then
- begin
- FIndexName := TDbfIndexDef(Source).IndexFile;
- FExpression := TDbfIndexDef(Source).Expression;
- FOptions := TDbfIndexDef(Source).Options;
- end else
- inherited;
- end;
- procedure TDbfIndexDef.SetIndexName(NewName: string);
- begin
- FIndexName := AnsiUpperCase(Trim(NewName));
- end;
- procedure TDbfIndexDef.SetExpression(NewField: string);
- begin
- FExpression := AnsiUpperCase(Trim(NewField));
- end;
- initialization
- {
- Entry_Mdx_BOF.RecBlockNo := RecBOF;
- Entry_Mdx_BOF.KeyData := #0;
- Entry_Mdx_EOF.RecBlockNo := RecEOF;
- Entry_Mdx_EOF.KeyData := #0;
- Entry_Ndx_BOF.LowerPageNo := 0;
- Entry_Ndx_BOF.RecNo := RecBOF;
- Entry_Ndx_BOF.KeyData := #0;
- Entry_Ndx_EOF.LowerPageNo := 0;
- Entry_Ndx_EOF.RecNo := RecEOF;
- Entry_Ndx_EOF.KeyData := #0;
- }
- LCIDList := TLCIDList.Create;
- LCIDList.Enumerate;
- finalization
- LCIDList.Free;
- end.
|