home *** CD-ROM | disk | FTP | other *** search
/ ftp.robelle3000.ai 2014 / 2014.06.ftp.robelle3000.ai.tar / ftp.robelle3000.ai / papers / sd.txt < prev    next >
Text File  |  1995-08-31  |  61KB  |  1,812 lines

  1.  
  2.  
  3.                   Adopting Self-Describing Files
  4.  
  5.  
  6.                          By David J. Greer
  7.  
  8.  
  9.                       Robelle Consulting Ltd.
  10.                      Unit 201, 15399-102A Ave.
  11.                    Surrey, B.C. Canada  V3R 7K1
  12.                       Phone:  (604) 582-1700
  13.                        Fax:  (604) 582-1799
  14.                       http://www.robelle.com
  15.  
  16.  
  17.  
  18.  
  19.  
  20.                              Abstract
  21.  
  22.  
  23.  Query can generate output to self-describing (SD) files, but no
  24.  HP products read these files except the old DSG and Listkeeper
  25.  products.  A self-describing file is a data file which stores a
  26.  standard description of its own record format in its user labels.
  27.  Thus, it is like a little stand-alone database (a trendy
  28.  developer might call this object-oriented).  If two software
  29.  tools understand SD files, it becomes trivial to transfer data
  30.  between them.  A user can archive some data in an SD file and
  31.  when it is restored five years later the SD file can tell what
  32.  the data means.  The author describes the internal format of SD
  33.  files, gives examples on how to read and write SD files, and
  34.  describes problems integrating SD files into a software tool.
  35.  
  36.  
  37.  
  38.  
  39.  
  40.  
  41.  
  42.               Copyright Robelle Consulting Ltd. 1992
  43.  
  44.  
  45.  Permission is granted to reprint this document (but not for
  46.  profit), provided that copyright notice is given.
  47.  
  48.  
  49.  
  50.  
  51.  
  52.                            Introduction
  53.  
  54.  
  55.  
  56.  For years, Query's Save Command has been able to create a file
  57.  that is self-describing.  A self-describing file is one that
  58.  contains the information about the fields in the file.  Normal
  59.  MPE and KSAM files are not self-describing.  In general, we know
  60.  nothing about the structure of the fields in each record.
  61.  
  62.  Unfortunately, few software tools create or understand
  63.  self-describing files.  While Query can produce self-describing
  64.  files, it cannot use them as input.  Our product Suprtool can
  65.  both create and understand self-describing files (including KSAM
  66.  ones).  In addition, Suprtool has a new self-describing format
  67.  that removes some restrictions of the original self-describing
  68.  structure.  In this article we will do the following:
  69.  
  70.  o  Describe the format of both the original self-describing file
  71.     (this will be a summary of the information in Appendix E of
  72.     the Query User Manual) and the new Robelle self-describing
  73.     file.
  74.  
  75.  o  Show how to create a self-describing file.
  76.  
  77.  o  Give a programming example that can understand and provide a
  78.     "form" listing of any self-describing file.
  79.  
  80.  o  Describe KSAM self-describing files.
  81.  
  82.  o  Speculate on what an "open system" self-describing file would
  83.     look like.
  84.  
  85.  
  86.  Query Versus Robelle Self-Describing Files
  87.  
  88.  Throughout this article we will refer to one of two types of
  89.  self-describing files.  The first kind are equivalent to the ones
  90.  produced by Query.  They are identified by the version number
  91.  " A.00.00".  The second kind were designed to overcome
  92.  limitations with the original self-describing format.  We
  93.  identify the revised files by calling them Robelle self-
  94.  describing files.  They have a version number of " B.00.00".
  95.  
  96.  
  97.  Examples In This Article
  98.  
  99.  Because we write code in SPL and SPLash!, we will give our
  100.  examples in these programming languages.  The only word of
  101.  caution is to remember that SPL uses zero-based addressing for
  102.  all its arrays.
  103.  
  104.  
  105.  MPE User Labels
  106.  
  107.  User labels are an optional part of an MPE file.  User labels are
  108.  part of the file, but they are not part of the data (i.e., when
  109.  reading the records in the file the user labels are ignored).
  110.  User labels are a handy place to store extra information about a
  111.  file.  Unfortunately, MS-Dos and UNIX have no concept similar to
  112.  MPE's user labels (see the section Future Self-Describing Formats
  113.  for ideas for UNIX and MS-Dos).
  114.  
  115.  The number of user labels must be specified when the file is
  116.  created.  On most versions of MPE, the only way to create a file
  117.  with user labels is via the FOPEN intrinsic.  Newer versions of
  118.  MPE/iX allow the ULABEL= keyword on the Build Command to specify
  119.  the number of user labels.  Each user label is 256 bytes long and
  120.  user labels are numbered from zero.  You access user labels by
  121.  calling the FREADLABEL and FWRITELABEL intrinsics.
  122.  
  123.  
  124.  
  125.  
  126.  
  127.                  Identifying Self-Describing Files
  128.  
  129.  
  130.  
  131.  An MPE file that is self-describing has a filecode of 1084.  You
  132.  will recognize these files by seeing "SD" next to the filecode of
  133.  a :listf,2:
  134.  
  135.  FILENAME  CODE  ----------LOGICAL RECORD---------  ----SPACE----
  136.                    SIZE  TYP    EOF      LIMIT R/B  SECTORS X MX
  137.  
  138.  LOADFILE  SD      128W  FB      33      10000  35      256  1  *
  139.  
  140.  Recognizing self-describing KSAM files is more difficult.  KSAM
  141.  sd-files do not have a special file code.  Instead, you must look
  142.  for a KSAM file with extra file labels.  On MPE/iX, this is done
  143.  with a :listf ,3 (on MPE V/E use Listdir.Pub.Sys):
  144.  
  145.  
  146.  What Is A Self-Describing File
  147.  
  148.  A self-describing file stores information in the MPE file labels
  149.  about the fields in each record of the file.  File labels are
  150.  like a special file within a file.  An MPE file label is 256
  151.  bytes long and an MPE file is created with 0 to 256 file labels.
  152.  The file labels are accessed via the Freadlabel and Fwritelabel
  153.  Intrinsic.  User labels are numbered from zero.
  154.  
  155.  Customarily, tools that create self-describing files leave the
  156.  first ten file labels (numbered 0 to 9) for user applications.
  157.  The self-describing information is broken into two kinds of
  158.  labels:  the header label and field labels.
  159.  
  160.  
  161.  
  162.  Header Label
  163.  
  164.  If an MPE file has n file labels, they are numbered from 0 to
  165.  n-1.  The self-describing labels are always added at the end of
  166.  the any file labels needed by the user.  The last file label will
  167.  be the sd-header label and the sd-field labels are arranged
  168.  backwards from this label (n-2, n-3, ...).  The format of the
  169.  header label is similar, but different for Query and Robelle
  170.  self-describing files.
  171.  
  172.  
  173.  Query Header Label
  174.  
  175.  The Query header label consists of the following fields:
  176.  
  177.  version (X8).  Always equal to " A.00.00" for Query self-
  178.     describing files.
  179.  
  180.  length (J1).  The length of each record in the file in bytes.  It
  181.     appears to always be identical to the MPE record length of the
  182.     file.
  183.  
  184.     fields (J1).  The number of fields in each file record.
  185.  
  186.     labels (J1).  Number of labels used for field descriptions
  187.     plus one for the header label.  This is different than the
  188.     number of MPE labels for the file.
  189.  
  190.  fields'per'label (J1).  Each field label contains one or more
  191.     field descriptions.  Do not assume a fixed number for this
  192.     field -- you must check the value of this field.
  193.  
  194.  size (J1).  Length of each field descriptor in 16-bit words.
  195.     Because MPE file labels are always 128 words long, the
  196.     fields'per'label should always be 128 / size.  Again, do not
  197.     assume a fixed constant for the field descriptor size.
  198.  
  199.  
  200.  Robelle Header Label
  201.  
  202.  The Robelle header label contains all of the fields of Query's
  203.  header label with one change (the version number is different)
  204.  and three additions:
  205.  
  206.  1. The version number is " B.00.00" instead of " A.00.00" (note
  207.     the space at the beginning).
  208.  
  209.  2. There are three new fields for handling sort keys.  These
  210.     fields are identical to the fields that you would pass to
  211.     Sortinit (in compatibility-mode):
  212.  
  213.     sort'max'keys (J1).  Maximum number of keys allowed in this
  214.        sd-file.  The sort'keys would be declared as:
  215.  
  216.               integer array sort'keys(0:sort'max'keys*3-1)
  217.  
  218.     sort'num'keys (J1).  The actual number of keys in the table.
  219.        This value must range from zero to sort'max'keys-1.
  220.  
  221.     sort'keys.  The sort keys themselves using the same
  222.        conventions as Sort/3000.  The byte-offsets of each key
  223.        start at one and not zero in the sort table.  The byte
  224.        offsets in each field entry remain the same (i.e.,
  225.        zero-based instead of one-based offsets).  The sort key
  226.        types correspond to those for the Sortinit intrinsic and
  227.        not the newer HPSortinit.
  228.  
  229.  
  230.  SPL Layout of the Header Label
  231.  
  232.  Here is the layout in SPL notation of the Robelle header label.
  233.  Note that we use exactly the same layout for accessing Query
  234.  header labels (we just ignore all sd'sort'... variables when
  235.  accessing Query self-describing files).  Each field descriptor is
  236.  fifteen words long, but even the Robelle field descriptor only
  237.  uses fourteen words.  We leave the last word unspecified (our
  238.  code always sets the filler words with binary zeroes):
  239.  
  240.      sdheader.srcinc:
  241.  
  242.      integer array sd'header(0:sd'label'len);   { 0 : 127 }
  243.      byte    array sd'version(*)        = sd'header;
  244.      integer array sd'reclength(*)      = sd'header(4);
  245.      integer array sd'numfields(*)      = sd'header(5);
  246.      integer array sd'numlabels(*)      = sd'header(6);
  247.      integer array sd'fieldsperlabel(*) = sd'header(7);
  248.      integer array sd'entrylen(*)       = sd'header(8);
  249.      integer array sd'sort'max'keys(*)  = sd'header(9);
  250.      integer array sd'sort'num'keys(*)  = sd'header(10);
  251.      integer array sd'sort'keys(*)      = sd'header(11);
  252.  
  253.  
  254.  Field Labels
  255.  
  256.  Every self-describing file has one or more field labels of 256
  257.  bytes.  Each field label has one or more field descriptors.  The
  258.  first fields in the file will be described in label N-2, the next
  259.  set of fields in N-3, and so on.  This is opposite to what you
  260.  might expect.  Self-describing files that Query produces have
  261.  eight field descriptors per user label.
  262.  
  263.  
  264.  
  265.  Query Field Labels
  266.  
  267.  Query always produces self-describing files with 15 words
  268.  reserved for each field descriptor.  Each field is described as
  269.  follows:
  270.  
  271.  field'name (X16).  The name of the field left-justified.  Field
  272.     names are in upper case.
  273.  
  274.  field'type (J1).  The type of the field taken from the following
  275.     list:
  276.  
  277.     1. ASCII (type U and X).
  278.  
  279.     2. free form ASCII numbers.
  280.  
  281.     3. signed integer (type I).
  282.  
  283.     4. floating point real (type R).
  284.  
  285.     5. packed decimal (type P).
  286.  
  287.     6. COBOL computational (type J).
  288.  
  289.     7. unsigned integers (type K).
  290.  
  291.     8. zoned decimal (type Z).
  292.  
  293.     9. IEEE floating point (type E).  This is a Robelle extension
  294.        that applies to either " A.00.00" or " B.00.00"
  295.        self-describing files.
  296.  
  297.     10.IMAGE compound field.
  298.  
  299.  field'offset (J1).  The offset of the field in bytes.  The offset
  300.     starts at zero.
  301.  
  302.  field'length (J1).  The length of the field in bytes.
  303.  
  304.  reserved'space (4J1).  Four words that are reserved for future
  305.     use.
  306.  
  307.  
  308.  Robelle Field Labels
  309.  
  310.  Many HP 3000 applications contain repeated fields.  Query
  311.  self-describing files map all repeated fields into type "10",
  312.  which is useless for applications that understand repeated
  313.  fields.  It would also be nice if additional user information,
  314.  such as the number of decimal points or the format of a date were
  315.  available.  The Robelle field descriptor provides for all of
  316.  these, by using three of the four words of reserved space.  All
  317.  fields up to field'length are the same as QUERY's (note
  318.  especially that field'length is the total length of the field and
  319.  not the length of one sub-field).  These are the new fields:
  320.  
  321.  field'repeat (J1).  In IMAGE terms, this is known as the
  322.     sub-count.  For simple fields, field'repeat is one (and not
  323.     zero).
  324.  
  325.  field'decplaces (J1).  Logical number of decimal places in the
  326.     field.  Zero means there are no decimal points.  This field
  327.     must be zero if the field'type is byte.
  328.  
  329.  field'date'type (J1).  Zero if the field is not a date.
  330.     Otherwise, contains a constant that describes the format of
  331.     the date.  These constants are described below.
  332.  
  333.  reserved'space (J1).  One word that is reserved for future use.
  334.  
  335.  
  336.  Date Format
  337.  
  338.  The date format is mapped into the data type and byte-length of
  339.  the field.  Here are the constants for each date format:
  340.  
  341.       1    yymmdd
  342.       2    ddmmyy
  343.       3    mmddyy
  344.       4    yymm
  345.       5    calendar (MPE intrinsic format)
  346.       6    yyyymmdd
  347.       7    ddmmyyyy
  348.       8    mmddyyyy
  349.       9    phdate   (PowerHouse format)
  350.       10   ask      (ASK ManMan format)
  351.  
  352.  
  353.  SPL Layout of the Field Descriptor
  354.  
  355.  Here is the layout in SPL notation of the Robelle field
  356.  descriptor.  Note that we use exactly the same layout for
  357.  accessing Query field descriptors (we ignore the repeat,
  358.  decplaces, and date'type fields for Query self-describing files):
  359.  
  360.     sdfield.srcinc:
  361.  
  362.     integer array sd'field(0:sd'max'field'len);  { 0 : 14 }
  363.     byte    array sd'field'name(*)     = sd'field;
  364.     integer array sd'field'type(*)     = sd'field(8);
  365.     integer array sd'field'offset(*)   = sd'field(9);
  366.     integer array sd'field'bytelen(*)  = sd'field(10);
  367.     integer array sd'field'repeat(*)   = sd'field(11);
  368.     integer array sd'field'decplaces(*)= sd'field(12);
  369.     integer array sd'field'date'type(*)= sd'field(13);
  370.  
  371.  
  372.  Support Routines
  373.  
  374.  To make our life easier, we have a standard include file with
  375.  both variables and SPL/SPLash! subroutines that we use in many of
  376.  our self-describing procedures:
  377.  
  378.  sdsubr.src:
  379.  
  380.  <<  Standard variables and subroutines needed to access fields in
  381.      a self-describing file.  This file must be included after all
  382.      variable declarations in a procedure.  >>
  383.  
  384.     integer
  385.        file'userlabels
  386.       ,file'foptions
  387.       ,file'filecode
  388.       ,current'labelnum
  389.       ,sd'field'index
  390.       ;
  391.  
  392.     integer array current'label(0:sd'label'len);
  393.  
  394.     subroutine file'error(local'filenum);
  395.        value   local'filenum;
  396.        integer local'filenum;
  397.     begin
  398.        xfileinfo(local'filenum);
  399.        goto error'exit;
  400.     end'subr;   <<file'error>>
  401.  
  402.     subroutine read'label'error(local'filenum);
  403.        value   local'filenum;
  404.        integer local'filenum;
  405.     begin
  406.        p "Unable to read label from self-describing file" err;
  407.        file'error(local'filenum);
  408.     end'subr;   <<read'label'error>>
  409.  
  410.     subroutine read'label(local'filenum,labelnum);
  411.        value   local'filenum, labelnum;
  412.        integer local'filenum, labelnum;
  413.     begin
  414.        blank(current'label,sd'label'len);
  415.        freadlabel(local'filenum
  416.                  ,current'label
  417.                  ,sd'label'len
  418.                  ,labelnum
  419.                  );
  420.        if < then
  421.           read'label'error(local'filenum)
  422.        else
  423.        if > then
  424.           if labelnum > file'userlabels then
  425.           begin
  426.              b'blank(outbuf,bl'outbuf);
  427.              move outbuf := "Attempting to read label ";
  428.              ascii(labelnum,10,outbuf'(26));
  429.              say'errx(outbuf,35,bl'outbuf);
  430.              read'label'error(local'filenum);
  431.           end'if;
  432.     end'subr;   <<read'label>>
  433.  
  434.     subroutine file'info(local'filenum);
  435.        value   local'filenum;
  436.        integer local'filenum;
  437.     begin
  438.        fgetinfo(local'filenum<<filenum          iv>>
  439.          ,                   <<filename         ba>>
  440.          ,file'foptions      <<foptions         l >>
  441.          ,                   <<aoptions         l >>
  442.          ,                   <<recsize          i >>
  443.          ,                   <<devtype          i >>
  444.          ,                   <<ldnum            l >>
  445.          ,                   <<hdaddr           l >>
  446.          ,file'filecode      <<filecode         i >>
  447.          ,                   <<recptr           d >>
  448.          ,                   <<eof              d >>
  449.          ,                   <<flimit           d >>
  450.          ,                   <<logcount         d >>
  451.          ,                   <<physcount        d >>
  452.          ,                   <<blksize          i >>
  453.          ,                   <<extsize          l >>
  454.          ,                   <<numextents       i >>
  455.          ,file'userlabels    <<userlabels       i >>
  456.          );
  457.        if <> then
  458.        begin
  459.           p "Unable to fgetinfo on file" err;
  460.           file'error(local'filenum);
  461.        end'if;
  462.     end'subr;   <<file'info>>
  463.  
  464.     logical subroutine get'field(local'filenum,offset);
  465.        value   local'filenum, offset;
  466.        integer local'filenum, offset;
  467.     begin
  468.        get'field := false;
  469.        if sd'field'index < sd'numfields then
  470.        begin
  471.           if (sd'field'index mod sd'fieldsperlabel) = 0 then
  472.           begin
  473.              current'labelnum := current'labelnum - 1;
  474.              read'label(local'filenum,current'labelnum);
  475.           end'if;
  476.           offset := (sd'field'index mod sd'fieldsperlabel) *
  477.                     sd'entrylen;
  478.           move sd'field := current'label(offset),(sd'entrylen);
  479.           sd'field'index   := sd'field'index + 1;
  480.           get'field := true;
  481.        end'if;
  482.     end'subr;   <<get'field>>
  483.  
  484.  
  485.  File'Error
  486.  
  487.  To make our life easier we will take a simple approach to file
  488.  system errors.  If any MPE file system intrinsic returns an
  489.  error, we call the Robelle equivalent of the printfileinfo
  490.  intrinsic and then we exit the Formselfdesc procedure.  Yes, we
  491.  use a goto in the file'error subroutine.  This is a good example
  492.  of where a goto enhances readability and reliability.
  493.  
  494.  
  495.  File'Info
  496.  
  497.  We have developed a standard set of subroutines for working with
  498.  self-describing files.  The file'info subroutine initializes the
  499.  file'userlabels, file'foptions, and file'filecode variables
  500.  (declared as part of the sdsubr.src file).
  501.  
  502.  
  503.  Read'Label
  504.  
  505.  It is important to understand the error checking in read'label.
  506.  MPE user labels may be allocated space, but they might not
  507.  actually be written.  For example, after first creating a file
  508.  with user labels, none of the user labels have actually been
  509.  written to the file.  If we get an end-of-file condition from
  510.  Freadlabel, we ignore the error unless a programming bug has
  511.  caused us to attempt to read a label that is greater than the
  512.  number of user labels in the file.
  513.  
  514.  
  515.  Get'Field
  516.  
  517.  We will describe how the get'field subroutine works later in the
  518.  section Understanding Self-Describing Information.
  519.  
  520.  
  521.  Am I A Self-describing File
  522.  
  523.  We determine if a file is self-describing in two ways:
  524.  
  525.  1. If the file has a filecode of 1084 and it has one or more MPE
  526.     file labels.
  527.  
  528.  2. The file is a KSAM file, has more than one label, we can read
  529.     the last label, and the last label starts with either the
  530.     string " A.00.00" or " B.00.00" (note the space at the
  531.     beginning).
  532.  
  533.  Here is a procedure that returns True if the passed filenum is a
  534.  self-describing file:
  535.  
  536.       $page "sd'file"
  537.       <<  Return true if the passed file is self-describing.
  538.       >>
  539.  
  540.       logical procedure sd'file(filenum);
  541.          value   filenum;
  542.          integer filenum;
  543.          option check 3;
  544.       begin
  545.  
  546.       $include sdheader.srcinc
  547.       $include sdfield.srcinc
  548.  
  549.       $include sdsubr.src
  550.  
  551.       $page "sd'file/mainline"
  552.  
  553.          sd'file := false;
  554.  
  555.          file'info(filenum);
  556.  
  557.          if file'filecode = 1084 and file'userlabels <> 0 then
  558.             sd'file := true
  559.          else
  560.          if file'foptions.(2:3) = 1 or
  561.             file'foptions.(2:3) = 3 then
  562.             if file'userlabels > 1 then
  563.             begin   <<ksam with extra user labels>>
  564.                read'label(filenum,file'userlabels-1);
  565.                move sd'header := current'label,(sd'label'len);
  566.                if sd'version = " A.00.00" or
  567.                   sd'version = " B.00.00" then
  568.                   sd'file := true;
  569.             end'else;
  570.  
  571.       error'exit:
  572.  
  573.       end'proc;   <<sd'file>>
  574.  
  575.  
  576.  
  577.  
  578.  
  579.                   Creating A Self-Describing File
  580.  
  581.  
  582.  
  583.  When we describe data structures we usually explain the input
  584.  routine first and then the creation/output routine second.  For
  585.  self-describing files, it is easier to do it in the opposite
  586.  order.  We will show the structure of a simple self-describing
  587.  file and then we will show the code that produced the
  588.  self-describing label information for the file.
  589.  
  590.  HowMessy is a Robelle program that reports on database
  591.  efficiency.  For years, this program has produced a report.
  592.  Unfortunately, reports must be read by humans.  It would make
  593.  more sense for HowMessy to produce a self-describing MPE file
  594.  with the efficiency information from one or more databases.  You
  595.  could then use a tool that understood self-describing files to
  596.  report and act on the information from the file produced by
  597.  HowMessy.  We will show all of the routines in HowMessy's
  598.  self-describing module, but first we need to know the structure
  599.  of the self-describing file.
  600.  
  601.  
  602.  HowMessy's Loadfile
  603.  
  604.  HowMessy creates a self-describing file called Loadfile.  This
  605.  file has one record per database/dataset/search-field for one or
  606.  more databases.  Here is a "form" listing of the Loadfile:
  607.  
  608.      File: LOADFILE.GROUP.ACCT     (SD Version B.00.00)
  609.         Entry:                     Offset
  610.            DATABASE             X26     1
  611.            DATASET              X16    27
  612.            DATASETNUM           I1     43
  613.            DATASETTYPE          X4     45
  614.            CAPACITY             I2     49
  615.            ENTRIES              I2     53
  616.            LOADFACTOR           I2     57           << .2  >>
  617.            SECONDARIES          I2     61           << .2  >>
  618.            MAXBLOCKS            I2     65
  619.            HIGHWATER            I2     69
  620.            PATHSORT             X1     73
  621.            PATHPRIMARY          X1     74
  622.            BLOCKFACTOR          I1     75
  623.            SEARCHFIELD          X16    77
  624.            MAXCHAIN             I2     93
  625.            AVECHAIN             I2     97           << .2  >>
  626.            STDDEVIATION         I2    101           << .2  >>
  627.            EXPECTEDBLOCKS       I2    105           << .2  >>
  628.            AVERAGEBLOCKS        I2    109           << .2  >>
  629.            INEFFICIENTPTRS      I2    113           << .2  >>
  630.            ELONGATION           I2    117           << .2  >>
  631.            FUTUREFIELDS         X136  121
  632.      Limit: 10000  EOF: 33  Entry Length: 256  Blocking: 35
  633.  
  634.  
  635.  Global Equates
  636.  
  637.  To simplify programming, we use a global constant "equates" that
  638.  define specific attributes of Query and Robelle self-describing
  639.  files.  When reading a self-describing file, we don't need most
  640.  of these constants, since the necessary numbers are provided in
  641.  the self-describing file header.  Here are the equates that we
  642.  use when creating self-describing files:
  643.  
  644.       sdequate.srcinc:
  645.  
  646.       equate
  647.          sd'max'field'len      = 15
  648.         ,sd'label'len          = 128
  649.         ,sd'max'fieldsperlabel = 8
  650.         ,sd'filler'labels      = 10
  651.         ;
  652.  
  653.       equate
  654.          sd'date'yymmdd   = 1
  655.         ,sd'date'ddmmyy   = 2
  656.         ,sd'date'mmddyy   = 3
  657.         ,sd'date'yymm     = 4
  658.         ,sd'date'calendar = 5
  659.         ,sd'date'yyyymmdd = 6
  660.         ,sd'date'ddmmyyyy = 7
  661.         ,sd'date'mmddyyyy = 8
  662.         ,sd'date'phdate   = 9
  663.         ,sd'date'askdate  = 10
  664.         ;
  665.  
  666.  
  667.  Computing the Number of Labels
  668.  
  669.  Before opening the Loadfile, HowMessy must determine how many
  670.  labels will be needed.  The following routine is used by Robelle
  671.  products to compute the number of user labels for a
  672.  self-describing file.  Note that we continue the Query standard
  673.  of reserving the first ten labels (numbered 0 to 9) for other
  674.  uses:
  675.  
  676.  $page "sd'compute'labels"
  677.  <<  Compute how many labels an SD file should have, based only
  678.      on the number of fields.  Includes the mysterious filler
  679.      labels.
  680.  >>
  681.  
  682.  integer procedure sd'compute'labels (numfields);
  683.     value   numfields;
  684.     integer numfields;
  685.     option check 3;
  686.  begin
  687.     sd'compute'labels :=
  688.                    (numfields-1+sd'max'fieldsperlabel) /
  689.                    sd'max'fieldsperlabel
  690.                    + 1 <<for the header label>>
  691.                    + sd'filler'labels;
  692.  
  693.  end'proc;   <<sd'compute'labels>>
  694.  
  695.  
  696.  Opening the Loadfile
  697.  
  698.  After computing the number of user labels, we can open a new MPE
  699.  file called Loadfile.  We designed the HowMessy Loadfile to have
  700.  records 256 bytes long.  To make our life easier, we have a few
  701.  global equates in the HowMessy self-describing module that we'll
  702.  use throughout the rest of the examples:
  703.  
  704.  $page "global equates and defines for the selfdesc module"
  705.  
  706.     equate
  707.        wl'loadfile  = 128
  708.       ,bl'loadfile  = wl'loadfile * 2
  709.       ,bl'item'name = 16
  710.       ,max'field    = 22              ! fields in Loadfile
  711.       ;
  712.  
  713.  Here is the actual code to create the Loadfile:
  714.  
  715.  $page "sd'open"
  716.  <<  Open the Loadfile and initialize the self-describing
  717.      information.
  718.  >>
  719.  
  720.  logical procedure sd'open(outfile,loadfile'filenum);
  721.     integer loadfile'filenum;   ! Note by reference -- returned
  722.     integer array outfile;
  723.     option check 3;
  724.  begin
  725.  $include localvar.srcinc
  726.  
  727.     byte array
  728.        loadfile'filename(0:bl'local'filename)
  729.       ;
  730.  
  731.     move loadfile'filename := "loadfile ";
  732.  
  733.     loadfile'filenum :=
  734.         fopen(loadfile'filename
  735.              ,                       << foptions         lv >>
  736.              ,1   <<write>>          << aoptions         lv >>
  737.              ,wl'loadfile            << recsize          iv >>
  738.              ,                       << device           ba >>
  739.              ,                       << formmsg          ba >>
  740.              ,sd'compute'labels(max'field)
  741.              ,35                     << blockfactor      iv >>
  742.              ,                       << numbuffers       iv >>
  743.              ,10000d                 << filesize         dv >>
  744.              ,                       << numextents       iv >>
  745.              ,                       << initialloc       iv >>
  746.              ,1084                   << filecode         iv >>
  747.              );
  748.     if loadfile'filenum = 0 then
  749.     begin
  750.        error(outfile,10);
  751.        xfileinfo(loadfile'filenum);
  752.     end'if
  753.     else
  754.        sd'open := true;
  755.  
  756.  end'proc;   <<sd'open>>
  757.  
  758.  Note that the filecode is 1084.  For non-KSAM files, this is used
  759.  to indicate a self-describing file.  When you do a :Listf of such
  760.  a file, MPE translates the "1084" filecode into "SD".
  761.  
  762.  
  763.  Writing the Self-Describing Labels
  764.  
  765.  Having successfully opened a new self-describing file, it's time
  766.  to write the self-describing information to the user labels.
  767.  Remember that the last user label (N-1) contains the header
  768.  information and the field labels are written in backward order
  769.  (N-2, N-3, ...).  Our routine to write the self-describing
  770.  information to the Loadfile writes the field information first
  771.  and then updates the header label as the last step:
  772.  
  773.  $page "sd'write'labels"
  774.  <<  Write out the labels of a self-describing file with the
  775.      Loadfile fields.
  776.  >>
  777.  
  778.  logical procedure sd'write'labels(outfile,filenum);
  779.     value   filenum;
  780.     integer filenum;
  781.     integer array outfile;
  782.     option check 3;
  783.  begin
  784.  $include localvar.srcinc
  785.     integer
  786.        field'index
  787.       ,field'offset
  788.       ,labelnum
  789.       ;
  790.  
  791.  $include sdheader.srcinc
  792.  $include sdfield.srcinc
  793.  
  794.     integer array sd'label(0:sd'label'len);
  795.  
  796.  $include sdsubr.src
  797.  $page "sd'write'labels/subroutines"
  798.     subroutine write'label(labelnum);
  799.        value   labelnum;
  800.        integer labelnum;
  801.     begin
  802.        fwritelabel(filenum,sd'label,sd'label'len,labelnum);
  803.        if <> then
  804.        begin
  805.           error(outfile,12);
  806.           file'error(filenum);
  807.        end'if;
  808.     end'subr;   <<write'label>>
  809.  
  810.     subroutine init'header;
  811.     begin
  812.        zero'buf(sd'header,sd'label'len);
  813.        b'blank(sd'version,8);
  814.        move sd'version  := " B.00.00";
  815.        sd'numfields     := max'field;
  816.        field'index      := 0;
  817.        sd'numlabels     := sd'compute'labels(sd'numfields) -
  818.                            sd'filler'labels;
  819.        sd'fieldsperlabel:= sd'max'fieldsperlabel;
  820.        sd'entrylen      := sd'max'field'len;
  821.     end'subr;   <<init'header>>
  822.  
  823.     subroutine init'all'labels(curr'label,num'labels);
  824.        value   curr'label, num'labels;
  825.        integer curr'label, num'labels;
  826.     begin
  827.        while curr'label > num'labels do
  828.        begin
  829.           write'label(curr'label);
  830.           curr'label := curr'label - 1;
  831.        end'while;
  832.     end'subr;     <<init'all'labels>>
  833.  
  834.     subroutine put'field(name,bytelen,decplaces,type);
  835.        value   bytelen, type, decplaces;
  836.        integer bytelen, decplaces, type;
  837.        byte array name;
  838.     begin
  839.        zero'buf(sd'field,sd'max'field'len);
  840.        move sd'field'name := name,(bl'item'name);
  841.        sd'field'type      := type;
  842.        sd'field'offset    := field'offset;
  843.        sd'field'bytelen   := bytelen;
  844.        sd'field'repeat    := 1;
  845.        move sd'label(sd'entrylen*sd'field'index) := sd'field,
  846.                                                     (sd'entrylen);
  847.        field'offset := field'offset + sd'field'bytelen;
  848.        sd'field'index := sd'field'index + 1;
  849.        if sd'field'index >= sd'fieldsperlabel then
  850.        begin
  851.           write'label(labelnum);
  852.           labelnum := labelnum - 1;
  853.           sd'field'index := 0;
  854.           zero'buf(sd'label,sd'label'len);
  855.        end'if;
  856.        b'blank(name,bl'item'name);
  857.     end'subr;  <<put'field>>
  858.  $page "sd'write'labels/mainline"
  859.  
  860.     sd'write'labels := false;
  861.  
  862.     init'header;
  863.  
  864.     file'info(filenum);
  865.  
  866.     field'offset   := 0;
  867.     sd'field'index := 0;
  868.     zero'buf(sd'label,sd'label'len);
  869.     init'all'labels(file'userlabels-1,sd'numlabels);
  870.  
  871.     labelnum       := file'userlabels - 2;
  872.  
  873.     sd'reclength   := bl'loadfile;
  874.  
  875.     b'blank(inbuf,bl'inbuf);
  876.  
  877.     move inbuf' := "DATABASE        "; put'field(inbuf, 26,0,1);
  878.     move inbuf' := "DATASET         "; put'field(inbuf, 16,0,1);
  879.     move inbuf' := "DATASETNUM      "; put'field(inbuf,  2,0,3);
  880.     move inbuf' := "DATASETTYPE     "; put'field(inbuf,  4,0,1);
  881.     move inbuf' := "CAPACITY        "; put'field(inbuf,  4,0,3);
  882.     move inbuf' := "ENTRIES         "; put'field(inbuf,  4,0,3);
  883.     move inbuf' := "LOADFACTOR      "; put'field(inbuf,  4,2,3);
  884.     move inbuf' := "SECONDARIES     "; put'field(inbuf,  4,2,3);
  885.     move inbuf' := "MAXBLOCKS       "; put'field(inbuf,  4,0,3);
  886.     move inbuf' := "HIGHWATER       "; put'field(inbuf,  4,0,3);
  887.     move inbuf' := "PATHSORT        "; put'field(inbuf,  1,0,1);
  888.     move inbuf' := "PATHPRIMARY     "; put'field(inbuf,  1,0,1);
  889.     move inbuf' := "BLOCKFACTOR     "; put'field(inbuf,  2,0,3);
  890.     move inbuf' := "SEARCHFIELD     "; put'field(inbuf, 16,0,1);
  891.     move inbuf' := "MAXCHAIN        "; put'field(inbuf,  4,0,3);
  892.     move inbuf' := "AVECHAIN        "; put'field(inbuf,  4,2,3);
  893.     move inbuf' := "STDDEVIATION    "; put'field(inbuf,  4,2,3);
  894.     move inbuf' := "EXPECTEDBLOCKS  "; put'field(inbuf,  4,2,3);
  895.     move inbuf' := "AVERAGEBLOCKS   "; put'field(inbuf,  4,2,3);
  896.     move inbuf' := "INEFFICIENTPTRS "; put'field(inbuf,  4,2,3);
  897.     move inbuf' := "ELONGATION      "; put'field(inbuf,  4,2,3);
  898.     move inbuf' := "FUTUREFIELDS    "; put'field(inbuf,136,0,1);
  899.  
  900.     if sd'field'index <> 0 then
  901.        write'label(labelnum);
  902.  
  903.     move sd'label := sd'header,(sd'label'len);
  904.     write'label(file'userlabels - 1);
  905.  
  906.     sd'write'labels := true;
  907.  
  908.  error'exit:
  909.  
  910.  end'proc;   <<sd'write'labels>>
  911.  
  912.  
  913.  Init'Header
  914.  
  915.  We start our procedure by initializing most of the fields in the
  916.  header label.  We zero out the header label and then we fill in
  917.  the variables of the header label.  This file has fields with an
  918.  implied decimal point, so we want to use the Robelle format of
  919.  self-describing files (version number " B.00.00").  The number of
  920.  fields is taken from our global equate.  The number of
  921.  self-describing labels is our computed number less the ten
  922.  overhead labels.  The number of fields in each label and the
  923.  length of each field description are taken from global equates
  924.  that match the values used by Query.  We also initialize the
  925.  field'index variable which is used as an index into a single
  926.  label buffer (varies from 0 to sd'fieldsperlabel - 1).
  927.  
  928.  
  929.  File'Info
  930.  
  931.  To make our code more general-purpose, we will not assume
  932.  anything about the HowMessy Loadfile format (this also makes it
  933.  easier to change later).  Instead, we call Fgetinfo to obtain the
  934.  number of labels in our file, so that we know exactly where the
  935.  last label is.  The file'info subroutine initializes the
  936.  file'userlabels variable (declared in the sdsubr.src file) with
  937.  the number of user labels in our file.
  938.  
  939.  
  940.  Init'All'Labels
  941.  
  942.  To be on the safe side, we initialize all user labels in our file
  943.  with binary zeroes.  Note that our write'label subroutine uses a
  944.  procedure global array called label'buf for writing.  We
  945.  initialize this buffer to binary zeroes and then continually
  946.  write it out to all of the self-describing labels.  We don't
  947.  touch the initial ten labels reserved for other use.
  948.  
  949.  
  950.  Put'Field
  951.  
  952.  This subroutine handles all of the details of adding a new field
  953.  to our self-describing Loadfile.  It initializes a new field
  954.  record, moves this field record to the appropriate place in
  955.  label'buf, and finally writes out labels as we overflow
  956.  sd'fieldsperlabel.  Each of our fields has a name (we move the
  957.  name to inbuf and initialize inbuf to blanks after adding the
  958.  field, a byte length, the implied number of decimal places, and a
  959.  type (either byte or integer for our file).  Note that the
  960.  put'field subroutine looks after computing the byte offset of
  961.  each field by incrementing a counter.
  962.  
  963.  We initialize each field record with binary zeroes (just to be
  964.  safe).  We then fill in each portion of field information.  We
  965.  then move our field record to the label'buf at the correct
  966.  offset.  This works well in SPL/SPLash!, but is more of a problem
  967.  in C or Pascal.  In these languages, we would create a
  968.  record/structure that was an array of field records and index
  969.  into the structure using the current field index.  As we filled
  970.  up the structure, we would write out a new user label to our
  971.  self-describing file.
  972.  
  973.  
  974.  Finishing Up
  975.  
  976.  After adding all the fields, we have to see if there is one label
  977.  record that has not been written to the Loadfile.  If so, we
  978.  write it out.  Finally, the header record is written out.   We do
  979.  this last, since some of the variables used by put'field were
  980.  ones from the header record.
  981.  
  982.  
  983.  Closing the Self-Describing File
  984.  
  985.  Our next routine closes the self-describing file and handles any
  986.  errors from Fclose.  Pretty straight-forward MPE programming:
  987.  
  988.  $page "sd'close"
  989.  <<  Close the loadfile and check for duplicate output files.
  990.  >>
  991.  
  992.  logical procedure sd'close(outfile,loadfile'filenum);
  993.     value   loadfile'filenum;
  994.     integer loadfile'filenum;
  995.     integer array outfile;
  996.     option check 3;
  997.  begin
  998.  $include localvar.srcinc
  999.  
  1000.     sd'close := false;
  1001.  
  1002.     fclose(loadfile'filenum,2,0);   ! Save temp
  1003.     if <> then
  1004.     begin
  1005.        error(outfile,11);
  1006.        xfileinfo(loadfile'filenum);
  1007.     end'if
  1008.     else
  1009.        sd'close := true;
  1010.  
  1011.  end'proc;   <<sd'close>>
  1012.  
  1013.  
  1014.  Providing a Shell
  1015.  
  1016.  To make life easier in HowMessy, we provide one routine for the
  1017.  main module to call.  This routine purges any exiting temporary
  1018.  Loadfile, creates our new Loadfile, writes out the self-
  1019.  describing information, and saves the Loadfile.  The controlling
  1020.  HowMessy routine then reopens Loadfile with write-access (this
  1021.  may seem inefficient, but HowMessy is written in both SPL/SPLash!
  1022.  and HP Pascal, so it was easier to organize the code this way):
  1023.  
  1024.  $page "sdcreate"
  1025.  <<  Create Loadfile with all the self-describing information.  We
  1026.      purge any existing file called Loadfile, create a temporary
  1027.      one, and then fill in the labels.
  1028.  >>
  1029.  
  1030.  integer procedure sdcreate(outfile);
  1031.     integer array outfile;
  1032.     option check 3;
  1033.  begin
  1034.  $include localvar.srcinc
  1035.  $include mpecmd.srcinc
  1036.  
  1037.     integer
  1038.        loadfile'filenum
  1039.       ;
  1040.  
  1041.     logical subroutine purge'loadfile;
  1042.     begin
  1043.        purge'loadfile := false;
  1044.        say'str "purge loadfile,temp";
  1045.        say'add rtn;
  1046.        if mpecmd'execute(outbuf,mpe'print'buffer) then
  1047.           purge'loadfile := true
  1048.        else
  1049.           error(outfile,9);
  1050.     end'subr;   <<purge'loadfile>>
  1051.  $page "sdcreate/mainline"
  1052.  
  1053.     sdcreate := 0;
  1054.  
  1055.     if purge'loadfile then
  1056.        if sd'open(outfile,loadfile'filenum) then
  1057.           if sd'write'labels(outfile,loadfile'filenum) then
  1058.              if sd'close(outfile,loadfile'filenum) then
  1059.                 sdcreate := 1;
  1060.  
  1061.  end'proc;   <<sdcreate>>
  1062.  
  1063.  
  1064.  
  1065.  
  1066.              Understanding Self-Describing Information
  1067.  
  1068.  
  1069.  
  1070.  Our HowMessy example showed the form of the Loadfile using a
  1071.  format similar to the one Query uses, but the input was an MPE
  1072.  self-describing file instead of an IMAGE dataset.  Here is our
  1073.  example form again:
  1074.  
  1075.      File: LOADFILE.GROUP.ACCT     (SD Version B.00.00)
  1076.         Entry:                     Offset
  1077.            DATABASE             X26     1
  1078.            DATASET              X16    27
  1079.            DATASETNUM           I1     43
  1080.            DATASETTYPE          X4     45
  1081.            CAPACITY             I2     49
  1082.            ENTRIES              I2     53
  1083.            LOADFACTOR           I2     57           << .2  >>
  1084.            SECONDARIES          I2     61           << .2  >>
  1085.            MAXBLOCKS            I2     65
  1086.            HIGHWATER            I2     69
  1087.            PATHSORT             X1     73
  1088.            PATHPRIMARY          X1     74
  1089.            BLOCKFACTOR          I1     75
  1090.            SEARCHFIELD          X16    77
  1091.            MAXCHAIN             I2     93
  1092.            AVECHAIN             I2     97           << .2  >>
  1093.            STDDEVIATION         I2    101           << .2  >>
  1094.            EXPECTEDBLOCKS       I2    105           << .2  >>
  1095.            AVERAGEBLOCKS        I2    109           << .2  >>
  1096.            INEFFICIENTPTRS      I2    113           << .2  >>
  1097.            ELONGATION           I2    117           << .2  >>
  1098.            FUTUREFIELDS         X136  121
  1099.      Limit: 10000  EOF: 33  Entry Length: 256  Blocking: 35
  1100.  
  1101.  
  1102.  Formselfdesc Procedure
  1103.  
  1104.  We have developed a stand-alone procedure for producing this
  1105.  output for a self-describing file.  The following is the source
  1106.  code that we use:
  1107.  
  1108.  $page "formselfdesc"
  1109.  <<  If the passed filenum is a self-describing file, print a
  1110.      description of the fields in the file on $stdlist.
  1111.  >>
  1112.  
  1113.  logical procedure formselfdesc(sd'filenum);
  1114.     value   sd'filenum;
  1115.     integer sd'filenum;
  1116.     option check 3;
  1117.  begin
  1118.  $include localvar.srcinc
  1119.  $include sdequate.srcinc
  1120.  
  1121.     integer
  1122.        file'code
  1123.       ,file'userlabels
  1124.       ,file'foptions
  1125.       ,current'labelnum
  1126.       ,field'index
  1127.       ,file'recsize
  1128.       ,file'blkfac
  1129.       ;
  1130.     double
  1131.        file'eof
  1132.       ,file'limit
  1133.       ;
  1134.     byte array
  1135.        filename(0:bl'local'filename)
  1136.       ;
  1137.  
  1138.  $include sdheader.srcinc
  1139.  $include sdfield.srcinc
  1140.  
  1141.     integer array current'label(0:sd'label'len);
  1142.  
  1143.     subroutine file'error;
  1144.     begin
  1145.        xfileinfo(sd'filenum);
  1146.        goto error'exit;
  1147.     end'subr;   <<file'error>>
  1148.  
  1149.     subroutine read'label(labelnum);
  1150.        value   labelnum;
  1151.        integer labelnum;
  1152.     begin
  1153.        freadlabel(sd'filenum,current'label,sd'label'len,labelnum);
  1154.        if <> then
  1155.        begin
  1156.           p "Unable to read label from self-describing file" err;
  1157.           file'error;
  1158.        end'if;
  1159.     end'subr;   <<read'label>>
  1160.  
  1161.     subroutine file'info(blksize);
  1162.        value   blksize;
  1163.        integer blksize;
  1164.     begin
  1165.        b'blank(filename,bl'local'filename);
  1166.        fgetinfo(sd'filenum      <<filenum          iv>>
  1167.          ,filename           <<filename         ba>>
  1168.          ,file'foptions      <<foptions         l >>
  1169.          ,                   <<aoptions         l >>
  1170.          ,file'recsize       <<recsize          i >>
  1171.          ,                   <<devtype          i >>
  1172.          ,                   <<ldnum            l >>
  1173.          ,                   <<hdaddr           l >>
  1174.          ,file'code          <<filecode         i >>
  1175.          ,                   <<recptr           d >>
  1176.          ,file'eof           <<eof              d >>
  1177.          ,file'limit         <<flimit           d >>
  1178.          ,                   <<logcount         d >>
  1179.          ,                   <<physcount        d >>
  1180.          ,blksize            <<blksize          i >>
  1181.          ,                   <<extsize          l >>
  1182.          ,                   <<numextents       i >>
  1183.          ,file'userlabels    <<userlabels       i >>
  1184.          );
  1185.        if <> then
  1186.        begin
  1187.           p "Unable to fgetinfo on file" err;
  1188.           file'error;
  1189.        end'if;
  1190.        if file'recsize <> 0 then
  1191.           file'blkfac := blksize / file'recsize
  1192.        else
  1193.           file'blkfac := 1;
  1194.        if file'recsize < 0 then
  1195.           file'recsize := file'recsize
  1196.        else
  1197.           file'recsize := file'recsize * 2;
  1198.     end'subr;   <<file'info>>
  1199.  
  1200.     logical subroutine file'is'sd;
  1201.     begin
  1202.        file'is'sd := false;
  1203.        file'info(0);
  1204.        if file'code = 1084 and file'userlabels <> 0 then
  1205.           file'is'sd := true
  1206.        else
  1207.        if file'foptions.(2:3) = 1 or file'foptions.(2:3) = 3 then
  1208.           if file'userlabels > 1 then
  1209.           begin   <<ksam with extra user labels>>
  1210.              read'label(file'userlabels-1);
  1211.              move sd'header := current'label,(sd'label'len);
  1212.              if sd'version = " A.00.00" or
  1213.                 sd'version = " B.00.00" then
  1214.                 file'is'sd := true;
  1215.           end'else;
  1216.     end'subr;   <<file'is'sd>>
  1217.  
  1218.     logical subroutine get'field(offset);
  1219.        value   offset;
  1220.        integer offset;
  1221.     begin
  1222.        get'field := false;
  1223.        if field'index < sd'numfields then
  1224.        begin
  1225.           if (field'index mod sd'fieldsperlabel) = 0 then
  1226.           begin
  1227.              current'labelnum := current'labelnum - 1;
  1228.              read'label(current'labelnum);
  1229.           end'if;
  1230.           offset := (field'index mod sd'fieldsperlabel) *
  1231.                     sd'entrylen;
  1232.           move sd'field := current'label(offset),(sd'entrylen);
  1233.           field'index   := field'index + 1;
  1234.           get'field := true;
  1235.        end'if;
  1236.     end'subr;   <<get'field>>
  1237.  
  1238.     subroutine print'outbuf(len);
  1239.        value   len;
  1240.        integer len;
  1241.     begin
  1242.        len := bl'outbuf;
  1243.        while len > 0 and outbuf'(len-1) = " " do
  1244.           len := len - 1;
  1245.        print(outbuf,-len,0);
  1246.     end'subr;   <<print'outbuf>>
  1247.  
  1248.     subroutine print'header(len);
  1249.        value   len;
  1250.        integer len;
  1251.     begin
  1252.        b'blank(outbuf,bl'outbuf);
  1253.        move outbuf'(4) := "File: ";
  1254.        move outbuf'(10) := filename,(bl'local'filename);
  1255.        len := bl'outbuf;
  1256.        while len > 0 and outbuf'(len-1) = " " do
  1257.           len := len - 1;
  1258.        len := len + 5;
  1259.        len := len + move outbuf'(len) := "(SD Version";
  1260.        len := len + move outbuf'(len) := sd'version,(8);
  1261.        len := len + move outbuf'(len) := ")";
  1262.        print'outbuf(0);
  1263.        b'blank(outbuf,bl'outbuf);
  1264.        move outbuf'(7) := "Entry:";
  1265.        move outbuf'(34) := "Offset";
  1266.        print(outbuf,-50,0);
  1267.     end'subr;   <<print'header>>
  1268.  
  1269.     subroutine print'trailer(len);
  1270.        value   len;
  1271.        integer len;
  1272.     begin
  1273.        b'blank(outbuf,bl'outbuf);
  1274.        len := move outbuf' := "    ";
  1275.        len := len + move outbuf'(len) := "Limit: ";
  1276.        len := len + dascii(file'limit,10,outbuf'(len));
  1277.        len := len + move outbuf'(len) := "  EOF: ";
  1278.        len := len + dascii(file'eof,10,outbuf'(len));
  1279.        len := len + move outbuf'(len) := "  Entry Length: ";
  1280.        len := len + ascii(file'recsize,10,outbuf'(len));
  1281.        len := len + move outbuf'(len) := "  Blocking: ";
  1282.        len := len + ascii(file'blkfac,10,outbuf'(len));
  1283.        print(outbuf,-len,0);
  1284.     end'subr;  <<print'trailer>>
  1285.  
  1286.     subroutine format'field'type;
  1287.     begin
  1288.        if 0 <= sd'field'type <= 9 then
  1289.           case sd'field'type of begin
  1290.           <<0>> move outbuf'(31) := "?";
  1291.           <<1>> move outbuf'(31) := "X";
  1292.           <<2>> move outbuf'(31) := "?";
  1293.           <<3>> move outbuf'(31) := "I";
  1294.           <<4>> move outbuf'(31) := "R";
  1295.           <<5>> move outbuf'(31) := "P";
  1296.           <<6>> move outbuf'(31) := "J";
  1297.           <<7>> move outbuf'(31) := "K";
  1298.           <<8>> move outbuf'(31) := "Z";
  1299.           <<9>> move outbuf'(31) := "E";
  1300.           end'case
  1301.        else
  1302.           move outbuf'(31) := "?";
  1303.     end'subr;   <<format'field'type>>
  1304.  
  1305.     logical subroutine field'is'sorted(sort'index);
  1306.        value   sort'index;
  1307.        integer sort'index;
  1308.     begin
  1309.        field'is'sorted := false;
  1310.        if sd'field'offset + 1 = sd'sort'keys(sort'index*3) and
  1311.           sd'field'bytelen    = sd'sort'keys(sort'index*3+1) then
  1312.              field'is'sorted := true;
  1313.     end'subr;   <<field'is'sorted>>
  1314.  
  1315.     subroutine format'sort'key(sort'index);
  1316.        value   sort'index;
  1317.        integer sort'index;
  1318.     begin
  1319.        sort'index := 0;
  1320.        while sort'index < sd'sort'num'keys do
  1321.        begin
  1322.           if field'is'sorted(sort'index) then
  1323.           begin
  1324.              move outbuf'(42) := "<<Sort ";
  1325.              ascii(sort'index+1,10,outbuf'(50));
  1326.              move outbuf'(52) := ">>";
  1327.           end'if;
  1328.           sort'index := sort'index + 1;
  1329.        end'while;
  1330.     end'subr;   <<format'sort'key>>
  1331.  
  1332.     subroutine format'date'type;
  1333.     begin
  1334.        if 1 <= sd'field'date'type <= 10 then
  1335.           case sd'field'date'type of begin
  1336.           <<0>> ;
  1337.           <<1>> move outbuf'(56) := "<<YYMMDD>>";
  1338.           <<2>> move outbuf'(56) := "<<DDMMYY>>";
  1339.           <<3>> move outbuf'(56) := "<<MMDDYY>>";
  1340.           <<4>> move outbuf'(56) := "<<YYMM>>";
  1341.           <<5>> move outbuf'(56) := "<<CALENDAR>>";
  1342.           <<6>> move outbuf'(56) := "<<YYYYMMDD>>";
  1343.           <<7>> move outbuf'(56) := "<<DDMMYYYY>>";
  1344.           <<8>> move outbuf'(56) := "<<MMDDYYYY>>";
  1345.           <<9>> move outbuf'(56) := "<<PHDATE>>";
  1346.           <<10>>move outbuf'(56) := "<<ASK>>";
  1347.           end'case;
  1348.     end'subr;   <<format'date'type>>
  1349.  
  1350.     subroutine format'decplaces;
  1351.     begin
  1352.        if sd'field'decplaces > 0 then
  1353.        begin
  1354.           move outbuf'(56) := "<< .";
  1355.           ascii(sd'field'decplaces,10,outbuf'(60));
  1356.           move outbuf'(63) := ">>";
  1357.        end'if;
  1358.     end'subr;   <<format'decplaces>>
  1359.  
  1360.     subroutine print'field'desc(field'repeat);
  1361.        value   field'repeat;
  1362.        integer field'repeat;
  1363.     begin
  1364.        b'blank(outbuf,bl'outbuf);
  1365.        move outbuf'(10) := sd'field'name,(16);
  1366.        if sd'version = " B.00.00" then
  1367.        begin
  1368.           field'repeat     := sd'field'repeat;
  1369.           sd'field'bytelen := sd'field'bytelen / field'repeat;
  1370.        end'if
  1371.        else
  1372.           field'repeat := 1;
  1373.        if field'repeat <> 1 then
  1374.           ascii(field'repeat,-10,outbuf'(30));
  1375.        format'field'type;
  1376.        if sd'field'type = 3 or    <<integer>>
  1377.           sd'field'type = 4 or    <<real   >>
  1378.           sd'field'type = 7 then  <<logical>>
  1379.           ascii(sd'field'bytelen/2,10,outbuf'(32))
  1380.        else
  1381.        if sd'field'type = 5 then  <<packed>>
  1382.           ascii(sd'field'bytelen*2,10,outbuf'(32))
  1383.        else
  1384.           ascii(sd'field'bytelen,10,outbuf'(32));
  1385.        ascii(sd'field'offset+1,-10,outbuf'(39));
  1386.        if sd'version = " B.00.00" then
  1387.        begin
  1388.           format'sort'key(0);
  1389.           format'date'type;
  1390.           format'decplaces;
  1391.        end'if;
  1392.        print'outbuf(0);
  1393.     end'subr;   <<print'field'desc>>
  1394.  $page "formselfdesc/mainline"
  1395.  
  1396.     formselfdesc := false;
  1397.  
  1398.     if sd'filenum <> 0 then
  1399.     begin
  1400.        if file'is'sd then
  1401.        begin
  1402.           read'label(file'userlabels-1);
  1403.           move sd'header := current'label,(sd'label'len);
  1404.           current'labelnum := file'userlabels - 1;
  1405.           print'header(0);
  1406.           field'index      := 0;
  1407.           while get'field(0) do
  1408.              print'field'desc(0);
  1409.           print'trailer(0);
  1410.           formselfdesc := true;
  1411.        end'if;
  1412.     end'if;
  1413.  
  1414.  error'exit:
  1415.  
  1416.  end'proc;   <<formselfdesc>>
  1417.  
  1418.  
  1419.  A Different Logical Structure
  1420.  
  1421.  Our HowMessy/Loadfile example had a number of separate
  1422.  procedures.  Our Formselfdesc procedure is self-contained, but we
  1423.  will describe each subroutine in this procedure.
  1424.  
  1425.  
  1426.  Formselfdesc Variables
  1427.  
  1428.  We include our standard files for the global self-describing
  1429.  equates, header layout, and field layout.  We also have a number
  1430.  of local variables that are used for indexing through the field
  1431.  labels and other variables needed to enhance the output listing
  1432.  (e.g., the number of records in the self-describing file).
  1433.  
  1434.  
  1435.  File'Is'Sd
  1436.  
  1437.  This is our standard sd'file procedure, rewritten to work as a
  1438.  stand-alone SPL subroutine.  File'Is'Sd looks after calling the
  1439.  file'info subroutine which calls Fgetinfo.  We initialize a
  1440.  number of variables during the file'info call.  Some of these are
  1441.  used for obtaining self-describing information and some are used
  1442.  to enhance the format of our form output (e.g., the filename and
  1443.  the file limit).
  1444.  
  1445.  
  1446.  Read'Label
  1447.  
  1448.  The basic strategy we use in this routine is to read a specific
  1449.  label into a buffer called current'label.  We then move this
  1450.  label to the appropriate self-describing header or field buffer.
  1451.  Read'label is careful to check for file system errors and abort
  1452.  if it finds any.
  1453.  
  1454.  
  1455.  Get'Field
  1456.  
  1457.  This subroutine is the key to understanding self-describing
  1458.  files.  When get'field is called the last label of the file has
  1459.  been read into the sd'header record.  The variable field'index is
  1460.  initialized to zero is used as a counter of self-describing
  1461.  files.  Each call to get'field returns one field description in
  1462.  the sd'field record.
  1463.  
  1464.  When first called, current'labelnum contains the number of the
  1465.  last label (minus one, since MPE numbers labels starting at
  1466.  zero).  We check to see if we need to read in a new label with
  1467.  the statement:
  1468.  
  1469.       if (field'index mod sd'fieldsperlabel) = 0 then
  1470.  
  1471.  Note that we use sd'fieldsperlabel as the divisor.  This is the
  1472.  value from our sd'header record and not our equate that we use
  1473.  when creating self-describing files.  Get'Field assumes that the
  1474.  current label record is in the buffer current'label.
  1475.  
  1476.  Each user label contains one or more field descriptions (in most
  1477.  cases there are eight per label).  We compute an offset in the
  1478.  label where the current field description is and then we move the
  1479.  field description from current'label to our sd'field record.
  1480.  
  1481.  
  1482.  Print'Field'Desc
  1483.  
  1484.  This routine looks after printing out the description of one
  1485.  field.  We use the same routine whether we are dealing with Query
  1486.  (" A.00.00") or Robelle (" B.00.00") self-describing files.  We
  1487.  do have to adjust the byte length for " B.00.00" self-describing
  1488.  fields, so that the output looks similar to what Query would
  1489.  produce for an IMAGE dataset.  Note how the format'type routine
  1490.  handles IEEE floating point for either type of self-describing
  1491.  file.
  1492.  
  1493.  For " B.00.00" self-describing files, we can produce extra
  1494.  information.  This is handled by the format'sort'key,
  1495.  format'date'type, and format'decplaces routines (which are only
  1496.  called for " B.00.00" self-describing files.
  1497.  
  1498.  
  1499.  Format'Sort'Key
  1500.  
  1501.  The sort information is stored in the sd'header record as an
  1502.  offset, a length, and a type.  There is no direct way for us to
  1503.  tell that a field is sorted.  Instead, we index through all of
  1504.  the sort keys checking if the sort key matches the current field
  1505.  definition (there might not be a match).  We use the index into
  1506.  the sort information as our key to print for the user.
  1507.  
  1508.  
  1509.  Field'Is'Sorted
  1510.  
  1511.  To make our code clearer, we encapsulate the code for checking if
  1512.  a specific sort key matches the current field in a subroutine.
  1513.  By giving this subroutine a descriptive name, we make the intent
  1514.  of the format'sort'key routine clearer.  Our field'is'sorted
  1515.  routine checks that the offset (adjusted appropriately for
  1516.  one-based and zero-based offsets) and the byte length of the
  1517.  field and the sort key match.  We decided to ignore the data type
  1518.  (the sd'type and the sort'type have different values).
  1519.  
  1520.  
  1521.  Summary
  1522.  
  1523.  It's harder to understand self-describing files than it is to
  1524.  create them.  When creating self-describing files you often only
  1525.  use a few of the self-describing features, but when understanding
  1526.  them there are no features that you can leave out.
  1527.  
  1528.  
  1529.  
  1530.  
  1531.  
  1532.                     KSAM Self-Describing Files
  1533.  
  1534.  
  1535.  
  1536.  Self-describing KSAM files are a little trickier to deal with.
  1537.  The 1084 filecode used for self-describing MPE files doesn't work
  1538.  well for KSAM.  It is more difficult to create a new KSAM file,
  1539.  since all of the key information must be passed to Fopen.  Here
  1540.  are a few hints for creating and understanding self-describing
  1541.  KSAM files.
  1542.  
  1543.  
  1544.  SD (1084) Filecode
  1545.  
  1546.  You can create a KSAM file with a filecode of 1084, but the
  1547.  resulting :listf,2 gives no hint that the file is a KSAM file.
  1548.  Here's an example Build Command of a compatibility-mode KSAM file
  1549.  with a filecode of 1084 and the resulting :listf,2.
  1550.  
  1551.  :run ksamutil.pub.sys
  1552.  >build file1;rec=-80,16,f,ascii;keyfile=file1key; &
  1553.               key=i,6,2;code=1084
  1554.  >exit
  1555.  :listf file1@,2
  1556.  
  1557.  FILENAME  CODE  ----------LOGICAL RECORD---------  ----SPACE----
  1558.                    SIZE  TYP      EOF    LIMIT R/B  SECTORS X MX
  1559.  
  1560.  FILE1     SD       80B  FA         0     1023  16       48  1  *
  1561.  FILE1KEY  KSAMK   128W  FB        98       98   1      112  1  8
  1562.  
  1563.  Notice how there is no way to identify file1 as being a KSAM
  1564.  file.  For this reason, we don't use the 1084 filecode on
  1565.  self-describing KSAM files.
  1566.  
  1567.  
  1568.  Creating KSAM Self-describing Files
  1569.  
  1570.  We use three steps to create self-describing KSAM files:
  1571.  
  1572.  1. Compute the number of labels (used in the KSAMUTIL or MPE/iX
  1573.     build command).  You could use our sd'compute'labels
  1574.     subroutine or you can compute the number of labels as the
  1575.     truncated value of:
  1576.  
  1577.         labels = (#fields + 7) / 8 + 11
  1578.  
  1579.  2. Build your KSAM/V file with KSAMUTIL and specify Labels=[the
  1580.     number computed above].  For KSAM/XL, use the Build Command
  1581.     with the userlabel keyword ;ULABEL=x (where x is the number
  1582.     computed above).
  1583.  
  1584.  3. Fopen the file as an old file with write access.
  1585.  
  1586.  The most difficult part is computing the number of labels.  For
  1587.  example, if we have eight fields:
  1588.  
  1589.       Labels = (8 + 7) / 8 + 11
  1590.              = 12
  1591.     MPE V/E:
  1592.  
  1593.       :run ksamutil.pub.sys
  1594.       >build file2;rec=-80,16,f,ascii;keyfile=file2k; &
  1595.        key=i,6,2,,duplicate;labels=12
  1596.       >exit
  1597.  
  1598.     MPE/iX:
  1599.  
  1600.       :build file2;rec=-80,16,f,ascii;key=(i,6,2,dup); &
  1601.                    ksamxl;ulabel=12
  1602.  
  1603.  
  1604.  
  1605.  Understanding KSAM Self-Describing Files
  1606.  
  1607.  Our sd'file routine returns true if a given file is
  1608.  self-describing.  If you examine the code in this routine
  1609.  carefully, you'll see that for KSAM files we have the statements:
  1610.  
  1611.       if file'foptions.(2:3) = 1 or file'foptions.(2:3) = 3 then
  1612.          if file'userlabels > 1 then
  1613.  
  1614.  Note that we check for more than one user label.  Why don't we
  1615.  check for more than zero user labels?  All self-describing files
  1616.  must have at least two labels (one for the header information and
  1617.  one or more for the field information).  When we first
  1618.  implemented our sd'file routine we only checked for more than
  1619.  zero user labels.  What we found was that many users had
  1620.  accidentally built KSAM files with one user label (which was
  1621.  almost always empty).  We have no idea why this seemed to be so
  1622.  common, but by checking for at least two labels we eliminated a
  1623.  lot of KSAM files that were not self-describing.
  1624.  
  1625.  
  1626.                   Future Self-Describing Formats
  1627.  
  1628.  
  1629.  
  1630.  We were motivated to create the new Robelle format
  1631.  self-describing files in order to provide a better interface
  1632.  between our product Suprtool and ASKPlus from ARES of France.
  1633.  Pierre Senant of ARES is the R&D Manager and the two of us worked
  1634.  out the " B.00.00" self-describing format (actually we forced
  1635.  most of the format on poor Pierre).
  1636.  
  1637.  ARES have been doing significant R&D work on UNIX and a portable
  1638.  version of ASKPlus.  As an example of how far we can go with
  1639.  self-describing information, here is an extract of Pierre's
  1640.  design for a UNIX implementation of self-describing files.
  1641.  
  1642.  
  1643.  SDASK Files
  1644.  
  1645.  A C/ISAM file is composed of two files, a data file and an index
  1646.  file.  An SDASK file defines another file called a 'label file'
  1647.  which contains the complete description of the data file.  Like
  1648.  MPE self-describing files, an SDASK file is composed of a header
  1649.  portion and a description of each field.  The data file and the
  1650.  label file must be located in the same directory.
  1651.  
  1652.  
  1653.  Header Format
  1654.  
  1655.  Pierre's header contains a lot more information than our MPE
  1656.  header label.  Here are the parts of the header:
  1657.  
  1658.  *  Version number.
  1659.  
  1660.  *  File code (1085).
  1661.  
  1662.  *  Checksum (currently unused).
  1663.  
  1664.  *  Number of fields per record.
  1665.  
  1666.  *  Record length (in bytes).
  1667.  
  1668.  *  Number of records.
  1669.  
  1670.  *  Number of sort keys.
  1671.  
  1672.  *  Password.
  1673.  
  1674.  *  Total field area length (in the SDASK file).
  1675.  
  1676.  *  File type (flat, C/ISAM, KSAM, Unibol, ...).
  1677.  
  1678.  *  Data file name.
  1679.  
  1680.  *  Unibol area (for migration from IBM/36 to UNIX)
  1681.  
  1682.  *  Filter:  logical expression defining a condition that must be
  1683.     True for the entries taken into account.  Originally developed
  1684.     for Unibol files, but this feature can be used for any other
  1685.     system.
  1686.  
  1687.  
  1688.  Field Description
  1689.  
  1690.  Each field description is variable length:
  1691.  
  1692.  *  Field type (U, X, I, J, K, P, R).  Additional information for
  1693.     Ascii fields are: Roman-8, PC-8, ANSI-8, Mac-Apple, EBCIDC,
  1694.     and ISO7-1 ... ISO7-13.  For Integer fields, there is
  1695.     additional information for Intel versus HP.  For Real fields,
  1696.     there is additional information for IEEE versus Classic.
  1697.  
  1698.  *  Length (in bytes).
  1699.  
  1700.  *  Offset.
  1701.  
  1702.  *  Scale (number of decimal places).
  1703.  
  1704.  *  Repeat factor.
  1705.  
  1706.  *  Flags:
  1707.  
  1708.     *  Null value allowed.  If this flag is True, each entry in
  1709.        the data file is preceded by a bitmap field.  Each bit
  1710.        indicates whether the corresponding field value is Null or
  1711.        not.
  1712.  
  1713.     *  Hidden field.
  1714.  
  1715.     *  Key.
  1716.  
  1717.     *  Duplicate key allowed.
  1718.  
  1719.  *  Field name length.
  1720.  
  1721.  *  Field name.
  1722.  
  1723.  *  Title length.
  1724.  
  1725.  *  Title.
  1726.  
  1727.  *  Edit mask length.
  1728.  
  1729.  *  Edit mask.
  1730.  
  1731.  *  Key file name length.
  1732.  
  1733.  *  Key file (reserved for future implementation on MS-Dos).
  1734.  
  1735.  
  1736.  Sort Information
  1737.  
  1738.  Sort descriptors are also variable length:
  1739.  
  1740.  *  Expression length.
  1741.  
  1742.  *  Sort expression (in ASKPlus syntax).  For example,
  1743.          cust-name
  1744.  
  1745.          cust-zipcode cat cust-address
  1746.  
  1747.  *  Flag:  ascending/descending.
  1748.  
  1749.  
  1750.                             Conclusion
  1751.  
  1752.  
  1753.  
  1754.  Self-describing files are a great idea.  As users, we almost
  1755.  always create MPE and KSAM files with a fixed record structure in
  1756.  mind.  By default, this record structure is lost when we build a
  1757.  file.  With self-describing files, we can retain the structure of
  1758.  our files.
  1759.  
  1760.  
  1761.  A Final Example
  1762.  
  1763.  The HP 3000 has a rich set of tools based on IMAGE.  One reason
  1764.  that so many good tools could be written for IMAGE was the DBINFO
  1765.  intrinsic.  This intrinsic let any program discover the structure
  1766.  of an IMAGE database.  Self-describing files provide the same
  1767.  flexibility for MPE and KSAM files.
  1768.  
  1769.  In this example, we show how two tools can be combined by using
  1770.  self-describing files.  Our HowMessy program reports on database
  1771.  efficiency.  While doing so it creates a self-describing file
  1772.  with the statistics for a database.  Once you have this file,
  1773.  it's possible to use Suprtool to check for certain boundary
  1774.  cases.  For example,
  1775.  
  1776.       :run howmessy.pub.robelle          {create "loadfile"}
  1777.  
  1778.       Enter database:  test.suprtest
  1779.  
  1780.  HowMessy creates the self-describing file called Loadfile (with
  1781.  the structure that we've shown previously).  We now use Suprtool
  1782.  to create a file that has all detail datasets that are more than
  1783.  85% full that also have a capacity greater than one:
  1784.  
  1785.       :run suprtool.pub.robelle
  1786.       >input   loadfile
  1787.       >if      datasettype = "D"  and   &   {detail dataset}
  1788.                capacity    > 1    and   &
  1789.                loadfactor  > 85.00          {more than 85% full}
  1790.       >output  loaddetl,link                {create SD file}
  1791.       >exit
  1792.  
  1793.  At Robelle, we would use our Xpress electronic mail system to
  1794.  mail the loaddetl file to the system manager.  Another
  1795.  alternative would be to extract the database and dataset names
  1796.  and use them to create a batch job to automatically increase the
  1797.  capacity of detail datasets more than 85% full.  The
  1798.  possibilities are endless, but only because HowMessy could
  1799.  provide information to Suprtool via the self-describing file.
  1800.  
  1801.  
  1802.  Software Tools
  1803.  
  1804.  Few software tools are capable of creating or understanding
  1805.  self-describing files.  This is a shame, since self-describing
  1806.  files are a powerful data structure.  One reason that so few
  1807.  tools handle self-describing files is that documentation on
  1808.  self-describing files has been non-existent.  I hope that by
  1809.  publishing this description and the programming examples in this
  1810.  paper that more vendors and users start creating and accepting
  1811.  self-describing files.
  1812.