home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / pascal / library / dos / btree / tree / btree.pas next >
Encoding:
Pascal/Delphi Source File  |  1989-07-13  |  27.1 KB  |  513 lines

  1. (* TBTree16             Copyright (c)  1988,1989       Dean H. Farwell II    *)
  2.  
  3. unit Btree;
  4.  
  5. (*****************************************************************************)
  6. (*                                                                           *)
  7. (*                      B T R E E   R O U T I N E S                          *)
  8. (*                                                                           *)
  9. (*****************************************************************************)
  10.  
  11. (* These routines are responsible for handling the indexes.  The scheme used
  12.    is Knuth's variation of B-Trees discussed on page 64 of "Database Systems
  13.    Volume I Fourth Edition" by C.J. Date.  There are two different types of
  14.    nodes in the tree.  The upper level(s) are index nodes and fill the role of
  15.    the normal B-Tree.  The lowest level nodes are sequence nodes and represent
  16.    a dense index to the file.  The entries in the sequence node are sorted and
  17.    thus provide an alternate means to access information in the file.  This
  18.    allows rapid sorted retrievals without the need to keep the data file
  19.    sorted.
  20.  
  21.    The internal workings of an index is not really important, but you need to
  22.    be aware of what an index contains and what operations can be performed
  23.    using an index.  An index is primarily used to do retrievals using the
  24.    value of one or more fields of a data record.  An index contains values
  25.    which are the same as values of a field in a data file.  Each value in an
  26.    index contains a corresponding logical record number which shows which
  27.    logical record corresponds to that value.  In many instances, a field will
  28.    not be unique.  This means that more than one record may have the same
  29.    value for a given field.  The index will handle these properly.  However,
  30.    it would not make sense to have multiple values for the same field for a
  31.    single logical record number.  This would imply that a data record field
  32.    could have two or more values at the same time, which it obviously cannot.
  33.    This would occur only if you were updating a value for the given record
  34.    number and failed to delete the old entry from the index.  The
  35.    documentation contains an example of an update which will keep this from
  36.    happening.  It is also important to realize that each index can handle only
  37.    one data type. The valid data types are defined by the ValueType type.  The
  38.    data type of the values in the index is specified at the time that the
  39.    index is created and is never changed.
  40.  
  41.    The first step in using an index is to create one.  There is a routine
  42.    (CreateIndexFile) especially for this purpose.  It will create the root and
  43.    first sequence nodes as well as the internal bitmap.  This bitmap is used
  44.    to keep track of which index nodes (physical records) are currently in use
  45.    and which are available.  These bitmaps are maintained interally as part of
  46.    the index file itself.  This alleviates the requirement to maintain
  47.    separate bitmap files and greatly simplifies things.  When creating a file,
  48.    you must supply the file name, the data type and the size of the entries.
  49.    The path and the extension for the file name is optional.  For example, the
  50.    path can be used to ensure that the files will always be in one place even
  51.    though the program is run from other places, etc.  The extension can help
  52.    in your own bookkeeping.  For example, all data files could use an
  53.    extension of '.dat' and all index files could use '.idx' etc.  This is
  54.    completely up to you and your application.
  55.  
  56.    Once you have created an index, you will want to put value/logical record
  57.    number pairs into the index and retrieve them back out.  One routine
  58.    (InsertValueInBTree) is supplied to insert a value and corresponding
  59.    logical record number.  Likewise, there is one routine supplied to delete a
  60.    value/logical record number pair.  There is no routine for performing an
  61.    update.  Instead, you will perform an update by doing a delete followed by
  62.    an insert.
  63.  
  64.    Retrieving the logical records number(s) corresponding to values is the
  65.    primary purpose of the indexes and is discussed now.  There are actually
  66.    two distinct ways to do this.  The more powerful method is to use the
  67.    routines available in BTREE2.INC.  Each of these routines will perform an
  68.    index search and will result in the creation of a logical record list. A
  69.    logical record list is nothing more than a list of logical record numbers.
  70.    These lists are fully explained in the LRECLIST.PAS file and are key to the
  71.    power provided by TBTREE.  You will want to fully understand how to create
  72.    and access these lists.
  73.  
  74.    A second way to access the index is to use an internal (to the index)
  75.    cursor.  This cursor should not be confused with the cursor which is kept
  76.    as part of the logical record lists discussed above.  These index cursors
  77.    are discussed indepth in BTREE3.INC.
  78.  
  79.    To understand either method of retrieval, you need to have the proper
  80.    framework for understanding the index.  Even though the indexes are
  81.    maintained as BTREEs, you should view them as a sequential file of
  82.    values/logical record number pairs.  This sequential file is ordered
  83.    by value in ascending order.  Think of the pairs laid out horizontally with
  84.    the lower values on the left.  Moving forward through the index means that
  85.    larger values are accessed.  The first entry is the lowest value, etc.
  86.    Hopefully, you get the picture.                                           *)
  87.  
  88.  
  89. (*\*)
  90. (* Version Information
  91.  
  92.    Version 1.1 - Corrected error in InsertValueInBTree routine.   I
  93.                  was not checking for duplicate entries.  This has
  94.                  been resolved.
  95.  
  96.                - Added GetRangeFromBTree routine
  97.  
  98.                - Added GetSubstringFromBTree routine
  99.  
  100.                - Divided the source code into 3 source files - BTREE.PAS
  101.                                                                BTREE1.INC
  102.                                                                BTREE2.INC
  103.    Version 1.2 - No Changes
  104.  
  105.    Version 1.3 - Added NumberOfBTreeLevels routine
  106.  
  107.    Version 1.4 - HUGE CHANGE - I no longer keep the data file name as part of
  108.                  the index file parameter record.  It was not used, and I
  109.                  foresee no real use for it in the future.  I thought that I
  110.                  would need it for my TBASE product, but I won't.  This will
  111.                  mean a change to anyone who has applications that they have
  112.                  written using TBTREE version 1.3 or earlier.  Any calls to
  113.                  the old CreateIndex routine will need to be changed.
  114.                  dataFName is no longer a parameter for that routine.
  115.                  Hopefully, this will not create a great inconvenience.  I
  116.                  just was tired of carrying around the extra baggage.
  117.  
  118.                - HUGE CHANGE - The bitmap files are a thing of the past.  The
  119.                  bitmaps are now maintained as part of the index files
  120.                  themselves.  This has caused a complete restructuring of the
  121.                  index file parameter record and the index file themselves.
  122.                  This means that any index files (BTREEs) created using
  123.  !!!!! - >>>     versions prior to 1.4 will need to be rebuilt.  This is
  124.  IMPORTANT       discussed in the documentation under the version information.
  125.  !!!!! - >>>     I apologize for the inconvenience. However, the sooner I make
  126.                  these kinds of changes, the better.  This will simplify your
  127.                  use of the routines and cut in half the number of files to be
  128.                  dealt with (since each index and data file previously had a
  129.                  bitmap).
  130.  
  131.                - Corrected an error related to maximum value size (size of an
  132.                  entry) for an index.  The documentation states that the
  133.                  maximum size for an entry in an index is 494 bytes.  However,
  134.                  an error limited this to 255 bytes.  This is because only one
  135.                  byte was allocated in the parameter record for the value
  136.                  size.  This has been corrected.  However, in practical terms,
  137.                  you want to keep the index entries small.  This change
  138.                  required a change to the parameter record definition.
  139.  
  140.                - Added a second way to retrieve information from the B-Tree
  141.                  indexes.  Rather than always creating a logical record list
  142.                  when retrievals are required, you can now work with the index
  143.                  more directly.  The BTREE3.INC contains many routines which
  144.                  manipulate an internal cursor which can be used to access
  145.                  logical record numbers in the index.  The newly added
  146.                  BTREE3.INC file has more details.
  147.  
  148.                - Added GetSubstringAtPositionFromBTree routine.  This routine
  149.                  allows more flexibility when doing partial string matches on
  150.                  an index.  It is exceptionally handy if you have an index on
  151.                  a string field which really is made of several sub-fields.
  152.                  Something like phone number is a good example.  You might be
  153.                  interested in only phone numbers which have a seven as the
  154.                  second digit, or whatever.
  155.  
  156.                - Made several changes internally to increase performance.  One
  157.                  primary change was to use a Binary Search technique to find
  158.                  entries in nodes at all levels.  Also, VAR parameters are
  159.                  used internally when a parameter is large.  For instance,
  160.                  when a page is passed to a routine as a parameter, it is
  161.                  passed as a VAR parameter.  Therefore, the entire 512 bytes
  162.                  do not have to be placed on the stack.  This will also reduce
  163.                  the stack requirements.
  164.  
  165.                - Changed name of the CreateIndex routine to CreateIndexFile to
  166.                  emphasize the change to the parameters for the routine.
  167.                  Also, it clashed with a name which I want to use with TBASE.
  168.  
  169.                - Added DeleteIndexFile routine.  This routine should always be
  170.                  used when deleting an index file.
  171.  
  172.    Version 1.5 - Changed code internally to use Inc and Dec where practical
  173.  
  174.                - Changed code internally to use newly added FastMove unit
  175.  
  176.                - Added UsingCursorAndGEValueGetLr routine
  177.  
  178.                - Changed the way records are added as the data file grows on
  179.                  inserts.  As the file becomes larger,  the number of records
  180.                  added at one time increases.  This will speed up the insert
  181.                  process.
  182.  
  183.                - Added FindLrNumInBTree routine.  It may prove useful for
  184.                  debugging or ensuring that an index is still in good shape.
  185.  
  186.                - Fixed error in GetEqualsFromBTree routine which would cause
  187.                  random failures on queries where the Condition was EQ
  188.  
  189.                - Fixed error in GetRangeFromBTree routine which caused
  190.                  problems when the upper range and lower range were the same
  191.  
  192.    Version 1.6 - Fixed an error which caused the wrong record(s) to be deleted
  193.                  when using the DeleteValueFromBTree routine.  Error only
  194.                  occurred under a rare set of circumstances, but will not
  195.                  occur at all now.
  196.  
  197.                - I undid the third change of version 1.4 above.  In essence,
  198.                  MAXVALSIZE is now equal to 245.  This represents the largest
  199.                  number of bytes for an index entry.  The reasons for this are
  200.                  somewhat complex, but each index node must be capable of
  201.                  holding at least two entries.  There are 498 bytes available
  202.                  (after 14 are used for overhead).  Any entry consists of the
  203.                  value plus a 4 byte record number.  (245 + 4) * 2 = 498.
  204.                  This is well past the practical limit anyway.  Keep the
  205.                  entries as small as possible.  Anything over about 30 bytes
  206.                  is not very desirable.  Remember, in a btree the number of
  207.                  levels is related to the size of the entries as well as the
  208.                  number of entries.  Large entries will cause the tree to be
  209.                  extremely deep.  This will greatly reduce the performance.
  210.                  To check the number of levels, use the NumberOfBTreeLevels
  211.                  routine provided.                                           *)
  212.  
  213. (*\*)
  214. (*////////////////////////// I N T E R F A C E //////////////////////////////*)
  215.  
  216. interface
  217.  
  218. uses
  219.     Compare,
  220.     FastMove,
  221.     FileDecs,
  222.     Files,
  223.     Math,
  224.     Numbers,
  225.     LRecList,
  226.     Page,
  227.     Strings;
  228.  
  229. const
  230.     MAXVALSIZE    = 245;                          (* max value size in index *)
  231.  
  232. type
  233.     VSizeType     = 1 .. MAXVALSIZE;         (* size range for index entries *)
  234.  
  235.  
  236. (*\*)
  237. (* This routine will create an index file with the file name as specified
  238.    by iFName.  The valSize parameter specifies the size of the index
  239.    entries.  The easiest way to determine this is to use the SizeOf
  240.    function.  The valType parameter specifies the type for the index
  241.    entries.  The types supported are those enumerated by the ValueType
  242.    enumerated type.
  243.  
  244.    note - Extremely important - WARNING - for STRINGVALUE indexes only - the
  245.    valSize must be 1 greater than the number of characters of the longest
  246.    string.  This will allow 1 byte for the string length to be stored.
  247.    for example - if 'abc' is the longest string then valSize = 4.            *)
  248.  
  249. procedure CreateIndexFile(iFName : FnString;
  250.                           valSize : VSizeType;
  251.                           valType : ValueType);
  252.  
  253.  
  254. (* This routine will delete an index file.                                   *)
  255.  
  256. procedure DeleteIndexFile(iFName : FnString);
  257.  
  258.  
  259. (* This routine will insert a value and its associated logical record number
  260.    into the given index file.  This routine will guard against duplicate
  261.    entries. An index should have no more than one occurence of any
  262.    lrNum,paramValue pair (no two entries match on paramValue and lrNum).  This
  263.    routine assures this by calling DeleteValueFromBTree prior to performing
  264.    the insert.  This will get rid of a previous occurence if it exists.      *)
  265.  
  266. procedure InsertValueInBTree(iFName : FnString;
  267.                              lrNum : LRNumber;
  268.                              var paramValue);
  269.  
  270.  
  271. (* This routine will delete a value and its associated logical record number
  272.    from a given index file.  If the logical record number passed in is zero
  273.    then all records with a matching paramValue will be deleted.  Otherwise,
  274.    only the entry with the matching paramValue and the matching logical record
  275.    number will be deleted.                                                   *)
  276.  
  277. procedure DeleteValueFromBTree(iFName : FnString;
  278.                                lrNum : LrNumber;
  279.                                var paramValue);
  280.  
  281. (*\*)
  282. (* This routine will start at the root node and return the number of levels
  283. that exist in a BTree.  The index file name is the only required input.      *)
  284.  
  285. function NumberOfBTreeLevels(iFName : FnString) : Byte;
  286.  
  287.  
  288. (* This routine will search an index and determine whether the given logical
  289.    record number is in the index.  If it is, TRUE is returned in found and the
  290.    value associated with the logical record number is returned in paramValue.
  291.    If it is not found, found will be returned as FALSE and paramValue will
  292.    remain unchanged.  This is primarily used for debugging or determining if
  293.    an index has somehow been damaged.                                        *)
  294.  
  295. procedure FindLrNumInBTree(iFName : FnString;
  296.                            lrNum : LrNumber;
  297.                            var paramValue;
  298.                            var found : Boolean);
  299.  
  300.  
  301. (* This routine locates all values within the index which meet a condition
  302.    (cond) and returns a list of logical record numbers corresponding to these
  303.    values.
  304.  
  305.    note - In the case of  cond = EX (exists) all entries in the index will be
  306.    returned.  paramValue is not really used in this case so anything (a dummy)
  307.    can be passed in as the parameter corresponding to paramValue.            *)
  308.  
  309. procedure GetValuesFromBTree(iFName : FnString;
  310.                              var paramValue;
  311.                              cond : Condition;
  312.                              var lrLst : LrList);
  313.  
  314.  
  315. (* This routine locates all values within the index within a given range and
  316.    returns a list of logical record numbers corresponding to values within
  317.    that range.  The range is determined by paramValue1 and paramValue2.
  318.    paramValue1 must be less than paramValue2.  cond1 and cond2 are used to
  319.    determine if the range is inclusive or exclusive.  cond1 must be either
  320.    GE or GT and cond2 must be either LE or LT for this to work.  If any of
  321.    the above conditions is not true then an empty list will be returned.     *)
  322.  
  323. procedure GetRangeFromBTree(iFName : FnString;
  324.                             var paramValue1;
  325.                             cond1 : Condition;
  326.                             var paramValue2;
  327.                             cond2 : Condition;
  328.                             var lrLst : LrList);
  329.  
  330. (*\*)
  331. (* This routine locates partial string matches in an index of type
  332.    STRINGVALUE.  A partial string match occurs when the string passed in as
  333.    paramValue is contained within a string entry in the index.  You must
  334.    specify where in the string the match must occur.  You accomplish this by
  335.    using cond.  cond can be CO which stands for contains.  In this case, a
  336.    match occurs if the string passed in as paramValue is anywhere in the
  337.    string in the index.  Another option for cond is ST which stands for
  338.    starts.  A match will occur in this instance if paramValue is located at
  339.    the start of the string in the index.  The last option for cond is EN which
  340.    stands for ends.  A match here occurs if paramValue matches the last
  341.    characters in the index string.  Be aware that if paramValue and the string
  342.    being checked are the same length and they match exactly, then a match
  343.    would occur if any of the three options were selected (not earth shattering
  344.    but true nonetheless).
  345.  
  346.    Otherwise, this works like any of the other retrieval routines.  A list of
  347.    logical record numbers will be returned.
  348.  
  349.    This is exceptionally useful for many applications.  For example, a field
  350.    might be a 7 digit code stored as a string (STRINGVALUE).  The last two
  351.    digits might mean something in particular (part category, state, or
  352.    whatever).  Using this you can look for all the matches on only the last
  353.    two digits.
  354.  
  355.    One reality note here!! - For cond <> ST I have to look at every entry in
  356.    the index for matches.  Why this is true should be reasonably obvious.
  357.    Anyway, for cond = EN or cond = CO it will be somewhat slower than for cond
  358.    = ST.  How much slower depends on the size of the index.  It boils down to
  359.    the difference between a O(LOG(n)) algorithm and a O(n) algorithm, the
  360.    former being much faster for n = a large number.  If any of the above is
  361.    confusing ignore it and experiment.  It is still better to use this routine
  362.    than to grovel through all of the data records yourself!!                 *)
  363.  
  364. procedure GetSubstringFromBTree(iFName : FnString;
  365.                                 var paramValue;      (* must be a string var *)
  366.                                 cond : StringCondition;
  367.                                 var lrLst : LrList);
  368.  
  369. (*\*)
  370. (* This routine is exactly like GetSubstringFromBTree except that matches only
  371.    occur if the substring passed in as paramValue is located at the exact
  372.    location specified by the position parameter.  Also, the cond parameter is
  373.    omitted since it only makes sense to look for a partial string match at a
  374.    particular position if cond is CO.  In other works, you will get a logical
  375.    record list with entries for all index values which contain paramValue at
  376.    the character position specified by position.                             *)
  377.  
  378. procedure GetSubstringAtPositionFromBTree(iFName : FnString;
  379.                                           var paramValue;   (* must be a string
  380.                                                                          var *)
  381.                                           position : StringLengthRange;
  382.                                           var lrLst : LrList);
  383.  
  384.  
  385. (* This routine will set the tree cursor to the front of the index.  In
  386.    other words, it will point to the first entry in the index.  Remember, the
  387.    index is ordered by the value of each entry.  It will also return the
  388.    logical record associated with the first entry in the index.  It will
  389.    return 0 only if there is no first entry (the index is empty).  This
  390.    routine should be called if you want to start at the beginning of an index
  391.    and want to retrieve logical record numbers in order of entry.            *)
  392.  
  393. function UsingCursorGetFirstLr(iFName : FnString) : LrNumber;
  394.  
  395.  
  396. (* This routine will set the tree cursor to the end of the index.  In other
  397.    words, it will point to the last entry in the index.  Remember, the index
  398.    is ordered by the value of each entry.  It will also return the logical
  399.    record associated with the last entry in the index.  It will return 0 only
  400.    if there is no first entry (the index is empty).  This routine should be
  401.    called if you want to start at the end of an index and want to retrieve
  402.    logical record numbers in order of entry.                                 *)
  403.  
  404. function UsingCursorGetLastLr(iFName : FnString) : LrNumber;
  405.  
  406.  
  407. (* This routine will set the tree cursor to the location in the index where
  408.    the first occurence of the desired value (paramValue) is located.  It will
  409.    also return the logical record associated with this entry. It will return 0
  410.    if there is no entry associated with this value.  This routine should be
  411.    called if you want to start at a certain location (at a certain value)
  412.    within the index and want to retrieve logical record numbers in forward or
  413.    reverse order.                                                            *)
  414.  
  415. function UsingCursorAndValueGetLr(iFName : FnString;
  416.                                   var paramValue) : LrNumber;
  417.  
  418. (*\*)
  419. (* This routine is the same as UsingCursorAndValueGetLr except that this
  420.    routine will set the tree cursor to the location of the first value in the
  421.    index which is greater than or equal to paramValue.  It will also return
  422.    the logical record associated with this entry.  It will return 0 if there
  423.    is no entry which is greater than or equal to this value.                 *)
  424.  
  425. function UsingCursorAndGEValueGetLr(iFName : FnString;
  426.                                     var paramValue) : LrNumber;
  427.  
  428. (* This routine will move the cursor to the right one entry and return the
  429.    value associated with this entry.  It will return 0 if the cursor was not
  430.    valid (not pointing to an entry) or if there is no next entry (you are at
  431.    end of index).  This routine should be called if you want to move the
  432.    cursor to the next larger entry from the present cursor position and
  433.    retrieve the associated logical record number.  This routine should not
  434.    normally be used until the cursor has been positioned using one of the
  435.    three previous positioning routines.                                      *)
  436.  
  437. function UsingCursorGetNextLr(iFName : FnString) : LrNumber;
  438.  
  439.  
  440. (* This routine will move the cursor to the left one entry and return the
  441.    value associated with this entry.  It will return 0 if the cursor was not
  442.    valid (not pointing to an entry) or if there is no previous entry (you are
  443.    at beginning of the index).  This routine should be called if you want to
  444.    move the cursor to the next smaller entry from the present cursor position
  445.    and retrieve the associated logical record number.  This routine should not
  446.    normally be used until the cursor has been positioned using one of the
  447.    three previous positioning routines.                                      *)
  448.  
  449. function UsingCursorGetPrevLr(iFName : FnString) : LrNumber;
  450.  
  451.  
  452. (* This routine will move the cursor to the right.  It will move the cursor
  453.    to the next entry in which the value is not equal to the current entry and
  454.    return the associated logical record number.  In other words, it will skip
  455.    the cursor over all matching values.  It will return 0 if the cursor was
  456.    not valid (not pointing to an entry) or if there is no next entry (you are
  457.    at beginning of the index).  This routine should be used if you only want
  458.    to process the first entry of a given value etc.  This routine should not
  459.    normally be used until the cursor has been positioned using one of the
  460.    three previous positioning routines.                                      *)
  461.  
  462. function UsingCursorSkipAndGetNextLr(iFName : FnString) : LrNumber;
  463.  
  464. (*\*)
  465. (* This routine will move the cursor to the left.  It will move the cursor to
  466.    the previous entry in which the value is not equal to the current entry and
  467.    return the associated logical record number.  In other words, it will skip
  468.    the cursor over all matching values.  It will return 0 if the cursor was
  469.    not valid (not pointing to an entry) or if there is no previous entry (you
  470.    are at beginning of the index).  This routine should be used if you only
  471.    want to process the first entry of a given value etc.  This routine should
  472.    not normally be used until the cursor has been positioned using one of the
  473.    three previous positioning routines.                                      *)
  474.  
  475. function UsingCursorSkipAndGetPrevLr(iFName : FnString) : LrNumber;
  476.  
  477.  
  478. (* This routine will not move the cursor.  It will return the logical record
  479.    number asociated with the current cursor position.  It will return 0 only
  480.    if the current cursor position is not valid.                              *)
  481.  
  482. function UsingCursorGetCurrLr(iFName : FnString) : LrNumber;
  483.  
  484.  
  485. (* This routine will set the cursor to invalid.  This is never required,
  486.    but can be used once the cursor use is completed and the cursor won't be
  487.    used until it is repositioned using one of the three positioning
  488.    routines. Using this routine will slightly speed up inserts and deletes.
  489.    This is because, on an insert or delete, the cursor position must be
  490.    kept correct if the cursor is valid.  This requires a small amount of
  491.    extra processing.  This processing is extraneous if you don't care about
  492.    the cursor position.                                                      *)
  493.  
  494. procedure UsingCursorMakeCursorInvalid(iFName : FnString);
  495.  
  496.  
  497.  
  498. (*!*)
  499. (*\*)
  500. (*///////////////////// I M P L E M E N T A T I O N /////////////////////////*)
  501.  
  502. implementation
  503.  
  504. {$I btree1.inc}         (* most of the implementation part of the btree unit *)
  505.  
  506. {$I btree2.inc}         (* the rest of the implementation of the btree unit  *)
  507.  
  508. {$I btree3.inc}         (* the newly added cursor routines                   *)
  509.  
  510. begin
  511. mustMoveCursor := FALSE;
  512. end.                                                    (* end of BTree unit *)
  513.