home *** CD-ROM | disk | FTP | other *** search
- (* TBTree16 Copyright (c) 1988,1989 Dean H. Farwell II *)
-
- unit Logical;
-
- (*****************************************************************************)
- (* *)
- (* L O G I C A L R E C O R D R O U T I N E S *)
- (* *)
- (*****************************************************************************)
-
-
- (* This unit is used to manipulate data files and the logical records (data
- records) which comprise the data files. The terms logical record and data
- record are used interchangeably. A logical record is made of a fixed
- number of bytes which is set at file creation time. The minimum logical
- record size is one byte. The maximum size is MAXDATASIZE (65520 bytes).
- Depending on the size of logical and physical records, many logical records
- could reside in a single physical record or a logical record could use many
- physical records. In the later case contiguous physical records are used.
- Since fixed logical and physical record sizes are used the physical records
- used for a logical record can easily be calculated.
-
- In version 1.4 this unit was totaly redesigned and took on the added task
- of dealing with the data files. This unit is used to create and delete
- data files and is also used to put data into the data files and retrieve
- data from the data files. Deleting data from the data files is also
- accomplished using this routine.
-
- Each data file maintains its own bitmap records. These bitmap records are
- used to determine which records are currentlyu in use and which records are
- available for reuse. You should never need to deal with the bitmap records
- because everything is handled automatically.
-
- Note - You should never use any of these routines for any files other than
- data files. *)
-
-
- (*\*)
- (* Version Information
-
- Version 1.1 - Added GetValidLogicalRecords routine
-
- Version 1.2 - Removed several types which were not used
-
- - Removed CalculateRecordSize routine
- This routine was not used and was for future enhancements
-
- Version 1.3 - Changed MAXDATASIZE to 65520 which is the maximum
- number of bytes for a structued type in Turbo Pascal 4.0
-
- - Added StoreNewLogicalRecord routine
-
- Version 1.4 - Fixed a bug in GetALogicalRecord routine. Bug affected
- retrievals for logical records with size > 512 bytes
-
- - Changed (expanded) definition of DataSizeRange. See note in
- interface section for details
-
- - Made many internal changes to the routine. The most
- important is that the bitmap records are now maintained as
- part of the data files. Individual bitmap files are no
- longer needed.
-
- - Added CreateDataFile routine which must be used to create
- data files
-
- - Added DeleteDataFile routine which must be used to delete
- data files
-
- - Moved the RecordUsed routine from the FILES unit and renamed
- it DataRecordUsed
-
- - The logical record size is not needed when data is stored and
- retrieved. It is now needed only when the data file is
- created.
-
- - Added DeleteDataFile routine. This routine should always be
- used when deleting a DATA file.
-
- - Added DeleteDataRecord routine which must be used for
- deleting data records.
-
- Version 1.5 - Fixed a couple of problems which caused fatal errors when
- several of the routines were called with lrNum = 0. Remember,
- zero is not a valid logical record number. The first valid
- logical record number is 1. See the comments associated
- with the specific routines DataRecordUsed, DeleteDataRecord,
- GetALogicalRecord and StoreALogicalRecord.
-
- - Changed code internally to use Inc and Dec where practical
-
- - Changed code internally to use newly added FastMove unit
-
- - Added LastDataRecord routine
-
- - Changed the way records are added as the index grows on
- inserts. As the file becomes larger, the number of records
- added at one time increases. This will speed up the insert
- process.
-
- Version 1.6 - No Changes *)
-
-
- (*\*)
- (*////////////////////////// I N T E R F A C E //////////////////////////////*)
-
- interface
-
- uses
- Compare,
- FastMove,
- FileDecs,
- Files,
- LRecList,
- Math,
- Numbers,
- Page;
-
- const
- MAXDATASIZE = 65520; (* max number of bytes for a logical record *)
-
- type
- DataSizeRange = 0 .. MAXDATASIZE; (* range for size of a field.
- Also maximum number of
- bytes in a logical record *)
-
- (* Note : in version 1.4 the
- definition has changed to include
- 0 in the valid range. This is
- done to allow a variable of this
- type to be initialized to 0 before
- a size is actually assigned. This
- adds flexibility with no loss of
- functionality *)
-
- (*\*)
- (* This routine will create a data file with the file name as specified
- by dFName. The lrSize parameter specifies the size of the data
- record. The easiest way to determine this is to use the SizeOf
- function. *)
-
- procedure CreateDataFile(dFName : FnString;
- lrSize : DataSizeRange);
-
-
- (* This routine will delete a data file. *)
-
- procedure DeleteDataFile(dFName : FnString);
-
-
- (* This routine will check for the existence of a particular data record. If
- the data record is in use, TRUE will be returned. Otherwise, FALSE will be
- returned. If this routine is called with lrNum = 0 then FALSE will be
- returned since the zeroth logical record is never a valid logical record. *)
-
- function DataRecordUsed(dFName : FnString;
- lrNum : LrNumber) : Boolean;
-
-
- (* This routine will delete a logical record from the data file by setting the
- appropriate bitmap bit to zero. If the data record (lrNum) is not in use,
- then nothing will happen. No error will occur. *)
-
- procedure DeleteDataRecord(dFName : FnString;
- lrNum : lrNumber);
-
-
- (* This routine will get a logical record from a given data file and will put
- the record into a memory location. The location will be destination. The
- number of bytes retrieved is equal to the size of the logical record which
- was set when the file was created. The BITMAP will be checked to ensure
- that the record is in use (that it exists). If it is in use then it is
- fetched. Otherwise, nothing will be returned in destination. Before
- calling this routine, you can check to see if the logical record exists.
- If it was retrieved from an index then it exists (unless the record was
- deleted and it wasn't deleted from the index). Also, record numbers which
- are stored in a logical record list as a result of GetValidLogicalRecords
- also exist as long as records were not deleted after the list was created.
- If you are not sure whether a logical record exists you can use
- DataRecordUsed(dFName,lrNum) to check for the existence of the record
- before calling this routine.
-
- Warning : If this routine is called with lrNum = 0 no error will occur, but
- nothing will be passed back in destination (destination will remain
- unchanged) You should ensure that lrNum is not equal to zero prior to
- calling this routine. *)
-
- procedure GetALogicalRecord(dFName : FnString;
- lrNum : LrNumber;
- var destination);
-
- (*\*)
- (* This routine will store a logical record for a given DATA file. The
- routine will set the logical record to used and will create the appropriate
- physical record(s) if required. The data must reside in source. This
- routine is only used if the logical record number is known. If a new
- record is to be stored use StoreNewLogicalRecord rather than this routine
-
- Warning : If this routine is called with lrNum = 0 no error will occur, but
- nothing will be saved. You should ensure that lrNum is not equal to zero
- prior to calling this routine. *)
-
- procedure StoreALogicalRecord(dFName : FnString;
- lrNum : LrNumber;
- var source);
-
-
- (* This routine will store a new logical record for a given logical (data)
- file. The routine will set the logical record to used and will create the
- appropriate physical record(s) if required. Normally, when inserting new
- records, you will not know the next unused logical record number. This
- routine will assign the appropriate logical record number so that you won't
- have to worry about it. The routine will return the logical record number
- which will be associated with this record upon return. You will need this
- returned logical record number if there are any indexes associated with
- this data file. *)
-
- function StoreNewLogicalRecord(dFName : FnString;
- var source) : LrNumber;
-
-
- (* This routine will return a list of logical records which are currently in
- use (contain valid data) for a given data file. This routine is necessary
- to be able to process all records which have not been deleted without
- using an index. *)
-
- procedure GetValidLogicalRecords(dFName : FnString;
- var lrLst : LrList);
-
-
- (* This routine will return the data record size for the given data file *)
-
- function GetDataRecordSize(dFName : FnString) : DataSizeRange;
-
-
- (* This routine will return the logical record number for the last logical
- record in use in the file (logical record with the highest logical record
- number). *)
-
- function LastDataRecord(dFName : FnString) : LrNumber;
-
- (*!*)
- (*\*)
- (*///////////////////// I M P L E M E N T A T I O N /////////////////////////*)
-
- implementation
-
-
- (* These parameters are contained in the first record (0) in the data file
-
- variable parameter type range
- -------- --------- ---- -----
- userData user data array UserDataArray N/A
- version version info VersionString N/A
- nextAvail next available lr LrNumber 0 - MAXLONGINT
- firstBMRec first bitmap record PrNumber 0 - MAXLONGINT
- lastBMRec last bitmap record PrNumber 0 - MAXLONGINT
- fType file type FileTypes INDEX,DATA,
- LLIST,VLRDATA
- lrSize logical record size DataSizeRange 0 - 65520
- bf blocking factor DataSizeRange 0 - 65520
- prReqd Pr's per Lr DataSizeRange 0 - 65520
- lastInUse last lr in use LrNumber 0 - MAXLONGINT *)
-
-
- type
- ParameterRecord = record
- userData : UserDataArray; (* for use by users *)
- version : VersionString; (* version of TBTREE used
- to create data file *)
- nextAvail : LrNumber; (* next logical record available *)
- firstBMRec : PrNumber; (* first record used for bitmap *)
- lastBMRec : PrNumber; (* last record used for bitmap *)
- fType : FileTypes; (* type of file *)
- lrSize : DataSizeRange; (* size of logical records *)
- bf : DataSizeRange; (* blocking factor *)
- prReqd : DataSizeRange; (* Pr's per Lr *)
- lastInUse : LrNumber; (* Last data record in use (not
- last record in file *)
- end;
-
- (*\*)
- (* This routine will create a data file with the file name as specified
- by dFName. The lrSize parameter specifies the size of the data
- record. The easiest way to determine this is to use the SizeOf
- function. *)
-
- procedure CreateDataFile(dFName : FnString;
- lrSize : DataSizeRange);
-
- var
- pRec : ParameterRecord;
- page : SinglePage;
-
- begin
- CreateGenericFile(dFName);
- FillChar(page,PAGESIZE,0);
- StorePage(dFName,0,page); (* parameter record *)
- StorePage(dFName,1,page); (* bitmap record *)
- pRec.version := CURRENTVERSION;
- pRec.nextAvail := 1;
- pRec.firstBMRec := 1;
- pRec.lastBMRec := 1;
- pRec.fType := DATA;
- pRec.lrSize := lrSize;
- if lrSize < PAGESIZE then
- begin
- pRec.bf := PAGESIZE Div lrSize;
- pRec.prReqd := 1;
- end
- else
- begin
- pRec.bf := ((lrSize - 1) Div PAGESIZE) + 1;
- pRec.prReqd := pRec.bf;
- end;
- pRec.lastInUse := 0;
- SaveFileParameters(dFName,pRec,SizeOf(pRec)); (* write parameters back to buffer *)
- end; (* end of CreateDataFile routine *)
-
-
- (* This routine will delete a data file. *)
-
- procedure DeleteDataFile(dFName : FnString);
-
- begin
- DeleteGenericFile(dFName);
- end; (* end of DeleteDataFile routine *)
-
- (*\*)
- (* This routine will calculate the physical record number and the byte location
- within the physical record for a given logical record. The logical record
- number is passed as a parameter. *)
-
- procedure ConvertLogicalToPhysical(lrNum : LrNumber;
- var pRec : ParameterRecord;
- (* var for speed only *)
- var prNum : PrNumber;
- var firstByte : PageRange);
-
- begin
- if pRec.lrSize < PAGESIZE then
- begin
- prNum := ((lrNum - 1) Div pRec.bf) + 1;
- firstByte := ((lrNum - ((pRec.bf * (prNum - 1)) + 1)) * pRec.lrSize)
- + 1;
- end
- else
- begin
- prNum := ((lrNum - 1) * pRec.bf) + 1;
- firstByte := 1;
- end;
- end; (* end of ConvertLogicalToPhysical routine *)
-
- (*\*)
- (* This routine will return the record number for the first unused data record
- (logical record). The routine checks to see if the bitmap records must be
- moved to make room. The bitmap records will moved down to free up disk
- space. The number of physical pages freed up depends on the size of the
- data file. It also resets the firstBMRec field in the parameter record to
- the next available record. *)
-
- function FirstUnusedDataRecord(var dFName : FnString;
- (* var for speed only *)
- var pRec : ParameterRecord) : LrNumber;
-
- var
- prNum : PrNumber;
- dummy : PageRange; (* needed as dummy parameter to a call *)
- newRecord : LrNumber; (* record number to be returned *)
- recsToMove : PrNumber;
-
- begin
- newRecord := pRec.nextAvail; (* record number to return *)
- pRec.nextAvail := FindNextAvailInBitmap(dFName,pRec.firstBMRec,
- pRec.lastBMRec,newRecord);
- ConvertLogicalToPhysical(newRecord,pRec,prNum,dummy);
- (* above call sets prNum to first pr for the
- new record *)
- if (prNum + (pRec.prReqd - 1)) >= pRec.firstBMRec then
- begin (* need to move bitmap records *)
- if pRec.firstBMRec <= 4 then
- begin
- recsToMove := pRec.prReqd;
- end
- else
- begin
- if pRec.firstBMRec <= 10 then
- begin
- recsToMove := pRec.prReqd * 3;
- end
- else
- begin
- recsToMove := pRec.prReqd * 5;
- end;
- end;
- MoveRecords(dFName,pRec.firstBMRec,pRec.lastBMRec,recsToMove);
- end;
- FirstUnUsedDataRecord := newRecord; (* record number to return *)
- end; (* end of FirstUnusedDataRecord routine *)
-
- (*\*)
- (* This routine will check for the existence of a particular data record. If
- the data record is in use, TRUE will be returned. Otherwise, FALSE will be
- returned. This routine checks to ensure that lrNum is not zero. If this
- is the case, FALSE will be returned, since the zeroth logical record never
- exists. *)
-
- function DataRecordUsedInternal(var dFName : FnString; (* var for speed only *)
- lrNum : LrNumber;
- var pRec : ParameterRecord) : Boolean;
- (* var for speed only *)
-
- begin
- if (lrNum = 0) or (lrNum > pRec.lastInUse) then
- begin
- DataRecordUsedInternal := FALSE;
- end
- else
- begin
- DataRecordUsedInternal := CheckBitInBitmap(dFName,
- pRec.firstBMRec,
- lrNum);
- end;
- end; (* end of DataRecordUsedInternal routine *)
-
-
- (* This routine will check for the existence of a particular data record. If
- the data record is in use, TRUE will be returned. Otherwise, FALSE will be
- returned. If this routine is called with lrNum = 0 then FALSE will be
- returned since the zeroth logical record is never a valid logical record. *)
-
- function DataRecordUsed(dFName : FnString;
- lrNum : LrNumber) : Boolean;
-
- var
- pRec : ParameterRecord;
-
- begin
- FetchFileParameters(dFName,pRec,SizeOf(pRec));
- DataRecordUsed := DataRecordUsedInternal(dFName,lrNum,pRec);
- end; (* end of DataRecordUsedInternal routine *)
-
- (*\*)
- (* This routine will mark the specified record as used. *)
-
- procedure SetDataRecordUsed(var dFName : FnString; (* var for speed only *)
- lrNum : LrNumber;
- var pRec : ParameterRecord (* var for speed only *)
- );
-
- var
- page : SinglePage;
- recCnt,
- prNum : PrNumber;
- byteNumDummy : PageRange; (* needed as dummy parameter to a call *)
- bitNumDummy : BytePosition; (* needed as dummy parameter to a call *)
-
- begin
- prNum := CalculateBitmapRecord(pRec.firstBMRec,lrNum);
- if prNum > pRec.lastBMRec then
- begin (* bitmap record does not exist *)
- FillChar(page,PAGESIZE,0);
- for recCnt := (pRec.lastBMRec + 1) to prNum do
- begin
- StorePage(dFName,prNum,page); (* store empty bitmap record *)
- end;
- pRec.lastBMRec := prNum;
- end;
- SetBitInBitmap(dFName,pRec.firstBMRec,lrNum,1);
- end; (* end of SetDataRecordUsed routine *)
-
- (*\*)
- (* This routine will delete a logical record from the data file by setting the
- appropriate bitmap bit to zero. If the data record (lrNum) is not in use,
- then nothing will happen. No error will occur. *)
-
- procedure DeleteDataRecord(dFName : FnString;
- lrNum : lrNumber);
-
- var
- pRec : ParameterRecord;
- page : SinglePage;
-
- begin
- FetchFileParameters(dFName,pRec,SizeOf(pRec));
- if DataRecordUsedInternal(dFName,lrNum,pRec) then
- begin
- SetBitInBitmap(dFName,pRec.firstBMRec,lrNum,0); (* mark as unused *)
- if lrNum < pRec.nextAvail then
- begin
- pRec.nextAvail := lrNum;
- end;
- if lrNum = pRec.lastInUse then
- begin
- {$B-} (* next statement depends on short circuit
- boolean expression evaluation *)
- while (pRec.lastInUse <> 0) and
- (not DataRecordUsedInternal(dFName,pRec.lastInUse,pRec)) do
- begin
- Dec(pRec.lastInUse);
- end;
- end;
- SaveFileParameters(dFName,pRec,SizeOf(pRec));
- end;
- end; (* end of DeleteDataRecord routine *)
-
- (*\*)
- (* This routine will get a logical record from a given data file and will put
- the record into a memory location. The location will be destination. The
- number of bytes retrieved is equal to the size of the logical record which
- was set when the file was created. The BITMAP will be checked to ensure
- that the record is in use (that it exists). If it is in use then it is
- fetched. Otherwise, nothing will be returned in destination. Before
- calling this routine, you can check to see if the logical record exists.
- If it was retrieved from an index then it exists (unless the record was
- deleted and it wasn't deleted from the index). Also, record numbers which
- are stored in a logical record list as a result of GetValidLogicalRecords
- also exist as long as records were not deleted after the list was created.
- If you are not sure whether a logical record exists you can use
- DataRecordUsed(dFName,lrNum) to check for the existence of the record
- before calling this routine.
-
- Warning : If this routine is called with lrNum = 0 no error will occur, but
- nothing will be passed back in destination (destination will remain
- unchanged) You should ensure that lrNum is not equal to zero prior to
- calling this routine. *)
-
- procedure GetALogicalRecord(dFName : FnString;
- lrNum : LrNumber;
- var destination);
-
- type
- MemoryArray = Array [1 .. MAXDATASIZE] of Byte;
-
- var
- pRec : ParameterRecord;
- prNum : PrNumber;
- firstByte : PageRange;
- page : SinglePage;
- byteCnt : DataSizeRange;
- memory : MemoryArray absolute destination;
- done : Boolean;
- size : DataSizeRange;
-
- begin
- FetchFileParameters(dFName,pRec,SizeOf(pRec));
- if DataRecordUsedInternal(dFName,lrNum,pRec) then (* check to ensure
- record is in
- use and that
- lrNum <> 0 *)
- begin
- ConvertLogicalToPhysical(lrNum,pRec,prNum,firstByte);
- size := pRec.lrSize;
- if size <= PAGESIZE then
- begin
- FetchPage(dFName,prNum,page);
- FastMover(page[firstByte],destination,size);
- end
- else
- begin
- byteCnt := 1;
- done := FALSE;
- while not done do
- begin
- FetchPage(dFName,prNum,page);
- if size > PAGESIZE then
- begin
- FastMover(page[firstByte],memory[byteCnt],PAGESIZE);
- Inc(prNum);
- Inc(byteCnt,PAGESIZE);
- Dec(size,PAGESIZE);
- end
- else
- begin (* last time thru loop *)
- FastMover(page[firstByte],memory[byteCnt],size);
- done := TRUE;
- end;
- end;
- end;
- end;
- end; (* end of GetALogicalRecord routine *)
-
- (*\*)
- (* This routine will store a logical record for a given DATA file. The
- routine will set the logical record to used and will create the appropriate
- physical record(s) if required. The data must reside in source. This
- routine is only used if the logical record number is known. If a new
- record is to be stored use StoreNewLogicalRecord rather than this routine
-
- Warning : If this routine is called with lrNum = 0 no error will occur, but
- nothing will be saved. You should ensure that lrNum is not equal to zero
- prior to calling this routine. *)
-
- procedure StoreALogicalRecord(dFName : FnString;
- lrNum : LrNumber;
- var source);
-
- type
- MemoryArray = Array [1 .. MAXDATASIZE] of Byte;
-
- var
- pRec : ParameterRecord;
- prNum : PrNumber;
- firstByte : PageRange;
- page : SinglePage;
- byteCnt : DataSizeRange;
- memory : MemoryArray absolute source;
- done : Boolean;
- size : DataSizeRange;
-
- begin
- if lrNum <> 0 then (* make sure that lrNum <> 0 else do nothing *)
- begin
- FetchFileParameters(dFName,pRec,SizeOf(pRec));
- ConvertLogicalToPhysical(lrNum,pRec,prNum,firstByte);
- SetDataRecordUsed(dFName,lrNum,pRec); (* It may already be used.
- This doesn't matter and
- its faster not to check *)
- size := pRec.lrSize;
- if size <= PAGESIZE then
- begin
- if PageExists(dFName,prNum) then (* if it exists get it *)
- begin
- FetchPage(dFName,prNum,page);
- end
- else (* if it doesn't exist make a new one *)
- begin
- FillChar(page,PAGESIZE,0);
- end;
- FastMover(source,page[firstByte],size);
- StorePage(dFName,prNum,page);
- end
- else
- begin
- byteCnt := 1;
- done := FALSE;
- while not done do
- begin
- if size > PAGESIZE then
- begin
- FastMover(memory[byteCnt],page[firstByte],PAGESIZE);
- StorePage(dFName,prNum,page);
- Inc(prNum);
- Inc(byteCnt,PAGESIZE);
- Dec(size,PAGESIZE);
- end
- else
- begin (* last time thru loop *)
- FillChar(page,PAGESIZE,0);
- FastMover(memory[byteCnt],page[firstByte],size);
- StorePage(dFName,prNum,page);
- done := TRUE;
- end;
- end;
- end;
- if pRec.lastInUse < lrNum then
- begin
- pRec.lastInUse := lrNum;
- SaveFileParameters(dFName,pRec,SizeOf(pRec));
- end;
- end;
- end; (* end of StoreALogicalRecord routine *)
-
- (*\*)
- (* This routine will store a new logical record for a given logical (data)
- file. The routine will set the logical record to used and will create the
- appropriate physical record(s) if required. Normally, when inserting new
- records, you will not know the next unused logical record number. This
- routine will assign the appropriate logical record number so that you won't
- have to worry about it. The routine will return the logical record number
- which will be associated with this record upon return. You will need this
- returned logical record number if there are any indexes associated with
- this data file. *)
-
- function StoreNewLogicalRecord(dFName : FnString;
- var source) : LrNumber;
-
- var
- pRec : ParameterRecord;
- lrNum : LrNumber;
-
- begin
- FetchFileParameters(dFName,pRec,SizeOf(pRec));
- lrNum := FirstUnUsedDataRecord(dFName,pRec);
- SaveFileParameters(dFName,pRec,SizeOf(pRec));
- StoreALogicalRecord(dFName,lrNum,source);
- StoreNewLogicalRecord := lrNum;
- end; (* end of StoreNewLogicalRecord routine *)
-
-
- (* This routine will return a list of logical records which are currently in
- use (contain valid data) for a given data file. This routine is necessary
- to be able to process all records which have not been deleted without
- using an index. *)
-
- procedure GetValidLogicalRecords(dFName : FnString;
- var lrLst : LrList);
-
- var
- pRec : ParameterRecord;
- lrNum : LrNumber;
-
- begin
- FetchFileParameters(dFName,pRec,SizeOf(pRec));
- CreateLrList(lrLst);
- for lrNum := 1 to pRec.lastInUse do (* will do nothing if file empty
- because if file is empty, then
- pRec.lastInUse = 0 *)
- begin
- if DataRecordUsedInternal(dFName,lrNum,pRec) then
- begin
- AddToLrList(lrNum,lrLst);
- end;
- end;
- end; (* end of GetValidLogicalRecords routine *)
-
- (*\*)
- (* This routine will return the data record size for the given data file *)
-
- function GetDataRecordSize(dFName : FnString) : DataSizeRange;
-
- var
- pRec : ParameterRecord;
-
- begin
- FetchFileParameters(dFName,pRec,SizeOf(pRec));
- GetDataRecordSize := pRec.lrSize;
- end; (* end of GetDataRecordSize routine *)
-
-
- (* This routine will return the logical record number for the last logical
- record in use in the file (logical record with the highest logical record
- number). *)
-
- function LastDataRecord(dFName : FnString) : LrNumber;
-
- var
- pRec : ParameterRecord;
-
- begin
- FetchFileParameters(dFName,pRec,SizeOf(pRec));
- LastDataRecord := pRec.lastInUse;
- end; (* end of LastDataRecord routine *)
-
-
- end. (* end of Logical unit *)