home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 18 REXX / 18-REXX.zip / blt2rx_o.zip / c_src.zip / bd_locks.c < prev    next >
C/C++ Source or Header  |  1996-10-13  |  19KB  |  586 lines

  1.  
  2. /* bd_locks.c -  4-Oct-1996 Cornel Huth
  3.  * This module is called by bd_main.c
  4.  * region locking
  5.  */
  6.  
  7. #include "platform.h"
  8.  
  9. // external to this module
  10.  
  11. void PutMsg(CHAR *strg);
  12. void GetMsg(CHAR *strg);
  13.  
  14. extern CHAR *collateTable;
  15.  
  16.  
  17. // public in this module
  18.  
  19. int bd_locks(void);
  20.  
  21.  
  22. // GO
  23.  
  24. int bd_locks(void) {
  25.  
  26. /* 
  27.  * File-Open locks are not demonstrated here since they typically are not
  28.  * used for online transaction processing since these locks require a file
  29.  * open/close to change the lock type.  The locks demonstrated are region
  30.  * locks/relocks, where physical regions of the file are locked for access,
  31.  * and not the file open itself (other apps can open at any time).
  32.  *
  33.  * This example uses LOCK_XB, RELOCK_XB, and UNLOCK_XB, which are transaction-
  34.  * list routines where a group of related files are all locked in one atomic
  35.  * operation (if all succeed all are locked, if one cannot be locked, none 
  36.  * are locked).  Each individual file can be locked on its own by using the
  37.  * UN/RE/LOCK_INDEX_XB and UN/RE/LOCK_DATA_XB routines.
  38.  *
  39.  * RELOCK_XB, RELOCK_INDEX_XB, and RELOCK_DATA_XB are not supported in 
  40.  * W95 systems (Win/NT does support SHARED region locks, as OS/2, but does
  41.  * not support atomic relocking).
  42.  *
  43.  * RELOCK_XB, RELOCK_INDEX_XB, and RELOCK_DATA_XB are not supported in
  44.  * DOSX32 systems (DOS doesn't support this feature).
  45.  *
  46.  */
  47.  
  48.  void BuildFieldListLocks(FIELDDESCTYPE fieldList[]);
  49.  
  50. #pragma pack(1)
  51.  
  52.  ACCESSPACK AP[2];
  53.  DOSFILEPACK DFP;
  54.  CREATEDATAPACK CDP;
  55.  CREATEINDEXPACK CIP;
  56.  HANDLEPACK HP;
  57.  LOCKPACK LP[2];
  58.  OPENPACK OP;
  59.  
  60.  struct EmpRecType {
  61.   CHAR tag;              // record tag, init to SPACE, * means deleted
  62.   CHAR empID[9];         // SSN (not 0T string)
  63.   CHAR empLN[16];        // last name
  64.   CHAR empFN[16];        // first name
  65.   CHAR empHire[8];       // "YYYYMMDD" (not 0T string)
  66.   CHAR empDept[6];       // department assigned
  67.  }; // 56 bytes
  68.  struct EmpRecType EmpRec;
  69.  
  70. #pragma pack()
  71.  
  72.  LONG rez;                       // return value from Bullet
  73.  LONG rezX;                      // converted pack index (pack that failed ID)
  74.  LONG isLocked;                  // flag indicating active locks
  75.  
  76.  CHAR nameData[]=".\\$bd_lock.dbf"; // data filename
  77.  ULONG dataID=0;                 // handles of data file
  78.  FIELDDESCTYPE fieldList[5];     // 5 fields used in data record
  79.  
  80.  CHAR *nameIndex[] = {
  81.  ".\\$bd_lok1.ix3",                 // first index file
  82.  ".\\$bd_lok2.ix3"                  // second...
  83.  };
  84.  ULONG indexID[] ={0,0};
  85.  CHAR *keyExpression[] = {
  86.  "SSN",
  87.  "SUBSTR(LNAME,1,4)+SUBSTR(SSN,6,4)",
  88.  };
  89.  CHAR *keyBuffer[2][68];
  90.  
  91.  CHAR putStr[128];
  92.  
  93.  setbuf(stdout,NULL);
  94.  PutMsg("(Self-running module)\n\n");
  95.  
  96.  memset(fieldList,0,sizeof(fieldList));  // init unused bytes to 0 (required)
  97.  BuildFieldListLocks(fieldList);
  98.  
  99.  // Delete previous files from any previous run (disregard any error return)
  100.  
  101.  DFP.func = DELETE_FILE_DOS;
  102.  DFP.filenamePtr = nameData;
  103.  rez = BULLET(&DFP);
  104.  DFP.filenamePtr = nameIndex[0];
  105.  rez = BULLET(&DFP);
  106.  DFP.filenamePtr = nameIndex[1];
  107.  rez = BULLET(&DFP);
  108.  
  109.  PutMsg("Creating one data file ... ");
  110.  
  111.  // Create the data file, a standard DBF (ID=3) as defined in fieldList above.
  112.  
  113.  CDP.func = CREATE_DATA_XB;
  114.  CDP.filenamePtr = nameData;
  115.  CDP.noFields = 5;
  116.  CDP.fieldListPtr = fieldList;
  117.  CDP.fileID = 0x03;
  118.  rez = BULLET(&CDP);
  119.  if (rez) {
  120.     sprintf(putStr,"Failed data file create.  Err: %d\n",rez);
  121.     PutMsg(putStr);
  122.     goto Abend;
  123.  }
  124.  
  125.  // Open the data file
  126.  
  127.  PutMsg("opening ... ");
  128.  OP.func = OPEN_DATA_XB;
  129.  OP.filenamePtr = nameData;
  130.  OP.asMode = READWRITE | DENYNONE;
  131.  rez = BULLET(&OP);
  132.  if (rez) {
  133.     sprintf(putStr,"Failed data file open.  Err: %d\n",rez);
  134.     PutMsg(putStr);
  135.     goto Abend;
  136.  }
  137.  dataID=OP.handle;
  138.  
  139.  // Create index files
  140.  
  141.  PutMsg("Done.\n");
  142.  PutMsg("Creating two index files ... ");
  143.  CIP.func = CREATE_INDEX_XB;
  144.  CIP.filenamePtr = nameIndex[0];
  145.  CIP.keyExpPtr = keyExpression[0];
  146.  CIP.xbLink = dataID;
  147.  CIP.sortFunction = ASCII_SORT;
  148.  CIP.codePage = CODEPAGE;
  149.  CIP.countryCode = CTRYCODE;
  150.  CIP.collatePtr = NULL;
  151.  CIP.nodeSize = 512;
  152.  rez = BULLET(&CIP);
  153.  if (rez) {
  154.     sprintf(putStr,"Failed first index file create.  Err: %d\n",rez);
  155.     PutMsg(putStr);
  156.     goto Abend;
  157.  }
  158.  
  159.  CIP.filenamePtr = nameIndex[1];
  160.  CIP.keyExpPtr = keyExpression[1];
  161.  CIP.xbLink = dataID;
  162.  CIP.sortFunction = NLS_SORT | SORT_SET;
  163.  CIP.codePage = CODEPAGE;
  164.  CIP.countryCode = CTRYCODE;
  165.  CIP.collatePtr = collateTable;
  166.  CIP.nodeSize = 512;
  167.  rez = BULLET(&CIP);
  168.  if (rez) {
  169.     sprintf(putStr,"Failed second index file create.  Err: %d\n",rez);
  170.     PutMsg(putStr);
  171.     goto Abend;
  172.  }
  173.  
  174.  // Open the index files
  175.  
  176.  PutMsg("opening ... ");
  177.  OP.func = OPEN_INDEX_XB;
  178.  OP.filenamePtr = nameIndex[0];
  179.  OP.asMode = READWRITE | DENYNONE;
  180.  OP.xbLink = dataID;
  181.  rez = BULLET(&OP);
  182.  if (rez) {
  183.     sprintf(putStr,"Failed first index file open.  Err: %d\n",rez);
  184.     PutMsg(putStr);
  185.     goto Abend;
  186.  }
  187.  indexID[0] = OP.handle;
  188.  
  189.  OP.filenamePtr = nameIndex[1];
  190.  OP.xbLink = dataID;
  191.  rez = BULLET(&OP);
  192.  if (rez) {
  193.     sprintf(putStr,"Failed second index file open.  Err: %d\n",rez);
  194.     PutMsg(putStr);
  195.     goto Abend;
  196.  }
  197.  indexID[1] = OP.handle;
  198.  PutMsg("Done.\n\n");
  199.  
  200.  // Lock as one would do in preparation for an update, or any operation
  201.  // where the database needs to be changed, or a status needs to be queried
  202.  // Note: when using the list-processing lock routines (LOCK_XB, UNLOCK_XB,
  203.  //       and RELOCK_XB) only index file handles are used in LP.handle --
  204.  //       their data file will be known internally and locked accordingly.
  205.  //
  206.  // Since index files only are listed, you may wonder how redudant locks
  207.  // are avoid.  The answer is that Bullet locks only the first time, thereafter
  208.  // a lock count is maintained (on a handle basis).  So, while this example
  209.  // does indeed "lock" the data file twice, only the first LP actually does
  210.  // anything regarding the data file (both index files relate to the same
  211.  // data file in this example); the second LP data lock mearly increments
  212.  // the lock count on the data file handle (the first encounter actually
  213.  // locks the data region and reloads the data header).  Since each index
  214.  // file is separate, there are indeed separate physical locks done to each
  215.  // index file (and each maintain separate lock counts, header reloads, etc.).
  216.  
  217.  PutMsg("Locking the database (exclusive locks) ... ");
  218.  isLocked = 0;
  219.  
  220.  LP[0].func = LOCK_XB;
  221.  LP[0].handle = indexID[0];
  222.  LP[0].xlMode = 0;       // as are index locks
  223.  LP[0].dlMode = 0;       // data locks are exclusive (initially)
  224.  LP[0].recStart = 0;     // data file's entire region is locked
  225.  LP[0].recCount = 0;     // might as well (complete and all that)
  226.  LP[0].nextPtr = &LP[1];
  227.  LP[1].handle = indexID[1];
  228.  LP[1].xlMode = 0;
  229.  LP[1].dlMode = 0;
  230.  LP[1].recStart = 0;
  231.  LP[1].recCount = 0;
  232.  LP[1].nextPtr = NULL;
  233.  rez = BULLET(&LP[0]);
  234.  if (rez) {
  235.  
  236.     // since this transaction routine deals with both index and data files
  237.     // rez is returned as the pack that failed: a negative number if the
  238.     // failure was data file related, and positive if index file related
  239.     // in any case, rez is not "the" error code, but is the index of the
  240.     // pack that failed (first pack being pack 1), and in the pack's .stat
  241.     // lies the true error code
  242.  
  243.     if (rez < 0) {              // data related failure
  244.        rezX = abs(rez)-1;       // minus 1 since C arrays are 0-based
  245.        rez = LP[rezX].stat;     // rez is true error code
  246.        sprintf(putStr,"Lock failed on data, LP[%d], err: %d\n",rezX,rez);
  247.        PutMsg(putStr);
  248.        goto Abend;
  249.     }
  250.     else {
  251.        rezX = rez-1;
  252.        rez = LP[rezX].stat;
  253.        sprintf(putStr,"Lock failed on index, LP[%d], err: %d\n",rezX,rez);
  254.        PutMsg(putStr);
  255.        goto Abend;
  256.     }
  257.  }
  258.  
  259.  // If it gets here, locks are in place
  260.  // there is no partial locking with LOCK_XB -- it either all succeeds or
  261.  // those locks that were successful in the call are backed out
  262.  
  263.  PutMsg("Done.\n\n");
  264.  isLocked=1;
  265.  
  266.  // At this point, the database (the three files in this case) can be
  267.  // written to without concern of corruption (i.e., only this process
  268.  // can write OR read to these files).  Assume that that has taken place...
  269.  
  270.  PutMsg("INSERT_XB one item into database ... ");
  271.  EmpRec.tag = ' ';
  272.  strncpy(EmpRec.empID,"000000001",9);
  273.  strcpy(EmpRec.empLN,"LockLastName");
  274.  strcpy(EmpRec.empFN,"LockFirstName");
  275.  strncpy(EmpRec.empHire,"19950618",8);
  276.  strcpy(EmpRec.empDept,"XYZ");
  277.  
  278.  // Inserts only the single item (adds a record, inserts a key into each index)
  279.  
  280.  AP[0].func = INSERT_XB;
  281.  AP[0].handle = indexID[0];
  282.  AP[0].recNo = 0;                // required
  283.  AP[0].recPtr = &EmpRec;
  284.  AP[0].keyPtr = keyBuffer[0];
  285.  AP[0].nextPtr = &AP[1];
  286.  AP[1].func = INSERT_XB;
  287.  AP[1].handle = indexID[1];
  288.  AP[1].recNo = 0x80000000;       // this pack using record added in first pack
  289.  AP[1].recPtr = &EmpRec;
  290.  AP[1].keyPtr = keyBuffer[1];
  291.  AP[1].nextPtr = NULL;
  292.  rez = BULLET(&AP[0]);
  293.  if (rez) {
  294.     if (rez < 0) {
  295.        rez = abs(rez);
  296.        sprintf(putStr,"INSERT_XB failed, data AP[%d], err: %d\n",rez-1,AP[rez-1].stat);
  297.        PutMsg(putStr);
  298.     }
  299.     else {
  300.        sprintf(putStr,"INSERT_XB failed, index AP[%d], err: %d\n",rez-1,AP[rez-1].stat);
  301.        PutMsg(putStr);
  302.     }
  303.     goto Abend;
  304.  }
  305.  
  306. #if RELOCK_AVAIL == 1   // RELOCK_XB is not supported in DOSX32 or WIN95
  307.  
  308.  // Assume that the insert was successful (it was), and now you want to allow
  309.  // other processes to be able to read the database, but you do not want
  310.  // them to be able to change it (not uncommon).  OS/2 offers two locking
  311.  // modes: 1) exclusive locks, where no other process may read or write
  312.  // to the region locked except the current process (the one locking it),
  313.  // and 2) shared locks, where any other process may READ the locked region,
  314.  // but NO process, INCLUDING the current process, may write to the region.
  315.  // This feature is not available in MS-DOS, except at the file open lock
  316.  // level (but file open locks are too slow to be generally useful).
  317.  // Relock is also an atomic operation, in that there is no chance that
  318.  // another process can steal away the locks during the relock.
  319.  // Note that you can have the initial lock state set to 'shared', rather
  320.  // that exclusive as this example, and then relock to 'exclusive', however
  321.  // you can only call RELOCK_XB if a prior full lock (LOCK_XB) is in force.
  322.  
  323.  // Note:  When switching from exclusive to shared locks, Bullet automatically
  324.  //        flushes the file (index or data).  This to ensure that a flush
  325.  //        does take place while write-permission is still available -- for
  326.  //        example, you can close files with shared locks last active, and
  327.  //        Bullet does not flush (flush=writes file header, commits to OS)
  328.  //        the file, and cannot, since it can only flush when it has an active
  329.  //        lock, and that lock mode must permit writing (shared does not).
  330.  //        Therefore, whenever you switch from exclusive lock to shared lock,
  331.  //        Bullet flushes the file (which may do nothing if no flush is needed).
  332.  
  333.  // Since LP[] was previously set, only the changed parms need to be set.
  334.  // Be sure to set each LP[].func in the pack to the new operation, though.
  335.  
  336.  PutMsg("Done.\n\nChanging locks from exclusive to shared, relock ... ");
  337.  LP[0].func = RELOCK_XB;
  338.  LP[0].xlMode = 1;       // as are index locks
  339.  LP[0].dlMode = 1;       // data locks are now shared
  340.  LP[1].func = RELOCK_XB;
  341.  LP[1].xlMode = 1;       // (also see the Note: above, regarding flushing)
  342.  LP[1].dlMode = 1;
  343.  rez = BULLET(&LP[0]);
  344.  if (rez) {
  345.     if (rez < 0) {
  346.        rezX = abs(rez)-1;
  347.        rez = LP[rezX].stat;
  348.        sprintf(putStr,"Relock failed on data, LP[%d], err: %d\n",rezX,rez);
  349.        PutMsg(putStr);
  350.        goto Abend;
  351.     }
  352.     else {
  353.        rezX = rez-1;
  354.        rez = LP[rezX].stat;
  355.        sprintf(putStr,"Relock failed on index, LP[%d], err: %d\n",rezX,rez);
  356.        PutMsg(putStr);
  357.        goto Abend;
  358.     }
  359.  }
  360.  
  361.  PutMsg("Done.\n\n...Attempting to write to the database (this will FAIL):\n");
  362.  
  363.  // At this point, any process may access the database and read from it,
  364.  // but no process, not even this one, may write to it so long as the
  365.  // shared locks are in place.
  366.  
  367.  // *Attempts* to insert into the database but it will fail because shared
  368.  // locks permit read-only access for all processes, even the locking one.
  369.  // The data record, EmpRec, is changed slightly so that an ERR_KEY_EXISTS
  370.  // error is not returned (only a read is needed to get that far). (o)
  371.  
  372.  strncpy(EmpRec.empID,"000000002",9); // change keys built (see above)
  373.  
  374.  AP[0].func = INSERT_XB;
  375.  AP[0].handle = indexID[0];
  376.  AP[0].recNo = 0;                // required (note that this also returns a value)
  377.  AP[0].recPtr = &EmpRec;
  378.  AP[0].keyPtr = keyBuffer[0];
  379.  AP[0].nextPtr = &AP[1];
  380.  AP[1].func = INSERT_XB;
  381.  AP[1].handle = indexID[1];
  382.  AP[1].recNo = 0x80000000;       // this pack using record added in first pack
  383.  AP[1].recPtr = &EmpRec;
  384.  AP[1].keyPtr = keyBuffer[1];
  385.  AP[1].nextPtr = NULL;
  386.  rez = BULLET(&AP[0]);
  387.  if (rez) {
  388.     if (rez < 0) {
  389.        rez = abs(rez);
  390.        sprintf(putStr,"Sure enough, INSERT_XB failed, data AP[%d], err: %d\n",rez-1,AP[rez-1].stat);
  391.        PutMsg(putStr);
  392.     }
  393.     else {
  394.        sprintf(putStr,"Sure enough, INSERT_XB failed, index AP[%d], err: %d\n",rez-1,AP[rez-1].stat);
  395.        PutMsg(putStr);
  396.     }
  397.  }
  398.  else {
  399.     PutMsg("\n");
  400.     PutMsg("That write should not have worked!  See source.\n");
  401.     goto Abend;
  402.  }
  403.  
  404.  // switch back to exclusive locks and now write the second item
  405.  
  406.  PutMsg("\n");
  407.  PutMsg("Changing locks from shared back to exclusive, relock ... ");
  408.  LP[0].func = RELOCK_XB;
  409.  LP[0].xlMode = 0;
  410.  LP[0].dlMode = 0;
  411.  LP[1].func = RELOCK_XB;
  412.  LP[1].xlMode = 0;
  413.  LP[1].dlMode = 0;
  414.  rez = BULLET(&LP[0]);
  415.  if (rez) {
  416.     if (rez < 0) {
  417.        rezX = abs(rez)-1;
  418.        rez = LP[rezX].stat;
  419.        sprintf(putStr,"Relock failed on data, LP[%d], err: %d\n",rezX,rez);
  420.        PutMsg(putStr);
  421.        goto Abend;
  422.     }
  423.     else {
  424.        rezX = rez-1;
  425.        rez = LP[rezX].stat;
  426.        sprintf(putStr,"Relock failed on index, LP[%d], err: %d\n",rezX,rez);
  427.        PutMsg(putStr);
  428.        goto Abend;
  429.     }
  430.  }
  431.  
  432.  PutMsg("Done.\n\n...Attempting to write to the database (this will WORK) ... ");
  433.  
  434.  AP[0].func = INSERT_XB;
  435.  AP[0].handle = indexID[0];
  436.  AP[0].recNo = 0;
  437.  AP[0].recPtr = &EmpRec;
  438.  AP[0].keyPtr = keyBuffer[0];
  439.  AP[0].nextPtr = &AP[1];
  440.  AP[1].func = INSERT_XB;
  441.  AP[1].handle = indexID[1];
  442.  AP[1].recNo = 0x80000000;
  443.  AP[1].recPtr = &EmpRec;
  444.  AP[1].keyPtr = keyBuffer[1];
  445.  AP[1].nextPtr = NULL;
  446.  rez = BULLET(&AP[0]);
  447.  if (rez) {
  448.     if (rez < 0) {
  449.        rez = abs(rez);
  450.        sprintf(putStr,"INSERT_XB failed, data AP[%d], err: %d\n",rez-1,AP[rez-1].stat);
  451.        PutMsg(putStr);
  452.     }
  453.     else {
  454.        sprintf(putStr,"INSERT_XB failed, index AP[%d], err: %d\n",rez-1,AP[rez-1].stat);
  455.        PutMsg(putStr);
  456.     }
  457.     goto Abend;
  458.  }
  459.  else
  460.     PutMsg("Done.\n");
  461.  
  462. #else
  463.  
  464.     PutMsg("Done.\n");   // DOSX32 and Win95 do not support RELOCK_XB
  465.  
  466. #endif
  467.  
  468.  
  469. // Fatal errors above come straight to here
  470. Abend:
  471.  
  472.  // Consider all processing done at this point, and you want to finish
  473.  // up your database access...
  474.  
  475.  // The operating system will release any locks on files when the files
  476.  // are closed, however, it is only good practice to ensure that you perform
  477.  // this task, if for no other reason than to make sure they are unlocked!
  478.  // As with the previos call, all LP[] parms are already set but .func.
  479.  // Also note that when unlocking, the .xlMode/.dlMode must be set to the
  480.  // current real state -- in other words, since .xlMode=.dlMode=1 at this
  481.  // point (set immediately above), when unlocking these two parms should
  482.  // remain the same.  The reason is that Bullet uses these parms to determine
  483.  // if a flush is necessary.  If they were 1, then the last access could only
  484.  // have been read-only, and so not flush is necessary (also, another process
  485.  // may STILL have a shared lock on the file, so a flush would therefor fail!).
  486.  // If 0, then the standard flush operation is performed.
  487.  
  488.  PutMsg("\n");
  489.  PutMsg("Unlocking the database (exclusive locks) ... ");
  490.  if (isLocked) {
  491.     LP[0].func = UNLOCK_XB;
  492.     LP[1].func = UNLOCK_XB;
  493.     rez = BULLET(&LP[0]);
  494.     if (rez) {
  495.        if (rez < 0) {
  496.           rezX = abs(rez)-1;
  497.           rez = LP[rezX].stat;
  498.           sprintf(putStr,"Unlock failed on data, LP[%d], err: %d\n",rezX,rez);
  499.           PutMsg(putStr);
  500.        }
  501.        else {
  502.           rezX = rez-1;
  503.           rez = LP[rezX].stat;
  504.           sprintf(putStr,"Unlock failed on index, LP[%d], err: %d\n",rezX,rez);
  505.           PutMsg(putStr);
  506.        }
  507.     }
  508.  }
  509.  
  510.  PutMsg("Done.\n\n");
  511.  
  512.  // Note that if there is an error in the unlock process, you may need to
  513.  // manually unlock those packs that lie beyond the failed pack.  Unlikely
  514.  // is such to happen, though.
  515.  
  516.  if (rez==0) isLocked=0;
  517.  
  518.  // Close files (index files first then data (recommended but not required))
  519.  
  520.  PutMsg("Closing files ... ");
  521.  if (indexID[0]) {
  522.     HP.func = CLOSE_INDEX_XB;
  523.     HP.handle = indexID[0];
  524.     rez = BULLET(&HP);
  525.     if (rez) {
  526.        sprintf(putStr,"Failed first index file close.  Err: %d\n",rez);
  527.        PutMsg(putStr);
  528.     }
  529.  }
  530.  
  531.  if (indexID[1]) {
  532.     HP.func = CLOSE_INDEX_XB;
  533.     HP.handle = indexID[1];
  534.     rez = BULLET(&HP);
  535.     if (rez) {
  536.        sprintf(putStr,"Failed second index file close.  Err: %d\n",rez);
  537.        PutMsg(putStr);
  538.     }
  539.  }
  540.  
  541.  if (dataID) {
  542.     HP.func = CLOSE_DATA_XB;
  543.     HP.handle = dataID;
  544.     rez = BULLET(&HP);
  545.     if (rez) {
  546.        sprintf(putStr,"Failed data file close.  Err: %d\n",rez);
  547.        PutMsg(putStr);
  548.     }
  549.  }
  550.  PutMsg("Done.\n");
  551.  return rez;  // module exit
  552. }
  553.  
  554.  
  555. //------------------------------------
  556. // Init field list items for data file
  557.  
  558. void BuildFieldListLocks(FIELDDESCTYPE fieldList[]) {
  559.  
  560.  strcpy(fieldList[0].fieldName, "SSN");  // field names must be upper-case
  561.  fieldList[0].fieldType = 'C';           // field types must be upper-case
  562.  fieldList[0].fieldLen = 9;
  563.  fieldList[0].fieldDC = 0;
  564.  
  565.  strcpy(fieldList[1].fieldName, "LNAME");
  566.  fieldList[1].fieldType = 'C';
  567.  fieldList[1].fieldLen = 16;
  568.  fieldList[1].fieldDC = 0;
  569.  
  570.  strcpy(fieldList[2].fieldName, "FNAME");
  571.  fieldList[2].fieldType = 'C';
  572.  fieldList[2].fieldLen = 16;
  573.  fieldList[2].fieldDC = 0;
  574.  
  575.  strcpy(fieldList[3].fieldName, "HIRED");
  576.  fieldList[3].fieldType = 'D';
  577.  fieldList[3].fieldLen = 8;      // date field type must be 8.0
  578.  fieldList[3].fieldDC = 0;
  579.  
  580.  strcpy(fieldList[4].fieldName, "DEPT");
  581.  fieldList[4].fieldType = 'C';
  582.  fieldList[4].fieldLen = 6;
  583.  fieldList[4].fieldDC = 0;
  584.  return;
  585. }
  586.