home *** CD-ROM | disk | FTP | other *** search
/ Shareware Overload / ShartewareOverload.cdr / progm / cbase.zip / CBASE10B.ZIP / GUIDE.TXT < prev    next >
Text File  |  1989-11-22  |  56KB  |  1,511 lines

  1.                              CBASE
  2.  
  3.                     The C Database Library
  4.  
  5.  
  6.  
  7.  
  8.  
  9.                             Citadel
  10.                       Brookville, Indiana Copyright  1989 by Citadel.  All rights reserved.
  11.  
  12. Citadel
  13. 241 East Eleventh Street
  14. Brookville, IN 47012
  15. 317-647-4720
  16. BBS 317-647-2403
  17.  
  18. Version 1.0b
  19.  
  20. This manual is protected by United States copyright law.  No part of it
  21. may be reproduced without the express written permission of Citadel.
  22.  
  23. Technical Support
  24. Technical support is available between 10 a.m. and 4 p.m. EST.  When
  25. calling for technical support, please have ready the following
  26. information:
  27.  
  28.           . product name, version, and serial number
  29.           . operating system and version number
  30.           . C compiler and version number
  31.           . computer brand and model
  32.  
  33. The Citadel BBS is available 24 hours a day.  Users of Citadel
  34. products are encouraged to make use of this resource.
  35.  
  36.  
  37.  
  38.  
  39.  
  40.  
  41.  
  42.  
  43.  
  44.  
  45.  
  46.  
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57. UNIX is a trademark of AT&T.  MS-DOS is a trademark of Microsoft
  58. Corporation.  Turbo C is a trademark of Borland International, Inc.
  59.  
  60.  
  61.  
  62.  
  63.                                                        Contents
  64.  
  65.  
  66. Chapter 1.  Introduction                                      1
  67.  
  68. Chapter 2.  Database Basics                                   3
  69.      2.1  Sequential File Structures
  70.      2.2  Inverted Files
  71.      2.3  B-trees
  72.  
  73. Chapter 3.  cbase Library Functions                           7
  74.      3.1  Access Control Functions
  75.      3.2  Lock Functions
  76.      3.3  Record Cursor Position Functions
  77.      3.4  Key Cursor Position Functions
  78.      3.5  Input/Output Functions
  79.      3.6  Data Import/Export Functions
  80.  
  81. Chapter 4.  An Example Program                               15
  82.      4.1  Data Definition
  83.      4.2  Opening a cbase
  84.      4.3  Locking a cbase
  85.      4.4  Accessing a cbase
  86.      4.5  Closing a cbase
  87.      4.6  Manipulating Exported Data
  88.  
  89. Appendix A.  Installation Instructions                       27
  90.      A1   The blkio Library
  91.      A2   The btree Library
  92.      A3   The cbase library
  93.      A4   The lseq Library
  94.  
  95. Appendix B.  Defining New Data Types                         31
  96.      B1   The Type Name
  97.      B2   The Comparison Function
  98.      B3   The Export and Import Functions
  99.      B4   The Type Count
  100.  
  101. Appendix C.  Porting to a New Operating System               35
  102.      C1   The HOST macro
  103.      C2   The File Descriptor Type
  104.      C3   File Access System Calls
  105.      C4   File Locking System Calls
  106.  
  107. References                                                   39
  108.  
  109.  
  110.  
  111.  
  112.                                        Chapter 1:  Introduction
  113.  
  114.  
  115.      cbase is a C database file management library.  Records may be
  116. accessed both randomly and sequentially through indexes stored in 
  117. B+-trees.  Records may also be accessed sequentially in the order in
  118. which they are stored.  Multiuser access is supported under any
  119. operating system with file locking capabilities.
  120.  
  121.      cbase is designed to be extremely portable.  It is written in strict
  122. adherence to the ANSI C standard, and can still be used with K&R C
  123. compilers.  All system dependent code is isolated in order to make it
  124. easy to port to new operating systems.
  125.  
  126.      Many of the operations performed by cbase internally represent
  127. independently useful tools, and the software has been designed with this
  128. in mind.  cbase actually comprises four individual libraries, each
  129. complete and independently accessible.  Figure 1.1 shows these libraries
  130. and their relationships.
  131.  
  132.               ┌─────────────────────────────────┐
  133.               │              cbase              │
  134.               └───────┬─────────────────┬───────┘
  135.               ┌───────┴───────┐ ┌───────┴───────┐
  136.               │     lseq      │ │     btree     │
  137.               └───────┬───────┘ └───────┬───────┘
  138.               ┌───────┴─────────────────┴───────┐
  139.               │              blkio              │
  140.               └─────────────────────────────────┘
  141.  
  142.           Figure 1.1. cbase and Underlying Libraries
  143.  
  144.      At the foundation of cbase is the blkio (block buffered i/o) library. 
  145. blkio is a buffered i/o library very similar to stdio but based on a file
  146. model more appropriate for structured files such as used in database
  147. software (see FROS89).  While stdio models a file as an unstructured
  148. stream of characters, blkio models a file as a collection of blocks made
  149. up of fields.  All file access and buffering is done using blkio.  All
  150. operating system specific code is isolated within a small portion of this
  151. library.
  152.  
  153.      The lseq (linked sequential file) library provides all the facilities
  154. necessary for the creation and manipulation of doubly linked sequential
  155. files.  The btree (B-tree) library provides the same for B+-tree files. 
  156. The cbase library uses lseq and btree to perform all structured file
  157. management operations.  The lseq library is used for record storage and
  158. the btree library for inverted file key storage.
  159.  
  160.      When using a particular library, all operations are performed with
  161. functions provided by that library.  No references need be made to
  162. underlying libraries.  When using the cbase library, it is therefore not
  163. necessary to know the functions included in the other libraries.
  164.  
  165.  
  166.  
  167.  
  168.                                     Chapter 2:  Database Basics
  169.  
  170.  
  171.      This chapter describes some of the basic database concepts relevant
  172. to cbase.  It is intended to be a brief and readily accessible
  173. introduction, and can be easily skipped by one already familiar with
  174. database fundamentals.  References are given where in-depth discussions
  175. may be found.
  176.  
  177.  
  178. 2.1  Sequential File Structures
  179.  
  180.      One of the simplest file organizations is the physical sequential
  181. file.  In this organization, records are simply written one after the other
  182. and sequential access is done in the order in which the records are
  183. physically stored.
  184.  
  185.      The physical sequential file works very well in cases where the
  186. data is static, but it is extremely inefficient when the data is dynamic. 
  187. Consider a file containing 100,000 records stored in a sorted order. 
  188. The insertion of a single record at the beginning of this file would
  189. result in 100,000 records being moved to make room at the beginning
  190. of the file for the new record.
  191.  
  192.      This identical problem also occurs when large ordered lists are
  193. stored in memory, and the same solution, the linked list, can be used
  194. for files (see pp. 106-12 of HORO76 for an explanation of linked lists
  195. in memory).  In a linked list, the record order is determined by pointers
  196. stored with each record, not by the physical record locations; a record
  197. logically located at the beginning of a file can be physically located
  198. anywhere within the file.  In a singly linked list each record has only a
  199. pointer to the next record and so the file can be traversed in just one
  200. direction.  In a doubly linked list, each record has both a next and a
  201. previous pointer, allowing bidirectional access.
  202.  
  203.  
  204. 2.2  Inverted Files
  205.  
  206.      Assume a data file containing member records for an organization,
  207. and that the record format for this file is
  208.  
  209.           typedef struct {
  210.                long number;
  211.                char name[24];
  212.                char address[81];
  213.                char city[24];
  214.                char state[3];
  215.                char zip[11];
  216.           } member_t;
  217.  
  218. Assume further that the data file has a physical sequential file structure,
  219. and that the records are sorted by the number field, therefore a binary
  220. search may be performed to quickly find a record with a given number
  221. field.  The field on which the records are sorted is called the primary
  222. index.  The problem arises when a query is made on another field
  223. besides the primary index, in which case a search of the entire data file
  224. is required.
  225.  
  226.     The problem of being able to perform queries efficiently on more
  227. than one field can be resolved through the use of inverted files.  For
  228. each secondary index exists an inverted file containing an entry for each
  229. record in the data file.  The inverted file for the name field, for
  230. example, would have entries containing the name field and record
  231. position, and these would be sorted by the name field.  To find a
  232. record with a given value in the name field, the inverted file for the
  233. name field would be searched.  The record position paired with the
  234. specified name value would then be used to locate the record in the
  235. data file.  See pp. 531-533 of HORO76 and pp. 75-78 of ULLM82 for
  236. more information on inverted files.
  237.  
  238.  
  239. 2.3  B-trees
  240.  
  241.      As discussed above, the physical sequential file provides
  242. unacceptable performance for dynamic data.  For static data, however, it
  243. provides efficient sequential as well as random access (using a binary
  244. search).  The linked sequential file solves the problem of performance
  245. for dynamic data while preserving efficient sequential access, but the
  246. ability to randomly access records is lost.  The need to store dynamic
  247. data which may be randomly accessed led to the development of the
  248. B-tree.
  249.  
  250.      As the name implies, the B-tree stores data in a tree structure,
  251. which allows random access.  And since the tree nodes are connected
  252. by pointers, the update problem is solved in the same way as by the
  253. linked sequential file organization.  The basic B-tree does not provide
  254. very efficient sequential access, however.  This led to a B-tree variant
  255. called the B+-tree, which provides both efficient random and sequential
  256. access.
  257.  
  258.      While the B+-tree does come close to providing the best of all
  259. worlds, it has two important drawbacks.  First, there is significantly
  260. more storage overhead for a B+-tree than for a sequential file.  Every
  261. entry in a B+-tree is stored in a leaf node, and the rest of the tree is
  262. simply scaffolding used when the tree is searched.  Second, each entry
  263. in any type of B-tree must be unique.  These characteristics make
  264. B-tree file organizations inappropriate for data files containing records,
  265. which may be both large and duplicated.  But for inverted files, which
  266. generally have relatively short entries that are by their very nature
  267. unique (because the file position of each record in the data file would
  268. be unique), the B+-tree is an ideal choice.  cbase therefore uses the
  269. linked sequential file organization for record storage and B+-trees for
  270. inverted files.
  271.  
  272.      The mechanics of B-trees is beyond the scope of this chapter.  A
  273. good discussion of the B-tree and its major variants may be found in
  274. COME79.
  275.  
  276.  
  277.  
  278.  
  279.                             Chapter 3:  cbase Library Functions
  280.  
  281.  
  282. 3.1  Access Control Functions
  283.  
  284.      The cbcreate function is used to create a new cbase.
  285.  
  286.     int cbcreate(const char *cbname, size_t
  287. recsize, int fldc, const cbfield_t fldv[]);
  288.  
  289. cbname points to a character string which is the name of the cbase. 
  290. This name is used as the name of the data file which holds the records
  291. in the cbase.  recsize specifies the record size to be used.  fldc is
  292. the number of fields in the cbase, and fldv is an array of fldc field
  293. definition structures.  Each structure in the array contains the definition
  294. for one field.   field definitions must be in the order in which the fields
  295. occur in the record.  The field definition structure type cbfield_t is
  296. defined in <cbase.h>.
  297.  
  298.     typedef struct {    /* field definition */
  299.         size_t offset;      /* field offset */
  300.         size_t size;        /* size of field */
  301.         int type;           /* type of field */
  302.         int flags;          /* flags */
  303.         char filename[FILENAME_MAX + 1];
  304.                             /* data file name */
  305.     } cbfield_t;
  306.  
  307. offset is the location of the field within the record and size is the
  308. size of the field.  type specifies the field data type, legal values for
  309. which are shown in Table 3.1.  flags values are constructed by
  310. bitwise ORing together flags from the following list.
  311.  
  312.     CB_FKEY         Field is to be a key.
  313.     CB_FUNIQ        Only for use with CB_FKEY.
  314.                     Indicates that the key is
  315.                     constrained to be unique.
  316.  
  317. FILENAME_MAX is an ANSI C definition indicating the maximum
  318. length of a filename string.  It is defined in <stdio.h>.
  319.  
  320.     t_char      signed character
  321.     t_charv     signed character array
  322.     t_uchar     unsigned character
  323.     t_ucharv    unsigned character array
  324.     t_short     signed short integer
  325.     t_shortv    signed short integer array
  326.     t_ushort    unsigned short integer
  327.     t_ushortv   unsigned short integer array
  328.     t_int       signed integer
  329.     t_intv      signed integer array
  330.     t_uint      unsigned integer
  331.     t_uintv     unsigned integer array
  332.     t_long      signed long integer
  333.     t_longv     signed long integer array
  334.     t_ulong     unsigned long integer
  335.     t_ulongv    unsigned long integer array
  336.     t_float     floating point
  337.     t_floatv    floating point array
  338.     t_double    double precision
  339.     t_doublev   double precision array
  340.     t_ldouble   long double
  341.     t_ldoublev  long double array
  342.     t_pointer   pointer
  343.     t_string    character string
  344.     t_binary    block of binary data (e.g., graphics)
  345.  
  346.                   Table 3.1. cbase Data Types
  347.  
  348.      Before a cbase that has been created may be used, it must be
  349. opened using the function
  350.  
  351.     cbase_t *cbopen(const char *cbname, const char
  352. *type, int fldc, const cbfield_t fldv[]);
  353.  
  354. cbname, fldc, and fldv serve the same functions as for cbcreate
  355. and must be given the same values as when the cbase was created. 
  356. type points to a character string specifying the type of operations for
  357. which the cbase is to be opened (as for the stdio function fopen). 
  358. Legal values for type are
  359.  
  360.     "r"       open for reading
  361.     "r+"      open for update (reading and writing)
  362.  
  363. cbopen returns a pointer to the open cbase.
  364.  
  365.  
  366.      The cbsync function causes any buffered data for a cbase to be
  367. written out.
  368.  
  369.     int cbsync(cbase_t *cbp);
  370.  
  371. The cbase remains open and the buffers retain their contents.
  372.  
  373.      After processing is completed on an open cbase, it must be closed
  374. using the function
  375.  
  376.     int cbclose(cbase_t *cbp);
  377.  
  378. The cbclose function causes any buffered data for the cbase to be
  379. written out, unlocks it, closes it, and frees the cbase pointer.
  380.  
  381.  
  382. 3.2  Lock Functions
  383.  
  384.      Before an open cbase can be accessed, it must be locked.  The
  385. function used to control the lock status of a cbase is
  386.  
  387.     int cblock(cbase_t *cbp, int ltype);
  388.  
  389. where cbp is a pointer to an open cbase and ltype is the lock type
  390. to be placed on the cbase.  The legal values for ltype are
  391.  
  392.     CB_RDLCK  lock cbase for reading
  393.     CB_WRLCK  lock cbase for reading and writing
  394.     CB_RDLKW  lock cbase for reading (wait)
  395.     CB_WRLKW  lock cbase for reading and writing (wait)
  396.     CB_UNLCK  unlock cbase
  397.  
  398. If ltype is CB_RDLCK and the cbase is currently write locked by
  399. another process, or if ltype is CB_WRLCK and the cbase is currently
  400. read or write locked by another process, cblock will fail and set
  401. errno to EAGAIN.  For the lock types which wait, cblock will not
  402. return until the lock is available.
  403.  
  404.      The cbgetlck function reports the lock status held by the calling
  405. process on a cbase.
  406.  
  407.      int cbgetlck(cbase_t *cbp);
  408.  
  409. It returns one of the legal values for the ltype argument in the
  410. cblock function.
  411.  
  412.  
  413. 3.3  Record Cursor Position Functions
  414.  
  415.      Each open cbase has a record cursor.  At any given time the
  416. record cursor is either positioned on a record in that cbase or on a
  417. special position called null.  The record on which the cursor is located
  418. is referred to as the current record.  The operations performed by most
  419. cbase functions are either on or relative to the current record, so the
  420. initial step in a transaction on a cbase is usually to position the record
  421. cursor on the desired record.  When accessing the records in a cbase in
  422. the order in which they are stored, the following functions are used to
  423. move the record cursor:
  424.  
  425.     int cbrecfirst(cbase_t *cbp);
  426.     int cbreclast(cbase_t *cbp);
  427.     int cbrecnext(cbase_t *cbp);
  428.     int cbrecprev(cbase_t *cbp);
  429.  
  430. The cbrecfirst function positions the record cursor to the first
  431. record, and cbreclast to the last record.  Before calling either of
  432. these functions cbreccnt should be used to test if the cbase is empty.
  433.  
  434.     unsigned long cbreccnt(cbase_t *cbp);
  435.  
  436. If the cbase is empty, there is no first or last record and so these
  437. functions would return an error.  The cbrecnext function advances
  438. the record cursor to the succeeding record, and cbrecprev retreats it
  439. to the preceding record.  In the record ordering, null is located before
  440. the first record and after the last.
  441.  
  442.      There are also functions for saving the current position of the
  443. record cursor and resetting it to that position:
  444.  
  445.     int cbgetrcur(cbase_t *cbp, cbrpos_t
  446. *cbrpos_p);
  447.     int cbsetrcur(cbase_t *cbp, const cbrpos_t
  448. *cbrpos_p);
  449.  
  450. The cbgetrcur function gets the current position of the record cursor
  451. and saves it in the variable pointed to by cbrpos_p.  cbrpos_t is
  452. the cbase record position type, defined in <cbase.h>.  cbsetrcur
  453. can then be used later to set the record cursor back to that position. 
  454. The record cursor can be positioned on null by passing cbsetrcur
  455. the NULL pointer rather than a pointer to a variable.  Other than this
  456. special case, cbsetrcur should only be called with record cursor
  457. positions previously saved with cbgetrcur.
  458.  
  459.      The cbrcursor macro is used to test if the record cursor for a
  460. cbase is positioned on a record or on null.
  461.  
  462.     void *cbrcursor(cbase_t *cbp);
  463.  
  464. If the record cursor of the cbase pointed to by cbp is positioned on
  465. null, cbrcursor returns the NULL pointer.  If it is on a record,
  466. cbrcursor returns a value not equal to the NULL pointer.  This
  467. function is useful for loops which need to test when the last (or first)
  468. record has been processed.
  469.  
  470.  
  471. 3.4  Key Cursor Position Functions
  472.  
  473.      Each open cbase also has a key cursor for each key (index)
  474. defined for that cbase.  As for the record cursor, a key cursor is either
  475. positioned on a record in that cbase or on null.  To access a cbase in
  476. the sort order of a certain key, the appropriate key cursor is used
  477. instead of the record cursor.  Each key cursor moves independently of
  478. the others, but whenever a key cursor position is set, the record cursor
  479. is moved to the same record.  The key cursors are not affected by
  480. moving the record cursor.
  481.  
  482.      The following functions are used to move a key cursor.
  483.  
  484.     int cbkeyfirst(cbase_t *cbp, int field);
  485.     int cbkeylast(cbase_t *cbp, int field);
  486.     int cbkeynext(cbase_t *cbp, int field);
  487.     int cbkeyprev(cbase_t *cbp, int field);
  488.  
  489. These perform as do the corresponding functions for the record cursor. 
  490. Note the key cursor functions may be used only with fields defined to
  491. be keys.
  492.  
  493.      The following function is used to search for a key of a certain 
  494. value.
  495.  
  496.     int cbkeysrch(cbase_t *cbp, int field, const
  497. void *buf);
  498.  
  499. field is the key in which to search for the data item pointed to by
  500. buf.  If a key of that value is not found, the key (and record) cursor
  501. is positioned to the record (possibly null) which would follow a record
  502. with that key value.
  503.  
  504.      Since the key cursors do not automatically follow the record
  505. cursor, the situation sometimes occurs where the record cursor is
  506. positioned to the desired record, but the cursor for the key to be used
  507. next is not.  The cbkeyalign function is used to align a specified
  508. key cursor with the record cursor.
  509.  
  510.     int cbkeyalign(cbase_t *cbp, int field);
  511.  
  512. The reason the key cursors are not updated every time the record cursor
  513. moves is not because it would be in any way difficult to do so, but
  514. because this would increase the overhead enormously.  And since only
  515. one key cursor is normally used at a time, this extra overhead would
  516. almost never provide any benefit in return.
  517.  
  518.      As for the record cursor, each key cursor position can be tested to
  519. be positioned on a record or on null.
  520.  
  521.     void *cbkcursor(cbase_t *cbp, int field);
  522.  
  523. If the key cursor specified by field of the cbase pointed to by cbp is
  524. positioned on null, cbkcursor returns the NULL pointer.  If it is on a
  525. record, cbkcursor returns a value not equal to the NULL pointer.
  526.  
  527.  
  528. 3.5  Input/Output Functions
  529.  
  530.      To read a record from a cbase, the record cursor for that cbase is
  531. first positioned to the desired record using either the record cursor
  532. position functions or the key cursor position functions.  One of the
  533. following functions is then called to read from the current record.
  534.  
  535.     int cbgetr(cbase_t *cbp, void *buf);
  536.     int cbgetrf(cbase_t *cbp, int field, void
  537. *buf);
  538.  
  539. cbp is a pointer to an open cbase and buf points to the storage area
  540. to receive the data read from the cbase.  The cbgetr function reads
  541. the entire current record, while cbgetrf reads the specified field
  542. from the current record.
  543.  
  544.      The functions for inserting a new record into a cbase are
  545.  
  546.     int cbinscur(cbase_t *cbp, const void *buf);
  547.     int cbinsert(cbase_t *cbp, const void *buf);
  548.  
  549. where buf points to the record to be inserted.  When a new record is
  550. inserted into a cbase, the position it holds relative to each key cursor is
  551. defined by the sort order for that key field.  There is no predefined sort
  552. order associated with the record cursor, however, and it is up to the
  553. user whether or not to store the records for each cbase in a sorted or
  554. unsorted order.  To store records in a sorted order, the record cursor is
  555. first positioned the the record after which to insert the new record. 
  556. cbinscur is then called to insert the record pointed to by buf after
  557. the current record.  If no sort order is desired, the step to position the
  558. record cursor is skipped.  cbinsert always inserts a record after the
  559. last record in the cbase.
  560.  
  561.      The cbdelcur function is used to delete a record.
  562.  
  563.     int cbdelcur(cbase_t *cbp);
  564.  
  565. The record cursor must first be positioned on the record to delete, then
  566. cbdelcur called to delete the current record.
  567.  
  568.      The cbputr function writes over an existing record.
  569.  
  570.     int cbputr(cbase_t *cbp, const void *buf);
  571.  
  572. buf points to the new record contents.  Writing over an existing record
  573. is equivalent to deleting the record and inserting a new one in the same
  574. position in the file.
  575.  
  576.  
  577. 3.6  Data Import/Export Functions
  578.  
  579.      cbase data can be exported to a text file using the cbexport
  580. function.
  581.  
  582.     int cbexport(cbase_t *cbp, const char
  583. *filename);
  584.  
  585. Every record in cbase cbp is converted to a text format and written to
  586. the file filename.  The export file format is defined as follows.
  587.  
  588.     . Each record is terminated by a newline ('\n').
  589.     . The fields in a record are delimited by vertical
  590.       bars ('|').
  591.     . Each field contains only printable characters.
  592.     . If a field contains the field delimiter
  593.       character, that character is replaced with \F.
  594.     . The individual elements of array data types are
  595.       exported as individual fields.
  596.  
  597.      Data may be imported from a text file using the cbimport
  598. function.
  599.  
  600.     int cbimport(cbase_t *cbp, const char
  601. *filename);
  602.  
  603. cbimport reads each record from the text file filename and inserts
  604. it into the cbase cbp.
  605.  
  606.      Data import/export is primarily used to move data between
  607. different database formats.  This sometimes requires some slight
  608. rearranging of the text before importing.  This can be most easily done
  609. using awk, a language designed specifically for manipulating records
  610. stored in text files.  awk is normally included with UNIX, and is also
  611. available for MS-DOS.
  612.  
  613.  
  614.  
  615.                                  Chapter 4:  An Example Program
  616.  
  617.  
  618.      Included with cbase is rolodeck, an example program illustrating
  619. the use of cbase.  Rolodeck is a program for storing business cards. 
  620. To allow it to be compiled without requiring any additional libraries for
  621. displays, and because the purpose of the program is purely instructional,
  622. the program has been given only a simple user interface.
  623.  
  624.  
  625. 4.1  Data Definition
  626.  
  627.      The first step in writing a program using cbase is to define the
  628. data to be stored.  This should be done in a separate header file for
  629. each record type to be used.  Figure 4.1 shows rolodeck.h, the data
  630. definition header for the business card record type used by the rolodeck
  631. program.
  632.  
  633.     Starting at the top, the first thing to note is the definition of a
  634. macro to prevent multiple includes.  This is a general practice
  635. applicable to any header file, cbase or otherwise, whose purpose is to
  636. allow a header to be specified for inclusion multiple times in a file
  637. while ensuring that the definitions within the file are processed only
  638. once.  For a header containing nothing but macro definitions, the only
  639. effect of preventing multiple includes will be reduced compile times. 
  640. But for a header containing, for instance, type definitions, multiple
  641. includes would produce compilation errors.  Notice how the include
  642. macro is related to the header file name; the file name is converted to
  643. upper case and the period replaced by an underscore ('.' is not a valid
  644. character for C identifiers).  These macros should be named consistently
  645. to minimize the possibility of accidentally defining the same macro
  646. elsewhere for some other purpose.
  647.  
  648.      At the beginning of every data definition header file, <cbase.h>
  649. should be included.  Following this is defined the name of the cbase. 
  650. This character string is the name used for the record file.  This macro
  651. is to be used for the cbname argument when calling cbcreate and
  652. cbopen. #ifndef ROLODECK_H  /* prevent multiple includes */
  653. #define ROLODECK_H
  654.  
  655. #include <cbase.h>
  656.  
  657. /* cbase name */
  658. #define RDNAME    ("rolodeck.dat")
  659.  
  660. /* record definition */
  661. typedef struct {
  662.     char rd_contact[41];    /* contact name */
  663.     char rd_title[41];    /* contact title */
  664.     char rd_company[41];    /* company name */
  665.     char rd_addr[2*40+1];    /* company address */
  666.     char rd_city[26];        /* city */
  667.     char rd_state[3];        /* state */
  668.     char rd_zip[11];        /* zip code */
  669.     char rd_phone[13];    /* phone number */
  670.     char rd_ext[5];        /* phone extension */
  671.     char rd_fax[13];        /* fax number */
  672.     char rd_notes[4*40+1];    /* notes */
  673. } rolodeck_t;
  674.  
  675. /* field numbers */
  676. #define RD_CONTACT     (0)
  677. #define RD_TITLE         (1)
  678. #define RD_COMPANY     (2)
  679. #define RD_ADDR         (3)
  680. #define RD_CITY         (4)
  681. #define RD_STATE         (5)
  682. #define RD_ZIP         (6)
  683. #define RD_PHONE         (7)
  684. #define RD_EXT         (8)
  685. #define RD_FAX         (9)
  686. #define RD_NOTES        (10)
  687. #define RDFLDC        (11)
  688.  
  689. /* field definition list */
  690. static cbfield_t rd_fldv[] = {
  691.     {offsetof(rolodeck_t, rd_contact[0]),
  692.      sizeofm(rolodeck_t, rd_contact),
  693.      t_string, CB_FKEY | CB_FUNIQ, "rd_cont.key"},
  694.     {offsetof(rolodeck_t, rd_title[0]),
  695.      sizeofm(rolodeck_t, rd_title),
  696.      t_string, 0, ""},
  697.     {offsetof(rolodeck_t, rd_company[0]),
  698.      sizeofm(rolodeck_t, rd_company),
  699.      t_string, CB_FKEY, "rd_comp.key"},
  700.     {offsetof(rolodeck_t, rd_addr[0]),
  701.      sizeofm(rolodeck_t, rd_addr),
  702.      t_string, 0, ""},
  703.     {offsetof(rolodeck_t, rd_city[0]),
  704.      sizeofm(rolodeck_t, rd_city),
  705.      t_string, 0, ""},
  706.     {offsetof(rolodeck_t, rd_state[0]),
  707.      sizeofm(rolodeck_t, rd_state),
  708.      t_string, 0, ""},
  709.     {offsetof(rolodeck_t, rd_zip[0]),
  710.      sizeofm(rolodeck_t, rd_zip),
  711.      t_string, 0, ""},
  712.     {offsetof(rolodeck_t, rd_phone[0]),
  713.      sizeofm(rolodeck_t, rd_phone),
  714.      t_string, 0, ""},
  715.     {offsetof(rolodeck_t, rd_ext[0]),
  716.      sizeofm(rolodeck_t, rd_ext),
  717.      t_string, 0, ""},
  718.     {offsetof(rolodeck_t, rd_fax[0]),
  719.      sizeofm(rolodeck_t, rd_fax),
  720.      t_string, 0, ""},
  721.     {offsetof(rolodeck_t, rd_notes[0]),
  722.      sizeofm(rolodeck_t, rd_notes),
  723.      t_string, 0, ""}
  724. };
  725.  
  726. #endif    /* #ifdef ROLODECK_H */
  727.  
  728.                     Figure 4.1. rolodeck.h
  729.  
  730.      Next is the type definition for the record being defined.  Each
  731. field in the record (member in the structure) begins with a character
  732. sequence derived from the cbase name, usually two characters, followed
  733. by an underscore.  This character sequence should be unique across all
  734. the record types used by a given program.  This type is for use when
  735. declaring record variables.  Variables of this type should be used for
  736. functions such as cbgetr which require a pointer to a record.
  737.  
  738.      Following the record type definition is a list of macros used to
  739. identify a field when calling cbase functions.  The names of these
  740. macros are constructed by taking the field names from the record type
  741. definition and converting them to uppercase.  This is why the field
  742. names must be made unique.  Each field name macro must be an
  743. integer indicating the number of the field in the record type definition,
  744. starting at zero.  A macro for the total number of fields is also defined. 
  745. This macro is to be used for the fldc argument when calling
  746. cbcreate and cbopen.
  747.  
  748.      The actual data definition is done in the field definition array. 
  749. The members of this array are of type cbfield_t, described in
  750. section 3.1.  This array specifies, for each field in the record, its offset
  751. in the record, its size, its type, some flags, and a filename to be used if
  752. the field is to be a key.  The offsetof macro should be used to
  753. specify the offset.  A macro sizeofm (size of member) is defined in
  754. <cbase.h> to give the size of a member of a structure.
  755.  
  756.      size_t sizeofm(struct_t, member);
  757.  
  758. The arguments to sizeofm are the same as for offsetof.  The size
  759. cannot be calculated from the difference between field offsets because
  760. padding characters may be added between structure members to
  761. maintain proper alignment (see KERN88).  The field definition array is
  762. to be used as the fldv argument to cbcreate and cbopen.
  763.  
  764.      It should be noted that every record type should normally have at
  765. least one unique key field.  This field is used to uniquely identify
  766. records.  The physical record position is often used for this purpose,
  767. but, as will be seen later in this chapter, a unique key is required for
  768. multiuser applications.
  769.  
  770.      In addition to the data definition header, all programs using cbase
  771. normally require that the following header files also be included.
  772.  
  773.     #include <blkio.h>
  774.     #include <cbase.h>
  775.     #include <errno.h>
  776.     #include <stdio.h>
  777.     #include <stdlib.h>
  778.  
  779. <blkio.h> is the header for the blkio library.  It is included to
  780. provide the definition of the NULL pointer and the declaration of the
  781. function bexit.  bexit is for use in place of exit in any program
  782. which uses the blkio library for file access.  It writes out any buffered
  783. data for any open block file then calls exit (see bexit in the blkio
  784. reference manual).  <cbase.h> is the cbase header file containing all
  785. the constant and type definitions and function declarations for using the
  786. cbase library.  <errno.h> contains the definition of the errno macro
  787. as well as definitions of error code values; all cbase functions use
  788. errno for error reporting.  <stdio.h> is the header for the standard
  789. buffered i/o library, and is included for the declarations of functions
  790. such as printf and scanf.  <stdlib.h> is included for the
  791. macros EXIT_SUCCESS and EXIT_FAILURE, which are
  792. implementation defined success and failure codes to be used as the
  793. argument to exit.  <string.h> is included for declarations of the
  794. string and memory functions (e.g., strcpy and memset).
  795.  
  796.  
  797. 4.2  Opening a cbase
  798.  
  799.      The first step in accessing an existing cbase is to open it.  Figure
  800. 4.2 shows the code from rolodeck.c to open the rolodeck cbase. 
  801. rolodeck is opened with a type argument of "r+" to allow both
  802. reading and writing.  The other arguments are the cbase name,
  803. RDNAME, the field count, RDFLDC, and the field definition list,
  804. rdfldv, all defined in the data definition header file, rolodeck.h. 
  805. On error cbopen returns the NULL pointer.  For this program there is
  806. only one cbase, but most applications will require several.
  807.  
  808.      If the named cbase does not exist, cbopen will fail and set errno
  809. to ENOENT.  In this example, if the rolodeck cbase does not exist, it is
  810. created and the program continues as normal.  Note that the cbase must
  811. still be opened after it is created.  In some cases a separate program is
  812. written to create all the cbases required by an application; in this case
  813. the main program usually interprets ENOENT as an error and exits.
  814.  
  815. /* open rolodeck cbase */
  816. cbp = cbopen(RDNAME, "r+", RDFLDC, rdfldv);
  817. if (cbp == NULL) {
  818.     if (errno != ENOENT) {
  819.         fprintf(stderr, "*** Error %d opening
  820. rolodeck.\n", errno);
  821.         bexit(EXIT_FAILURE);
  822.     }
  823.     /* create rolodeck cbase */
  824.     printf("Rolodeck does not exist. 
  825. Creating...\n");
  826.     if (cbcreate(RDNAME, sizeof(rolodeck_t),
  827. RDFLDC, rdfldv) == -1) {
  828.         fprintf(stderr, "*** Error %d creating
  829. rolodeck.\n", errno);
  830.         bexit(EXIT_FAILURE);
  831.     }
  832.     cbp = cbopen(RDNAME, "r+", RDFLDC, rdfldv);
  833.     if (cbp == NULL) {
  834.         fprintf(stderr, "*** Error %d opening
  835. rolodeck.\n", errno);
  836.         bexit(EXIT_FAILURE);
  837.     }
  838. }
  839.  
  840.                   Figure 4.2. Opening a cbase
  841.  
  842.  
  843. 4.3  Locking a cbase
  844.  
  845.      Befor lock; a
  846. write locked cbase can be neither read nor write locked by any other
  847. process.  Write locks are exclusive because, if one process tried to read
  848. data while it was partially modified by another, the data would probably
  849. be in an inconsistent state.  Processes which will only read data,
  850. however, can safely proceed to do so concurrently.
  851.  
  852.      While a cbase is write locked, other processes needing to access
  853. that cbase must wait until it is unlocked so that they can in turn lock it
  854. themselves to complete their processing.  While a cbase is read locked,
  855. only processes needing to write data need to wait.  Using a write lock
  856. when a read lock would suffice will therefore delay processing
  857. unnecessarily.  For the same reason, locks of both types should be held
  858. for the shortest time possible; a common mistake in writing multiuser
  859. applications is to pause for use input while holding a lock on a cbase,
  860. which would cause a lock to be held indefinitely.
  861.  
  862.      If an attempt is made to obtain a lock on a cbase, but is blocked
  863. by a lock held by another process, cblock will fail and set errno to
  864. EAGAIN.  The call to cblock is therefore usually made in a loop with a
  865. predefined maximum number of tries.  It is usually most convenient to
  866. place this in a function configured suitably for the application being
  867. developed.  Figure 4.3 shows this function from rolodeck.c.  It may
  868. also be suitable in some instances to sleep for a short (possibly random)
  869. time between attempts to lock.
  870.  
  871. #define MAXLTRIES (20)    /* max lock tries */
  872.  
  873. /* rdlock:  rolodeck lock */
  874. int rdlock(cbp, ltype)
  875. cbase_t *cbp;
  876. int ltype;
  877. {
  878.     int i = 0;
  879.  
  880.     for (i = 0; i < MAXLTRIES; i++) {
  881.         if (cblock(cbp, ltype) == -1) {
  882.             if (errno == EAGAIN) {
  883.                 continue;
  884.             }
  885.             return -1;
  886.         } else {
  887.             errno = 0;
  888.             return 0;
  889.         }
  890.     }
  891.  
  892.     errno = EAGAIN;
  893.     return -1;
  894. }
  895.  
  896.              Figure 4.3. Rolodeck Locking Function
  897.  
  898.      There are also two lock types (CB_RDLKW and CB_WRLCKW)
  899. which, if the requested lock is blocked, will wait until it can be
  900. obtained.  These are not usually used, however, because if the lock is
  901. not freed in a reasonable time, the process waiting for the lock will be
  902. hung.
  903.  
  904.      For an applications where there will be only a single process, the
  905. necessary locks can be set immediately after opening the cbases to be
  906. accessed.
  907.  
  908.      One critical concern when locking multiple cbases is the possibility
  909. of deadlock.  Deadlock is an extensive subject, and there are a number
  910. of ways of ways of dealing with it.  Most texts on operating systems
  911. (see CALI82) and database theory cover the subject in detail.
  912.  
  913.  
  914. 4.4  Accessing a cbase
  915.  
  916.      The gross structure of the rolodeck program is a case statement
  917. within a loop.  At the start of the loop, a user request is read, the value
  918. of which is used to select the action performed in the case statement. 
  919. Each individual action performed in the case statement illustrates the
  920. use of cbase to perform a basic operation, e.g., inserting a record,
  921. deleting a record, finding the next record, exporting data to a text file,
  922. etc.  The operation of finding the next record serves as a good general
  923. example.  The code for this from rolodeck.c is shown in figure 4.4.
  924.  
  925.      One of the most important points to notice in the example code is
  926. that a unique key (the contact name, here) is used to relocate the
  927. current record when a cbase is locked.  This is because, when a cbase
  928. is unlocked, it may be modified by another process.  A record at a
  929. given location may be deleted, and the empty slot possibly reused for a
  930. new record.  Because of this, cbsetrpos cannot be used with a
  931. record position obtained during a previously held lock.  As mentioned
  932. in the section 4.3, applications which do not involve multiple processes
  933. accessing the same data can simply lock a cbase and leave it locked
  934. rather than locking the cbase immediately prior to each transaction.
  935.  
  936.      Another central point is the use of multiple keys.  In the rolodeck
  937. program, both the contact and the company names are keys.  The
  938. variable sf is used to identify the current sort field (which can be
  939. changed interactively).  Before using the cbkeynext function, the
  940. appropriate key cursor must first be positioned.  cbkeysrch positions
  941. only the key on which the search is made, which here is the unique
  942. key.  If the next card is to be found using the sort order of a different
  943. key, cbkeyalign must first be used to align that key cursor with the
  944. current record.  The reason that all the key cursors do not automatically
  945. follow the record cursor is that the overhead would be considerable, and
  946. usually only one key is used at a time.
  947.  
  948. case RD_REQ_NEXT_CARD:    /* next card */
  949.     rdlock(cbp, CB_RDLCK);
  950.     if (cbreccnt(cbp) == 0) {
  951.         printf("The rolodeck is empty.\n\n");
  952.         rdlock(cbp, CB_UNLCK);
  953.         continue;
  954.     }
  955.     /* use unique key field to set record cursor
  956. */
  957.     found = cbkeysrch(cbp, RD_CONTACT,
  958. rd.rd_contact);
  959.     if (sf != RD_CONTACT) {
  960.         /* align cursor of sort key */
  961.         if (cbkeyalign(cbp, sf) == -1) {
  962.             fprintf(stderr, "*** Error %d
  963. aligning key.\n", errno);
  964.             bexit(EXIT_FAILURE);
  965.         }
  966.     }
  967.     if (found == 1) {
  968.         /* advance key (and rec) cursor 1
  969. position */
  970.         cbkeynext(cbp, sf);
  971.     }
  972.     if (cbrcursor(cbp) == NULL) {
  973.         printf("End of deck.\n\n");
  974.         rdlock(cbp, CB_UNLCK);
  975.         continue;
  976.     }
  977.     cbgetr(cbp, &rd);
  978.     rdlock(cbp, CB_UNLCK);
  979.     break;    /* case RD_REQ_NEXT_CARD: */
  980.  
  981.                  Figure 4.4. Next Record Code
  982.  
  983.  
  984. 4.5  Closing a cbase
  985.  
  986.      When a program is through accessing a cbase, the cbase should be
  987. closed.  Figure 4.5 shows this code from rolodeck.c.
  988.  
  989. /* close cbase */
  990. if (cbclose(cbp) == -1) {
  991.     fprintf(stderr, "*** Error %d closing
  992. rolodeck.\n", errno);
  993.     bexit(EXIT_FAILURE);
  994. }
  995.  
  996.                   Figure 4.5. Closing a cbase
  997.  
  998. A cbase is automatically unlocked when it is closed.  A cbase is
  999. normally opened and closed only once in a program.
  1000.  
  1001.  
  1002. 4.6  Manipulating Exported Data
  1003.  
  1004.      Exported data often requires some processing before it is used. 
  1005. For instance, consider modifying an application to add a new field to an
  1006. existing cbase.  If there is a considerable amount of data already stored,
  1007. it would be desirable to use the import/export functions rather than
  1008. manually entering the data again.  Another common example is moving
  1009. data between cbase and another database package, which may use
  1010. different field and record separators.
  1011.  
  1012.      An ideal tool for processing records in text files is awk.  Below is
  1013. an example awk program for inserting a new field at position two in all
  1014. the records in a text file (note that awk field numbering starts at one,
  1015. not zero).
  1016.  
  1017. BEGIN {
  1018.     FS = "|";     # set input and output field and
  1019. record separators
  1020.     OFS = FS;
  1021.     RS = "\n";
  1022.     ORS = RS;
  1023.     NEWFIELD = 2; # field to insert
  1024. }
  1025.  
  1026. # insfld:  insert field n of current record
  1027. function insfld(n)
  1028. {
  1029.     if ((n < 1) || (n > NF + 1)) {
  1030.         return -1;
  1031.     }
  1032.  
  1033.     for (i = NF; i >= n; i--) {
  1034.         $(i + 1) = $i;
  1035.     }
  1036.     $n = "";
  1037.  
  1038.     return 0;
  1039. }
  1040.  
  1041. {
  1042.     if (insfld(NEWFIELD) == -1) {
  1043.         printf "Error inserting new second
  1044. field.\n";
  1045.         exit 1;
  1046.     }
  1047.     print $0;
  1048. }
  1049.  
  1050. END{
  1051.     exit 0;
  1052. }
  1053.  
  1054.             Figure 4.6. awk Program to Insert Field
  1055.  
  1056.     The predefined variables FS and OFS are used to set the input and
  1057. output field separators, respectively.  The predefined variables RS and
  1058. ORS are used to set the input and output record separators, respectively. 
  1059. Setting these variables appropriately is all that is necessary to convert
  1060. between text file formats using different field and record separators. 
  1061. The following short program would convert a text file exported from a
  1062. database using the tab character as a field separator for import by
  1063. cbase.
  1064.  
  1065. BEGIN {
  1066.     FS = "\t";     # set input and output field
  1067. and record separators
  1068.     OFS = "|";
  1069.     RS = "\n";
  1070.     ORS = RS;
  1071. }
  1072.  
  1073. {
  1074.     print $0
  1075. }
  1076.  
  1077. END{
  1078.     exit 0;
  1079. }
  1080.  
  1081.              Figure 4.7. awk Program to Change FS
  1082.  
  1083.  
  1084.  
  1085.  
  1086.                          Appendix A:  Installation Instructions
  1087.  
  1088.  
  1089.      The cbase library is distributed in MS-DOS format on either two
  1090. 360K 5¼" diskettes or one 720K 3½" diskette.  On the distribution
  1091. diskettes is a directory for each component library containing the files
  1092. for that library.  There is also a directory for the manx utility used to
  1093. extract the reference manual from the source code, and a directory for
  1094. the example program.
  1095.  
  1096.      If cbase is being installed on an MS-DOS system, the following
  1097. two commands will copy the contents of the distribution diskettes onto
  1098. the hard drive.
  1099.  
  1100.      > mkdir cbase
  1101.      > xcopy /s /v a: cbase
  1102.  
  1103. An operating system besides MS-DOS will require either a facility to
  1104. read MS-DOS diskettes, or access to an MS-DOS machine from which
  1105. files can be transferred by a serial link or network to the target
  1106. machine.  If the transfer process does not automatically convert the text
  1107. files to the format of the target system, an additional conversion utility
  1108. may be necessary.
  1109.  
  1110.      The remaining installation instructions for each library, which
  1111. should be performed in the directory containing that library, are given
  1112. below.  The batch files provided for MS-DOS installation (install.bat)
  1113. are written for Borland Turbo C; because there is so little uniformity
  1114. among C compilers for this operating system, modifications will be
  1115. required for other compilers.  Instructions for doing this are given at
  1116. the beginning of the blkio installation batch file.  The necessary
  1117. modifications are straightforward and the same for the other libraries. 
  1118. If a make utility is available, the UNIX makefiles may instead be
  1119. adapted.
  1120.  
  1121.      Before proceeding to install the libraries, the manx utility should
  1122. be compiled and placed in a directory in the path.  manx is used to
  1123. generate the reference manual for each library.  After installing the
  1124. libraries, the final step is to compile the example program.
  1125.  
  1126.  
  1127. A1.  The blkio Library
  1128.  
  1129.                              UNIX
  1130.      1. Install the boolean header file.
  1131.              $ su
  1132.              # cp bool.h /usr/include
  1133.              # ^d
  1134.      2. Build the blkio library.  Examine the makerec file
  1135.         afterward for warnings.
  1136.              $ make blkio > makerec
  1137.      3. Install the blkio library.  This will copy file blkio.h to
  1138.         /usr/include and the blkio library archive to
  1139.         /usr/lib.
  1140.              $ su
  1141.              # make install
  1142.              # ^d
  1143.  
  1144.  
  1145.                             MS-DOS
  1146.  
  1147.      1. If necessary, modify install.bat for the C compiler
  1148.         being used.
  1149.      2. Install the blkio library.
  1150.              > install
  1151.  
  1152.  
  1153. A2.  The btree Library
  1154.  
  1155.                              UNIX
  1156.  
  1157.      1. Install the blkio library.
  1158.      2. Build the btree library.  Examine the makerec file
  1159.         afterward for warnings or errors.
  1160.              $ make btree > makerec
  1161.      3. Install the btree library.  This will copy btree.h to
  1162.         /usr/include and the btree library archive to
  1163.         /usr/lib/libbtree.a.
  1164.              $ su
  1165.              # make install
  1166.              # ^d
  1167.  
  1168.  
  1169.                             MS-DOS
  1170.  
  1171.      1. Install the blkio library.
  1172.      2. If necessary, modify install.bat for the C compiler
  1173.         being used.
  1174.      3. Install the btree library.
  1175.              > install
  1176.  
  1177.  
  1178. A3.  The cbase library
  1179.  
  1180.                              UNIX
  1181.  
  1182.      1. Install the btree and lseq libraries.
  1183.      2. Build the cbase library.  Examine the makerec file
  1184.         afterward for warnings or errors.
  1185.              $ make cbase > makerec
  1186.      3. Install the cbase library.  This will copy cbase.h to
  1187.         /usr/include and the cbase library archive to
  1188.         /usr/lib/libcbase.a.
  1189.              $ su
  1190.              # make install
  1191.              # ^d
  1192.  
  1193.  
  1194.                             MS-DOS
  1195.  
  1196.      1. Install the btree and lseq libraries.
  1197.      2. If necessary, modify install.bat for the C compiler
  1198.         being used.
  1199.      3. Install the cbase library.
  1200.              > install
  1201.  
  1202.  
  1203. A4.  The lseq Library
  1204.  
  1205.                              UNIX
  1206.  
  1207.      1. Install the blkio library.
  1208.      2. Build the lseq library.  Examine the makerec file
  1209.         afterward for warnings or errors.
  1210.              $ make lseq > makerec
  1211.      3. Install the lseq library.  This will copy lseq.h to
  1212.         /usr/include and the lseq library archive to
  1213.         /usr/lib/liblseq.a.
  1214.              $ su
  1215.              # make install
  1216.              # ^d
  1217.  
  1218.  
  1219.                             MS-DOS
  1220.  
  1221.      1. Install the blkio library.
  1222.      2. If necessary, modify install.bat for the C compiler
  1223.         being used.
  1224.      3. Install the lseq library.
  1225.              > install
  1226.  
  1227.  
  1228.  
  1229.  
  1230.  
  1231.                            Appendix B:  Defining New Data Types
  1232.  
  1233.  
  1234.      cbase allows custom data types to be defined by the user.  Custom
  1235. data types are completely integrated; they are installed in exactly the
  1236. same manner as the predefined data types.  A data type definition
  1237. consists of a macro used as the type name (e.g., t_string), and three
  1238. functions:  a comparison function, an export function, and an import
  1239. function.  The comparison function is the most important; it determines
  1240. the sort order for data of that type.  The export function is used to
  1241. export data of the associated type to a text file, and the import function
  1242. to import data.  These functions are supplied by the user when a new
  1243. data type is installed, but afterwards are used only internally by cbase. 
  1244. Below are given step-by-step instructions for defining a new cbase data
  1245. type.
  1246.  
  1247.  
  1248. B1.  The Type Name
  1249.  
  1250.      For each cbase data type there is a corresponding type name by
  1251. which the user refers to that data type.  The type names are defined in
  1252. cbase.h.
  1253.  
  1254.     #define t_char    (0) /* signed character */
  1255.     ...
  1256.     #define t_string (24) /* character string */
  1257.     #define t_binary (25) /* block of binary data
  1258. */
  1259.  
  1260. Type names must be defined as integers, starting at zero and increasing
  1261. in steps of one.  The type name for a new data type would be added at
  1262. the end of this list, and be defined as an integer one greater than the
  1263. last data type in the list.  To avoid possible conflict with future
  1264. predefined types, user defined type names should not start with t_.
  1265.  
  1266.     #define ut_new   (27) /* new data type */
  1267.  
  1268.  
  1269. B2.  The Comparison Function
  1270.  
  1271.      A data type is characterized primarily by its sort order.  Each data
  1272. type is given a comparison function which defines this sort order. 
  1273. Comparison functions are of the form
  1274.  
  1275.     int cmp(const void *p1, const void *p2, size_t
  1276. n);
  1277.  
  1278. p1 and p2 are pointers to two data items to be compared, and n is the
  1279. size of the data items.  The value returned must be less than, equal to,
  1280. or greater than zero if the data item pointed to by p1 is less than,
  1281. equal to, or greater than, respectively, that pointed to by p2.  The C
  1282. standard library function memcmp would be a valid cbase comparison
  1283. function.
  1284.  
  1285.      All cbase comparison functions are located in the file cbcmp.c. 
  1286. For a new data type, a comparison function would be added in this file.
  1287.  
  1288.     static int newcmp(const void *p1, const void
  1289. *p2, size_t n)
  1290.     {
  1291.         ...
  1292.     }
  1293.  
  1294. Comparison functions are made static because they are accessed
  1295. only through an array of function pointers, cbcmpv, also defined in
  1296. cbcmp.c.  This array contains the comparison function for each cbase
  1297. data type.  The integer value of the type name is used by cbase as an
  1298. index into this array, and so the comparison functions must be in the
  1299. same order as the type names.  A pointer to the comparison function
  1300. for a new data type would be added at the end of this array.
  1301.  
  1302.     /* cbase comparison function table */
  1303.     cbcmp_t cbcmpv[] = {
  1304.         charcmp,
  1305.         ...
  1306.         pointercmp,
  1307.         binarycmp,
  1308.         newcmp
  1309.     };
  1310.  
  1311.  
  1312. B3.  The Export and Import Functions
  1313.  
  1314.      Each data type has an associated export function.  This export
  1315. function takes a data item of the associated type and writes it to a file
  1316. in a text format.  Export functions are of the form
  1317.  
  1318.     int exp(FILE *fp, const void *p, size_t n);
  1319.  
  1320. p is a pointer to the data item of size n to be exported.  The export
  1321. function converts the data item to a text format, then writes it to the
  1322. current position in file fp.  Upon successful completion, a value of
  1323. zero is returned.  Otherwise, a value of -1 is returned.  See cbexport in
  1324. the cbase Programmer's Reference Manual for a description of the
  1325. format of exported data.
  1326.  
  1327.      All cbase export functions are located in the file cbexp.c.  For a
  1328. new data type, an export function would be added in this file.
  1329.  
  1330.     static int newexp(FILE *fp, const void *p,
  1331. size_t n)
  1332.     {
  1333.         ...
  1334.     }
  1335.  
  1336.      Just like comparison functions, export functions are accessed by
  1337. cbase through an array.  This array, cbexpv, is defined in cbexp.c. 
  1338. A pointer to the export function for the new data type would be added
  1339. at the end of this array.
  1340.  
  1341.      The import function reads a data item from a text file.  Import
  1342. functions are of the form
  1343.  
  1344.     int imp(FILE *fp, const void *p, size_t n);
  1345.  
  1346. The parameters and return value are the same as for the export
  1347. function.  Import functions are located in cbimp.c.  Pointers to the
  1348. import functions are stored in the array cbimpv.
  1349.  
  1350.  
  1351. B4.  The Type Count
  1352.  
  1353.      The macro CBTYPECNT is defined in cbase_.h as the number
  1354. of data types defined.  It must be incremented by one for each new
  1355. data type added.
  1356.  
  1357.  
  1358.      After completing these steps, rebuild the cbase library (see
  1359. Appendix A).  The underlying libraries do not need to be rebuilt.
  1360.  
  1361.  
  1362.  
  1363.  
  1364.                  Appendix C:  Porting to a New Operating System
  1365.  
  1366.  
  1367.      The foundation of cbase is the blkio library.  It is responsible for
  1368. performing all file operations.  Since the stdio library cannot be used to
  1369. perform database file operations, it was necessary to use some operating
  1370. system specific code in writing blkio.  blkio must therefore be modified
  1371. for use under a currently unsupported operating system.  The remaining
  1372. libraries comprising cbase utilize blkio in the same way as stdio is used
  1373. to allow complete portability, and so to port cbase to a new operating
  1374. system requires only that blkio be ported to that system.  The steps
  1375. necessary to do this are outlined below.
  1376.  
  1377.  
  1378. C1.  The HOST Macro
  1379.  
  1380.      In the blkio library's private header file blkio_.h, a macro is
  1381. defined for each supported operating system.  When installing the blkio
  1382. library, the host operating system is selected by defining the HOST
  1383. macro using one of these system macros.  When porting to a new
  1384. operating system, a new system macro must be defined in blkio_.h.
  1385.  
  1386.     #define UNIX    (1)      /* UNIX */
  1387.     #define MSDOS   (2)      /* MS-DOS */
  1388.      #define NEWSYS  (3)      /* new os */
  1389.     #define HOST    NEWSYS
  1390.  
  1391.      In some instances it will be necessary to take into account
  1392. variations among the C compilers available for a system.  For
  1393. MS-DOS, the macro MSDOSC, also defined in blkio_.h, is used to
  1394. select a compiler in the same way as the HOST macro is used to select
  1395. the operating system.  To port to a new C compiler under MS-DOS, a
  1396. new compiler macro must be defined.
  1397.  
  1398.     #define TURBOC  (1)      /* Turbo C */
  1399.     #define MSC     (2)      /* Microsoft C */
  1400.     #define NEWDOSC (3)      /* new MS-DOS C
  1401. compiler */
  1402.     #define MSDOSC  NEWDOSC
  1403.  
  1404. To port to multiple incompatible compilers under a new operating
  1405. system, a new compiler selection macro analogous to MSDOSC would
  1406. need to be defined.
  1407.  
  1408.  
  1409. C2.  The File Descriptor Type
  1410.  
  1411.      In most operating systems, an open file is accessed not by name,
  1412. but through some type of handle, usually called a file descriptor.  File
  1413. descriptors are normally of type int, but this is not guaranteed.  A
  1414. union is therefore defined (in cbase.h) for the file descriptor.
  1415.  
  1416.     typedef union {        /* file descriptor type
  1417. */
  1418.         char cfd;      /* character fildes */
  1419.         short sfd;     /* short int fildes */
  1420.         int ifd;       /* int fildes */
  1421.         long lfd;      /* long int fildes */
  1422.     } fd_t
  1423.  
  1424. This union type is then used for the file descriptor of the block file
  1425. control structure.
  1426.  
  1427.     typedef struct {       /* block file control
  1428. structure */
  1429.         fd_t fd;       /* file descriptor */
  1430.         ...
  1431.     } BLKFILE;
  1432.  
  1433. When modifying the code in subsequent sections, the appropriate
  1434. member of the union fd_t would be used to access a file descriptor. 
  1435. If the file descriptor type for the new system is short, for instance,
  1436. the file descriptor for BLKFILE *bp would be accessed as bp->fd.sfd. 
  1437. It will be necessary to add a member to the fd_t union if one of the
  1438. required type does not already exist.
  1439.  
  1440.  
  1441. C3.  File Access System Calls
  1442.  
  1443.      The bulk of the operating system specific code is related to the
  1444. system calls used to access the file system.  These system calls perform
  1445. basic operations such as opening, reading, and writing a file, and are
  1446. conceptually the same on most systems.  In fact, they can usually be
  1447. directly translated to a corresponding call on the new system.
  1448.  
  1449.      All system calls accessing the file system are isolated in the file
  1450. buops.c (blkio unbuffered operations).  The HOST macro is used to
  1451. separate sections of code used for different operating systems. 
  1452. Similarly, compiler selection macros such as MSDOSC are used to
  1453. separate sections of code for different compilers under the same
  1454. operating system.
  1455.  
  1456.     #if HOST == UNIX
  1457.         .
  1458.         .
  1459.         .
  1460.     #elif HOST == MSDOS
  1461.     #if MSDOSC == TURBOC
  1462.         .
  1463.         .
  1464.         .
  1465.     #elif MSDOSC == MSC
  1466.         .
  1467.         .
  1468.         .
  1469.     #endif
  1470.     #endif
  1471.  
  1472. When porting to a new operating system (or compiler), each of these
  1473. conditional compilations using HOST (or the relevant compiler selection
  1474. macro) must be located and an additional #elif added for the new
  1475. system (or compiler).
  1476.  
  1477.  
  1478. C4.  File Locking System Calls
  1479.  
  1480.      System calls are also used to perform file locking.  All system
  1481. calls for file locking are located in the file lockb.c.  This file must
  1482. be modified using the same instructions given for buops.c.  If file
  1483. locking will not be used on the new system, lockb.c need not be
  1484. altered.
  1485.  
  1486.  
  1487.  
  1488.  
  1489.                                                      References
  1490.  
  1491.  
  1492. CALI82    Calingaert, P. Operating Systems Elements. Englewood
  1493.     Cliffs, NJ: Prentice Hall, 1982.
  1494.  
  1495. COME79    Comer, D. The Ubiquitous B-tree. ACM Computing
  1496.     Surveys, June 1979.
  1497.  
  1498. FROS89    Frost, L. A Buffered I/O Library for Structured Files. The
  1499.     C Users Journal, October 1989.
  1500.  
  1501. HORO76    Horowitz, E. and S. Sahni. Fundamentals of Data
  1502.     Structures. Rockville, MD: Computer Science Press, 1976.
  1503.  
  1504. KERN88    Kernighan, B. and D. Ritchie. The C Programming
  1505.     Language. Englewood Cliffs, NJ: Prentice Hall, 1988.
  1506.  
  1507. KNUT68    Knuth D. The Art of Computer Programming Volume 3 / 
  1508.     Sorting and Searching. Reading, MA: Addison-Wesley, 1968.
  1509.  
  1510. ULLM82    Ullman, J. Principles of Database Systems. Rockville, MD:
  1511.     Computer Science Press, 1982.