home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ftp.robelle3000.ai 2014
/
2014.06.ftp.robelle3000.ai.tar
/
ftp.robelle3000.ai
/
papers
/
sd.pro
< prev
next >
Wrap
Text File
|
1995-08-31
|
70KB
|
2,097 lines
.comment File: SD
.comment
.comment Purpose: Adopting Self-Describing Files
.comment
.comment To print this document, do the following:
.comment
.comment :run printdoc.pub.robelle;info="SD"
.comment
.com {ifout starts here}
.comment Choose the output device parameters for this document
.if outfinal
. if outrecord
. out(las 30 c1 r+ s7).com Robelle bound version, attached
. else
. out(las 30 c1 r- s7).com Robelle bound version
. endif
.elseif outhelpcomp
. out(lpt q+ w80).com helpcomp;parm=1
.elseif outa4
. if outlpt
. if outrecord
. out(lpt s7 r+).com A4 paper, $Stdlist/LP/disc, attached
. else
. out(lpt s7 r-).com A4 paper, $Stdlist/LP/disc
. endif
. elseif outlaser
. if outrecord
. if outdouble
. out(las 30 s8 r+ d+).com A4 paper, LaserJet, attached, duplex
. else
. out(las 30 s8 r+).com A4 paper, LaserJet, attached
. endif
. else
. if outdouble
. out(las 30 s8 r- d+).com A4 paper, LaserJet, duplex
. else
. out(las 30 s8 r-).com A4 paper, LaserJet
. endif
. endif
. else.com No other outxxx jcws specified
. out(lpt s7 r-).com generic: A4 paper, $Stdlist/LP/disc
. endif
.elseif outtext
. if outrecord
. out(lpt s0 u- r+)
. else
. out(lpt s0 u-)
. endif
.else
. if outlpt
. if outrecord
. out(lpt s3 r+).com Letter, $Stdlist/LP/disc, attached
. else
. out(lpt s3 r-).com Letter, $Stdlist/LP/disc
. endif
. elseif outlaser
. if outrecord
. if outdouble
. out(las 30 s7 r+ d+).com Letter, LaserJet, attached, duplex
. else
. out(las 30 s7 r+).com Letter, LaserJet, attached
. endif
. else
. if outdouble
. out(las 30 s7 r- d+).com Letter, LaserJet, duplex
. else
. out(las 30 s7 r-).com Letter, LaserJet
. endif
. endif
. else.com No outxxx jcws specified
. out(lpt s3 r-).com Letter, generic: $Stdlist/LP/disc
. endif
.endif
.comment
.comment Choose the fonts for this document
.if outfinal
. include final.qlibdata.green
.else
. include f92286f.qlibdata.robelle
.endif
.comment
.comment Choose the margins for this document
.comment
.if outhelpcomp
. mar(r78)
.elseif outa4
. mar(r62)
.else
. mar(r65)
.endif
.comment
.comment Choose the forms for this document
.comment
.comment Form 1 is for the body of the manual
.comment Form 2 is for unnumbered pages (end of section)
.comment Form 3 is for the table of contents (roman numerals)
.comment
.if outtext
. form(k1 [L8000] )
. form(k2 [L8000] )
. form(k3 [L8000] )
.elseif outa4
. form(k1 [ T #23 S:40 // l58 / #33 "-" pn:1 "-" /]
+ [ S #23 T:40 // l58 / #33 "-" pn:1 "-" /])
. form(k2 [ // l58 / #33 pr:3 /]).com Roman numerals
. form(k3 [ // l57 // ])
.else
. form(k1 [ T #26 S:40 // l55 / #33 "-" pn:1 "-" /]
+ [ S #26 T:40 // l55 / #33 "-" pn:1 "-" /])
. form(k2 [ // l55 / #33 pr:3 /]).com Roman numerals
. form(k3 [ // l54 // ])
.endif
.comment
.comment Option settings:
.comment
.comment For text output we want a ragged right edge.
.comment
.if outtext
. opt(j4 p+ b+ r-)
.else
. opt(j4 p+ b+).com Okay to insert four blanks between words
.comment when justifying, two spaces after period,
.comment suppress blank lines at top of page
.endif
.comment
.comment Other formatting parameters:
.par(f` p5 s1 u3) .com Automatic .skip 1.page 5.undent 3
.inp(u~ b@ h\ e& t# f|).com Underline,Blank,Hyphen,Escape,Tab,Font
.skip 5
.opt(l- r- f-)
|3Adopting Self-Describing Files|
|1By David J.@Greer|
|1Robelle Consulting Ltd.|
Unit 201, 15399-102A Ave.
Surrey, B.C.@Canada V3R 7K1
Phone:@@(604) 582-1700
Fax:@@(604) 582-1799
http://www.robelle.com
.font 0.opt.skip 2
.opt(l- r- f-)
`|3Abstract|
.opt.skip 1
Query can generate output to self-describing (SD) files, but no
HP products read these files except the old DSG and Listkeeper
products. A self-describing file is a data file which stores a
standard description of its own record format in its user labels.
Thus, it is like a little stand-alone database (a trendy
developer might call this object-oriented). If two software tools
understand SD files, it becomes trivial to transfer data between
them. A user can archive some data in an SD file and when it is
restored five years later the SD file can tell what the data
means. The author describes the internal format of SD files,
gives examples on how to read and write SD files, and describes
problems integrating SD files into a software tool.
.skip 5.opt(l- r- f-)
Copyright Robelle Consulting Ltd.@1992
.opt
Permission is granted to reprint this document (but ~not~ for
profit), provided that copyright notice is given.
.com The next FORM is for Contents/Preface:
.page.for([ // l55 / #33 pr:3 /]).com Roman numerals
.com The next FORM is for the body of manual:
.if outdouble
.page
.endif
.for([ T #26 S:40 // l55 / #33 "-" pn:1 "-" /]
+ [ S #26 T:40 // l55 / #33 "-" pn:1 "-" /])
.page.count 1 .com Start page numbering over again.
.contents(i+3)
.tit
.page.sub
.skip 3.opt(l- r- f-)
`|3Introduction|
.opt.skip 2
.tit Adopting Self-Describing Files
.ent `Introduction
For years, Query's Save Command has been able to create a file that is
self-describing. A self-describing file is one that contains the
information about the fields in the file. Normal MPE and KSAM
files are not self-describing. In general, we know nothing about
the structure of the fields in each record.
Unfortunately, few software tools create or understand
self-describing files. While Query can produce self-describing
files, it cannot use them as input. Our product Suprtool can both
create and understand self-describing files (including KSAM ones). In
addition, Suprtool has a new self-describing format that removes
some restrictions of the original self-describing structure. In
this article we will do the following:
.mar(l+3)
`o#4Describe the format of both the original self-describing file
(this will be a summary of the information in Appendix E of the
Query User Manual) and the new Robelle self-describing file.
`o#4Show how to create a self-describing file.
`o#4Give a programming example that can understand and provide
a "form" listing of any self-describing file.
`o#4Describe KSAM self-describing files.
`o#4Speculate on what an "open system" self-describing file
would look like.
.mar
.sub |1Query versus Robelle|
`|1Query Versus Robelle Self-Describing Files|
Throughout this article we will refer to one of two types of
self-describing files. The first kind are equivalent to the
ones produced by Query. They are identified by the version
number "@A.00.00". The second kind were designed to overcome
limitations with the original self-describing format. We identify
the revised files by calling them Robelle self-\describing files. They
have a version number of "@B.00.00".
.sub |1Examples In SPL|
`|1Examples In This Article|
Because we write code in SPL and SPLash!, we will give our
examples in these programming languages. The only word of
caution is to remember that SPL uses zero-based addressing
for all its arrays.
.sub |1MPE User Labels|
`|1MPE User Labels|
User labels are an optional part of an MPE file. User labels are
part of the file, but they are not part of the data (i.e., when
reading the records in the file the user labels are ignored).
User labels are a handy place to store extra information about a
file. Unfortunately, MS-Dos and UNIX have no concept similar to
MPE's user labels
(see the section |2Future Self-Describing Formats| for ideas
for UNIX and MS-Dos).
The number of user labels must be specified when the file is
created. On most versions of MPE, the only way to create a file
with user labels is via the FOPEN intrinsic. Newer versions of
MPE/iX allow the ULABEL= keyword on the Build Command to specify
the number of user labels. Each user label is 256 bytes long and
user labels are numbered from zero. You access user labels by
calling the FREADLABEL and FWRITELABEL intrinsics.
.tit
.page.sub
.skip 3.opt(l- r- f-)
`|3Identifying Self-Describing Files|
.opt.skip 2
.tit Identifying
.ent `Identifying Self-Describing Files
An MPE file that is self-describing has a filecode of 1084. You
will recognize these files by seeing "SD" next to the filecode of
a :listf,2:
.font 5.opt(f-)
FILENAME CODE ----------LOGICAL RECORD--------- ----SPACE----
SIZE TYP EOF LIMIT R/B SECTORS #X MX
LOADFILE SD 128W FB 33 10000 35 256 1 *
.font 0.opt
Recognizing self-describing KSAM files is more difficult. KSAM
sd-files do not have a special file code. Instead, you must look
for a KSAM file with extra file labels. On MPE/iX, this is done
with a :listf ,3 (on MPE V/E use Listdir.Pub.Sys):
.sub |1Self-Describing Files|
`|1What Is A Self-Describing File|
A self-describing file stores information in the MPE file
labels about the fields in each record of the file.
File labels are like a special file within a file. An MPE file
label is 256 bytes long and an MPE file is created with 0 to
256 file labels. The file labels are accessed via the Freadlabel
and Fwritelabel Intrinsic.
User labels are numbered from zero.
Customarily, tools that create self-describing files leave
the first ten file labels (numbered 0 to 9) for user
applications. The self-describing information is broken into
two kinds of labels: the header label and field labels.
.if outlaser then
.page 20
.skip 1
0-9 #27 Filler labels
.skip 3
#31|1.|
.break
#31|1.|
.break
#31|1.|
.skip 2
n-3 #27 Second field label
.skip 2
n-2 #27 First field label
.skip 2
n-1 #27 Header label
.skip 1
.com Next 4 .pcl's: PBOX 144.01 +525 -925 900 150 2 0 0 1 50
.pcl~&f0S~*p+525x-925Y~*c900a2B~*c0P~*c2a150B~*c0P~*p+900X~*c0P
.pcl~*p-900x+150Y~*c902a2B~*c0P
.pcl~*p+1X~*c900a1B~*c50g0P~*p-150y-1X~*p+1y+900X
.pcl~*c1a150B~*c0P~&f1S
.com Next 4 .pcl's: PBOX 144.11 +525 -775 900 300 2 0 0 1 50
.pcl~&f0S~*p+525x-775Y~*c900a2B~*c0P~*c2a300B~*c0P~*p+900X~*c0P
.pcl~*p-900x+300Y~*c902a2B~*c0P
.pcl~*p+1X~*c900a1B~*c50g0P~*p-300y-1X~*p+1y+900X
.pcl~*c1a300B~*c0P~&f1S
.com Next 4 .pcl's: PBOX 144.21 +525 -475 900 150 2 0 0 1 50
.pcl~&f0S~*p+525x-475Y~*c900a2B~*c0P~*c2a150B~*c0P~*p+900X~*c0P
.pcl~*p-900x+150Y~*c902a2B~*c0P
.pcl~*p+1X~*c900a1B~*c50g0P~*p-150y-1X~*p+1y+900X
.pcl~*c1a150B~*c0P~&f1S
.com Next 4 .pcl's: PBOX 144.31 +525 -325 900 150 2 0 0 1 50
.pcl~&f0S~*p+525x-325Y~*c900a2B~*c0P~*c2a150B~*c0P~*p+900X~*c0P
.pcl~*p-900x+150Y~*c902a2B~*c0P
.pcl~*p+1X~*c900a1B~*c50g0P~*p-150y-1X~*p+1y+900X
.pcl~*c1a150B~*c0P~&f1S
.com Next 4 .pcl's: PBOX 144.41 +525 -175 900 150 2 0 0 1 50
.pcl~&f0S~*p+525x-175Y~*c900a2B~*c0P~*c2a150B~*c0P~*p+900X~*c0P
.pcl~*p-900x+150Y~*c902a2B~*c0P
.pcl~*p+1X~*c900a1B~*c50g0P~*p-150y-1X~*p+1y+900X
.pcl~*c1a150B~*c0P~&f1S
.endif.com if not outlaser
.sub |1Header Label|
`|1Header Label|
If an MPE file has n file labels, they are numbered from 0 to
n-1. The self-describing labels are always added at the end of the
any file labels needed by the user. The last file label will be the
sd-header label
and the sd-field labels are arranged backwards from
this label (n-2, n-3, ...). The format of the header label is
similar, but different for Query and Robelle self-describing
files.
.skip 1
.sub |1Query Header Label|
`|1Query Header Label|
The Query header label consists of the following fields:
.mar(l+3)
`|1version (X8).|@@Always equal to "@A.00.00" for Query
self-\describing files.
`|1length (J1).|@@The length of each record in the file in bytes.
It appears to always
be identical to the MPE record length of the file.
`|1#fields (J1).|@@The number of fields in each file record.
`|1#labels (J1).|@@Number of labels used for field descriptions
plus one for the header label.
This is different than the number of MPE labels for the file.
`|1fields'per'label (J1).|@@Each field label contains one or more
field descriptions. Do not assume a fixed number for this field
-- you must check the value of this field.
`|1size (J1).|@@Length of each field descriptor in 16-bit words.
Because MPE file labels are always 128 words long, the
|1fields'per'label| should always be 128 / |1size|. Again, do not
assume a fixed constant for the field descriptor size.
.mar
.sub |1Robelle Header Label|
`|1Robelle Header Label|
The Robelle header label contains all of the fields of Query's
header label with one change (the version number is different)
and three additions:
.mar(l+3)
`1.#4The version number is "@B.00.00" instead of "@A.00.00" (note
the space at the beginning).
`2.#4There are three new fields for handling sort keys.
These fields are identical to the fields that you
would pass to Sortinit (in compatibility-mode):
.mar(l+3).par(p1)
`|1sort'max'keys (J1)|.@@Maximum number of keys allowed in this
sd-file. The |1sort'keys| would be declared as:
.skip 1
|5 integer array sort'keys(0:sort'max'keys*3-1)|
`|1sort'num'keys (J1)|.@@The actual number of keys in the table. This
value must range from zero to |1sort'max'keys-1|.
`|1sort'keys|.@@The sort keys themselves using the same
conventions as Sort/3000. The byte-offsets of each key start
at one and not zero in the sort table. The byte offsets in
each field entry remain the same (i.e., zero-based instead of
one-based offsets). The sort key types correspond to those for
the Sortinit intrinsic and not the newer HPSortinit.
.mar.par
.mar
.sub |1Header Label|
`|1SPL Layout of the Header Label|
Here is the layout in SPL notation of the Robelle header label.
Note that we use exactly the same layout for accessing Query
header labels (we just ignore all sd'sort'...@variables when
accessing Query self-describing files).
Each field descriptor is fifteen words long, but even the
Robelle field descriptor only uses fourteen words. We leave
the last word unspecified (our code always sets the filler words
with binary zeroes):
.font 5.opt(f-)
sdheader.srcinc:
integer array sd'header(0:sd'label'len); { 0 : 127 }
byte array sd'version(*) = sd'header;
integer array sd'reclength(*) = sd'header(4);
integer array sd'numfields(*) = sd'header(5);
integer array sd'numlabels(*) = sd'header(6);
integer array sd'fieldsperlabel(*) = sd'header(7);
integer array sd'entrylen(*) = sd'header(8);
integer array sd'sort'max'keys(*) = sd'header(9);
integer array sd'sort'num'keys(*) = sd'header(10);
integer array sd'sort'keys(*) = sd'header(11);
.font 0.opt
.sub |1Field Labels|
`|1Field Labels|
Every self-describing file has one or more field labels of 256 bytes.
Each field label has one or more field descriptors.
The first fields in the file will be described in label N-2, the
next set of fields in N-3, and so on. This is opposite to what
you might expect.
Self-describing files that Query produces have eight field
descriptors per user label.
.if outlaser then
.page 25
.opt(l- r- f-)
Picture of one field label
.opt
.skip 4
.font 1
#9 1 #15 2 #21 3 #27 4 #33 5 #39 6 #45 7 #51 8
.font 0
.com Next 4 .pcl's: PBOX 3 +180 -180 180 180 2 0 0 1 50
.pcl~&f0S~*p+180x-180Y~*c180a2B~*c0P~*c2a180B~*c0P~*p+180X~*c0P
.pcl~*p-180x+180Y~*c182a2B~*c0P
.pcl~*p+1X~*c180a1B~*c50g0P~*p-180y-1X~*p+1y+180X
.pcl~*c1a180B~*c0P~&f1S
.com Next 4 .pcl's: PBOX 8 +360 -180 180 180 2 0 0 1 50
.pcl~&f0S~*p+360x-180Y~*c180a2B~*c0P~*c2a180B~*c0P~*p+180X~*c0P
.pcl~*p-180x+180Y~*c182a2B~*c0P
.pcl~*p+1X~*c180a1B~*c50g0P~*p-180y-1X~*p+1y+180X
.pcl~*c1a180B~*c0P~&f1S
.com Next 4 .pcl's: PBOX 13 +540 -180 180 180 2 0 0 1 50
.pcl~&f0S~*p+540x-180Y~*c180a2B~*c0P~*c2a180B~*c0P~*p+180X~*c0P
.pcl~*p-180x+180Y~*c182a2B~*c0P
.pcl~*p+1X~*c180a1B~*c50g0P~*p-180y-1X~*p+1y+180X
.pcl~*c1a180B~*c0P~&f1S
.com Next 4 .pcl's: PBOX 18 +720 -180 180 180 2 0 0 1 50
.pcl~&f0S~*p+720x-180Y~*c180a2B~*c0P~*c2a180B~*c0P~*p+180X~*c0P
.pcl~*p-180x+180Y~*c182a2B~*c0P
.pcl~*p+1X~*c180a1B~*c50g0P~*p-180y-1X~*p+1y+180X
.pcl~*c1a180B~*c0P~&f1S
.com Next 4 .pcl's: PBOX 23 +900 -180 180 180 2 0 0 1 50
.pcl~&f0S~*p+900x-180Y~*c180a2B~*c0P~*c2a180B~*c0P~*p+180X~*c0P
.pcl~*p-180x+180Y~*c182a2B~*c0P
.pcl~*p+1X~*c180a1B~*c50g0P~*p-180y-1X~*p+1y+180X
.pcl~*c1a180B~*c0P~&f1S
.com Next 4 .pcl's: PBOX 28 +1080 -180 180 180 2 0 0 1 50
.pcl~&f0S~*p+1080x-180Y~*c180a2B~*c0P~*c2a180B~*c0P~*p+180X~*c0P
.pcl~*p-180x+180Y~*c182a2B~*c0P
.pcl~*p+1X~*c180a1B~*c50g0P~*p-180y-1X~*p+1y+180X
.pcl~*c1a180B~*c0P~&f1S
.com Next 4 .pcl's: PBOX 33 +1260 -180 180 180 2 0 0 1 50
.pcl~&f0S~*p+1260x-180Y~*c180a2B~*c0P~*c2a180B~*c0P~*p+180X~*c0P
.pcl~*p-180x+180Y~*c182a2B~*c0P
.pcl~*p+1X~*c180a1B~*c50g0P~*p-180y-1X~*p+1y+180X
.pcl~*c1a180B~*c0P~&f1S
.com Next 4 .pcl's: PBOX 38 +1440 -180 180 180 2 0 0 1 50
.pcl~&f0S~*p+1440x-180Y~*c180a2B~*c0P~*c2a180B~*c0P~*p+180X~*c0P
.pcl~*p-180x+180Y~*c182a2B~*c0P
.pcl~*p+1X~*c180a1B~*c50g0P~*p-180y-1X~*p+1y+180X
.pcl~*c1a180B~*c0P~&f1S
.com Next 5 .pcl's: PBOX 43 +1620 -180 90 180 2 2 20 1 50
.pcl~&f0S~*p+1620x-180Y~&f0S~*p+2a+2B~*c90a180b20g2P~&f1S
.pcl~*c90a2B~*c0P~*c2a180B~*c0P~*p+90X~*c0P
.pcl~*p-90x+180Y~*c92a2B~*c0P
.pcl~*p+1X~*c90a1B~*c50g2P~*p-180y-1X~*p+1y+90X
.pcl~*c1a180B~*c2P~&f1S
.endif.com if outlaser
.sub |1Query Field Labels|
`|1Query Field Labels|
Query always produces self-describing files with 15 words
reserved for each field descriptor.
Each field is described as
follows:
.mar(l+3)
`|1field'name (X16).|@@The name of the field left-justified.
Field names are in upper case.
`|1field'type (J1).|@@The type of the field taken from the
following list:
.mar(l+3)
`1.#7ASCII (type U and X).
`2.#7free form ASCII numbers.
`3.#7signed integer (type I).
`4.#7floating point real (type R).
`5.#7packed decimal (type P).
`6.#7COBOL computational (type J).
`7.#7unsigned integers (type K).
`8.#7zoned decimal (type Z).
`9.#7IEEE floating point (type E). This is a Robelle extension
that applies to either "@A.00.00" or "@B.00.00" self-describing
files.
`10.#7IMAGE compound field.
.mar
`|1field'offset (J1).|@@The offset of the field in bytes. The
offset starts at zero.
`|1field'length (J1).|@@The length of the field in bytes.
`|1reserved'space (4J1).|@@Four words that are reserved for future
use.
.mar
.sub |1Robelle Field Labels|
`|1Robelle Field Labels|
Many HP 3000 applications contain repeated fields. Query
self-describing files map all repeated fields into type "10", which
is useless for applications that understand repeated fields.
It would also be nice if additional
user information, such as the number of decimal points or the
format of a date were available. The Robelle field descriptor
provides for all of these, by using three of the four words of
reserved space.
All fields up to |1field'length| are the same as QUERY's
(note especially
that |1field'length| is the total length of the field and not
the length of one sub-field). These are the new fields:
.mar(l+3)
`|1field'repeat (J1)|.@@In IMAGE terms, this is known as the sub-count.
For simple fields, |1field'repeat| is one (and not zero).
`|1field'decplaces (J1)|.@@Logical number of decimal places in the
field. Zero means there are
no decimal points. This field must be zero if the |1field'type|
is byte.
`|1field'date'type (J1)|.@@Zero if the field is not a date. Otherwise,
contains a constant that describes the format of the date. These
constants are described below.
`|1reserved'space (J1).|@@One word that is reserved for future
use.
.mar
.sub |1Date Format|
`|1Date Format|
The date format is mapped into the data type and byte-length
of the field. Here are the constants for each
date format:
1 #8yymmdd
2 #8ddmmyy
3 #8mmddyy
4 #8yymm
5 #8calendar (MPE intrinsic format)
6 #8yyyymmdd
7 #8ddmmyyyy
8 #8mmddyyyy
9 #8phdate (PowerHouse format)
10 #8ask (ASK ManMan format)
.sub |1SPL Field Layout|
`|1SPL Layout of the Field Descriptor|
Here is the layout in SPL notation of the Robelle field
descriptor.
Note that we use exactly the same layout for accessing Query
field descriptors (we ignore the repeat, decplaces, and
date'type fields for Query self-describing files):
.font 5.opt(f-)
sdfield.srcinc:
integer array sd'field(0:sd'max'field'len); { 0 : 14 }
byte array sd'field'name(*) = sd'field;
integer array sd'field'type(*) = sd'field(8);
integer array sd'field'offset(*) = sd'field(9);
integer array sd'field'bytelen(*) = sd'field(10);
integer array sd'field'repeat(*) = sd'field(11);
integer array sd'field'decplaces(*)= sd'field(12);
integer array sd'field'date'type(*)= sd'field(13);
.font 0.opt
.sub |1Support Routines|
`|1Support Routines|
To make our life easier, we have a standard include file with
both variables and SPL/SPLash! subroutines that we use in many
of our self-describing procedures:
.font 5
sdsubr.src:
<< Standard variables and subroutines needed to access fields in
a self-describing file. This file must be included after all
variable declarations in a procedure.
>>
integer
file'userlabels
,file'foptions
,file'filecode
,current'labelnum
,sd'field'index
;
integer array current'label(0:sd'label'len);
subroutine file'error(local'filenum);
value local'filenum;
integer local'filenum;
begin
xfileinfo(local'filenum);
goto error'exit;
end'subr; <<file'error>>
subroutine read'label'error(local'filenum);
value local'filenum;
integer local'filenum;
begin
p "Unable to read label from self-describing file" err;
file'error(local'filenum);
end'subr; <<read'label'error>>
subroutine read'label(local'filenum,labelnum);
value local'filenum, labelnum;
integer local'filenum, labelnum;
begin
blank(current'label,sd'label'len);
freadlabel(local'filenum
,current'label
,sd'label'len
,labelnum
);
if < then
read'label'error(local'filenum)
else
if > then
if labelnum > file'userlabels then
begin
b'blank(outbuf,bl'outbuf);
move outbuf := "Attempting to read label #";
ascii(labelnum,10,outbuf'(26));
say'errx(outbuf,35,bl'outbuf);
read'label'error(local'filenum);
end'if;
end'subr; <<read'label>>
subroutine file'info(local'filenum);
value local'filenum;
integer local'filenum;
begin
fgetinfo(local'filenum<<filenum iv>>
, <<filename ba>>
,file'foptions <<foptions l >>
, <<aoptions l >>
, <<recsize i >>
, <<devtype i >>
, <<ldnum l >>
, <<hdaddr l >>
,file'filecode <<filecode i >>
, <<recptr d >>
, <<eof d >>
, <<flimit d >>
, <<logcount d >>
, <<physcount d >>
, <<blksize i >>
, <<extsize l >>
, <<numextents i >>
,file'userlabels <<userlabels i >>
);
if <> then
begin
p "Unable to fgetinfo on file" err;
file'error(local'filenum);
end'if;
end'subr; <<file'info>>
logical subroutine get'field(local'filenum,offset);
value local'filenum, offset;
integer local'filenum, offset;
begin
get'field := false;
if sd'field'index < sd'numfields then
begin
if (sd'field'index mod sd'fieldsperlabel) = 0 then
begin
current'labelnum := current'labelnum - 1;
read'label(local'filenum,current'labelnum);
end'if;
offset := (sd'field'index mod sd'fieldsperlabel) *
sd'entrylen;
move sd'field := current'label(offset),(sd'entrylen);
sd'field'index := sd'field'index + 1;
get'field := true;
end'if;
end'subr; <<get'field>>
.font 0
.sub |1File'Error|
`|1File'Error|
To make our life easier we will take a simple approach to file
system errors. If any MPE file system intrinsic returns an error,
we call the Robelle equivalent of the printfileinfo intrinsic and
then we exit the Formselfdesc procedure. Yes, we use a goto in
the |1file'error| subroutine. This is a good example of where a
goto enhances readability and reliability.
.sub |1File'Info|
`|1File'Info|
We have developed a standard set of subroutines for working with
self-describing files. The |1file'info| subroutine initializes
the |1file'userlabels|, |1file'foptions|, and |1file'filecode|
variables (declared as part of the |5sdsubr.src| file).
.sub |1Read'Label|
`|1Read'Label|
It is important to understand the error checking in
|1read'label|. MPE user labels may be allocated space, but
they might not actually be written. For example, after first
creating a file with user labels, none of the user labels have
actually been written to the file. If we get an end-of-file
condition from Freadlabel, we ignore the error unless a
programming bug has caused us to attempt to read a label that is
greater than the number of user labels in the file.
.sub |1Get'Field|
`|1Get'Field|
We will describe how the |1get'field| subroutine works later in
the section |2Understanding Self-Describing Information|.
.sub |1Am I Self-Describing|
`|1Am I A Self-describing File|
We determine if a file is self-describing in two ways:
.mar(l+3)
`1.#4If the file has a filecode of 1084 and it has one or more
MPE file labels.
`2.#4The file is a KSAM file, has more than one label, we can
read the last label, and the last label starts with either the
string "@A.00.00" or "@B.00.00" (note the space at the
beginning).
.mar
Here is a procedure that returns True if the passed filenum is a
self-describing file:
.font 5.opt(f-)
$page "sd'file"
<< Return true if the passed file is self-describing.
>>
logical procedure sd'file(filenum);
value filenum;
integer filenum;
option check 3;
begin
$include sdheader.srcinc
$include sdfield.srcinc
$include sdsubr.src
$page "sd'file/mainline"
sd'file := false;
file'info(filenum);
if file'filecode = 1084 and file'userlabels <> 0 then
sd'file := true
else
if file'foptions.(2:3) = 1 or
file'foptions.(2:3) = 3 then
if file'userlabels > 1 then
begin <<ksam with extra user labels>>
read'label(filenum,file'userlabels-1);
move sd'header := current'label,(sd'label'len);
if sd'version = " A.00.00" or
sd'version = " B.00.00" then
sd'file := true;
end'else;
error'exit:
end'proc; <<sd'file>>
.font 0.opt
.tit
.page.sub
.skip 3.opt(l- r- f-)
`|3Creating A Self-Describing File|
.opt.skip 2
.tit Creating
.ent `Creating A Self-Describing File
When we describe data structures we usually explain the input
routine first and then the creation/output routine second. For
self-describing files, it is easier to do it in the opposite order.
We will show the structure of a simple self-describing file and
then we will show the code that produced the self-describing label
information for the file.
HowMessy is a Robelle program that reports on database efficiency. For
years, this program has produced a report. Unfortunately, reports
must be read by humans. It would make more sense for HowMessy to
produce a self-describing MPE file with the efficiency
information from one or more databases.
You could then use a tool that understood self-describing files
to report and act on the information from the file produced by
HowMessy.
We will show all of the
routines in HowMessy's self-describing module, but first we need
to know the structure of the self-describing file.
.sub |1HowMessy's Loadfile|
`|1HowMessy's Loadfile|
HowMessy creates a self-describing file called Loadfile. This
file has one record per database/dataset/search-field for one
or more databases. Here is a "form" listing of the Loadfile:
.font 5.opt(f-)
File: LOADFILE.GROUP.ACCT (SD Version B.00.00)
Entry: Offset
DATABASE X26 1
DATASET X16 27
DATASETNUM I1 43
DATASETTYPE X4 45
CAPACITY I2 49
ENTRIES I2 53
LOADFACTOR I2 57 << .2 >>
SECONDARIES I2 61 << .2 >>
MAXBLOCKS I2 65
HIGHWATER I2 69
PATHSORT X1 73
PATHPRIMARY X1 74
BLOCKFACTOR I1 75
SEARCHFIELD X16 77
MAXCHAIN I2 93
AVECHAIN I2 97 << .2 >>
STDDEVIATION I2 101 << .2 >>
EXPECTEDBLOCKS I2 105 << .2 >>
AVERAGEBLOCKS I2 109 << .2 >>
INEFFICIENTPTRS I2 113 << .2 >>
ELONGATION I2 117 << .2 >>
FUTUREFIELDS X136 121
Limit: 10000 EOF: 33 Entry Length: 256 Blocking: 35
.font 0.opt
.sub |1Global Equates|
`|1Global Equates|
To simplify programming, we use a global constant
"equates" that define specific attributes of Query and Robelle
self-describing files. When reading a self-describing file, we
don't need most of these constants, since
the necessary numbers are provided in the self-describing file
header. Here are the equates that we use when creating
self-describing files:
.font 5.opt(f-)
sdequate.srcinc:
equate
sd'max'field'len = 15
,sd'label'len = 128
,sd'max'fieldsperlabel = 8
,sd'filler'labels = 10
;
equate
sd'date'yymmdd = 1
,sd'date'ddmmyy = 2
,sd'date'mmddyy = 3
,sd'date'yymm = 4
,sd'date'calendar = 5
,sd'date'yyyymmdd = 6
,sd'date'ddmmyyyy = 7
,sd'date'mmddyyyy = 8
,sd'date'phdate = 9
,sd'date'askdate = 10
;
.font 0.opt
.sub |1How Many Labels?|
`|1Computing the Number of Labels|
Before opening the Loadfile, HowMessy must determine how many
labels will be needed. The following routine is used by
Robelle products to compute the number of user labels for a
self-describing file. Note that we continue the Query standard of
reserving the first ten labels (numbered 0 to 9) for other uses:
.font 5.opt(f-)
$page "sd'compute'labels"
<< Compute how many labels an SD file should have, based only
on the number of fields. Includes the mysterious filler
labels.
>>
integer procedure sd'compute'labels (numfields);
value numfields;
integer numfields;
option check 3;
begin
sd'compute'labels :=
(numfields-1+sd'max'fieldsperlabel) /
sd'max'fieldsperlabel
+ 1 <<for the header label>>
+ sd'filler'labels;
end'proc; <<sd'compute'labels>>
.font 0.opt
.sub |1Opening the Loadfile|
`|1Opening the Loadfile|
After computing the number of user labels, we can open a new MPE
file called Loadfile. We designed the HowMessy Loadfile to have
records 256 bytes long. To make our life easier, we have a few
global equates in the HowMessy self-describing module that we'll
use throughout the rest of the examples:
.font 5.opt(f-)
$page "global equates and defines for the selfdesc module"
equate
wl'loadfile = 128
,bl'loadfile = wl'loadfile * 2
,bl'item'name = 16
,max'field = 22 ! #fields in Loadfile
;
.font 0.opt
Here is the actual code to create the Loadfile:
.font 5.opt(f-)
$page "sd'open"
<< Open the Loadfile and initialize the self-describing
information.
>>
logical procedure sd'open(outfile,loadfile'filenum);
integer loadfile'filenum; ! Note by reference -- returned
integer array outfile;
option check 3;
begin
$include localvar.srcinc
byte array
loadfile'filename(0:bl'local'filename)
;
move loadfile'filename := "loadfile ";
loadfile'filenum :=
fopen(loadfile'filename
, << foptions lv >>
,1 <<write>> << aoptions lv >>
,wl'loadfile << recsize iv >>
, << device ba >>
, << formmsg ba >>
,sd'compute'labels(max'field)
,35 << blockfactor iv >>
, << numbuffers iv >>
,10000d << filesize dv >>
, << numextents iv >>
, << initialloc iv >>
,1084 << filecode iv >>
);
if loadfile'filenum = 0 then
begin
error(outfile,10);
xfileinfo(loadfile'filenum);
end'if
else
sd'open := true;
end'proc; <<sd'open>>
.font 0.opt
Note that the filecode is 1084. For non-KSAM files, this is used
to indicate a self-describing file. When you do a :Listf of such
a file, MPE translates the "1084" filecode into "SD".
.sub |1Writing the Labels|
`|1Writing the Self-Describing Labels|
Having successfully opened a new self-describing file,
it's time to write the self-describing information to the user
labels. Remember that the last user label (N-1) contains the
header information and the field labels are written in backward
order (N-2, N-3, ...). Our routine to write the self-describing
information to the Loadfile writes the field information first
and then updates the header label as the last step:
.font 5.opt(f-)
$page "sd'write'labels"
<< Write out the labels of a self-describing file with the
Loadfile fields.
>>
logical procedure sd'write'labels(outfile,filenum);
value filenum;
integer filenum;
integer array outfile;
option check 3;
begin
$include localvar.srcinc
integer
field'index
,field'offset
,labelnum
;
$include sdheader.srcinc
$include sdfield.srcinc
integer array sd'label(0:sd'label'len);
$include sdsubr.src
$page "sd'write'labels/subroutines"
subroutine write'label(labelnum);
value labelnum;
integer labelnum;
begin
fwritelabel(filenum,sd'label,sd'label'len,labelnum);
if <> then
begin
error(outfile,12);
file'error(filenum);
end'if;
end'subr; <<write'label>>
subroutine init'header;
begin
zero'buf(sd'header,sd'label'len);
b'blank(sd'version,8);
move sd'version := "@B.00.00";
sd'numfields := max'field;
field'index := 0;
sd'numlabels := sd'compute'labels(sd'numfields) -
sd'filler'labels;
sd'fieldsperlabel:= sd'max'fieldsperlabel;
sd'entrylen := sd'max'field'len;
end'subr; <<init'header>>
subroutine init'all'labels(curr'label,num'labels);
value curr'label, num'labels;
integer curr'label, num'labels;
begin
while curr'label > num'labels do
begin
write'label(curr'label);
curr'label := curr'label - 1;
end'while;
end'subr; <<init'all'labels>>
subroutine put'field(name,bytelen,decplaces,type);
value bytelen, type, decplaces;
integer bytelen, decplaces, type;
byte array name;
begin
zero'buf(sd'field,sd'max'field'len);
move sd'field'name := name,(bl'item'name);
sd'field'type := type;
sd'field'offset := field'offset;
sd'field'bytelen := bytelen;
sd'field'repeat := 1;
move sd'label(sd'entrylen*sd'field'index) := sd'field,
(sd'entrylen);
field'offset := field'offset + sd'field'bytelen;
sd'field'index := sd'field'index + 1;
if sd'field'index >= sd'fieldsperlabel then
begin
write'label(labelnum);
labelnum := labelnum - 1;
sd'field'index := 0;
zero'buf(sd'label,sd'label'len);
end'if;
b'blank(name,bl'item'name);
end'subr; <<put'field>>
$page "sd'write'labels/mainline"
sd'write'labels := false;
init'header;
file'info(filenum);
field'offset := 0;
sd'field'index := 0;
zero'buf(sd'label,sd'label'len);
init'all'labels(file'userlabels-1,sd'numlabels);
labelnum := file'userlabels - 2;
sd'reclength := bl'loadfile;
b'blank(inbuf,bl'inbuf);
move inbuf' := "DATABASE "; put'field(inbuf, 26,0,1);
move inbuf' := "DATASET "; put'field(inbuf, 16,0,1);
move inbuf' := "DATASETNUM "; put'field(inbuf, 2,0,3);
move inbuf' := "DATASETTYPE "; put'field(inbuf, 4,0,1);
move inbuf' := "CAPACITY "; put'field(inbuf, 4,0,3);
move inbuf' := "ENTRIES "; put'field(inbuf, 4,0,3);
move inbuf' := "LOADFACTOR "; put'field(inbuf, 4,2,3);
move inbuf' := "SECONDARIES "; put'field(inbuf, 4,2,3);
move inbuf' := "MAXBLOCKS "; put'field(inbuf, 4,0,3);
move inbuf' := "HIGHWATER "; put'field(inbuf, 4,0,3);
move inbuf' := "PATHSORT "; put'field(inbuf, 1,0,1);
move inbuf' := "PATHPRIMARY "; put'field(inbuf, 1,0,1);
move inbuf' := "BLOCKFACTOR "; put'field(inbuf, 2,0,3);
move inbuf' := "SEARCHFIELD "; put'field(inbuf, 16,0,1);
move inbuf' := "MAXCHAIN "; put'field(inbuf, 4,0,3);
move inbuf' := "AVECHAIN "; put'field(inbuf, 4,2,3);
move inbuf' := "STDDEVIATION "; put'field(inbuf, 4,2,3);
move inbuf' := "EXPECTEDBLOCKS "; put'field(inbuf, 4,2,3);
move inbuf' := "AVERAGEBLOCKS "; put'field(inbuf, 4,2,3);
move inbuf' := "INEFFICIENTPTRS "; put'field(inbuf, 4,2,3);
move inbuf' := "ELONGATION "; put'field(inbuf, 4,2,3);
move inbuf' := "FUTUREFIELDS "; put'field(inbuf,136,0,1);
if sd'field'index <> 0 then
write'label(labelnum);
move sd'label := sd'header,(sd'label'len);
write'label(file'userlabels - 1);
sd'write'labels := true;
error'exit:
end'proc; <<sd'write'labels>>
.font 0.opt
.sub |1Initializing the Labels|
`|1Init'Header|
We start our procedure by initializing most of the fields in the
header label. We zero out the header label and then we
fill in the variables of the header label. This file has fields
with an implied decimal point, so we want to use the Robelle
format of self-describing files (version number "@B.00.00").
The number of fields is taken from our global equate. The
number of self-describing labels is our computed number less the
ten overhead labels. The number of fields in each label and the
length of each field description are taken from global equates
that match the values used by Query. We also initialize the
|1field'index| variable which is used as an index into a single
label buffer (varies from 0 to sd'fieldsperlabel - 1).
.sub |1File'Info|
`|1File'Info|
To make our code more general-purpose, we
will not assume anything about the HowMessy Loadfile format (this
also makes it easier to change later). Instead, we call Fgetinfo
to obtain the number of labels in our file, so that we know
exactly where the last label is. The |1file'info| subroutine
initializes the |1file'userlabels| variable
(declared in the |5sdsubr.src| file) with the number of
user labels in our file.
.sub |1Init'All'Labels|
`|1Init'All'Labels|
To be on the safe side, we initialize all user labels in our file
with binary zeroes. Note that our |1write'label| subroutine uses
a procedure global array called |1label'buf| for writing. We
initialize this buffer to binary zeroes and then continually
write it out to all of the self-describing labels. We don't
touch the initial ten labels reserved for other use.
.sub |1Put'Field|
`|1Put'Field|
This subroutine handles all of the details of adding a new field
to our self-describing Loadfile. It initializes a new field
record, moves this field record to the appropriate place in
|1label'buf|, and finally writes out labels as we overflow
|1sd'fieldsperlabel|. Each of our fields has a name (we move the
name to |1inbuf| and initialize |1inbuf| to blanks after adding
the field, a byte length, the implied number of decimal places,
and a type (either byte or integer for our file). Note that the
|1put'field| subroutine looks after computing the byte offset of
each field by incrementing a counter.
We initialize each field record with binary zeroes (just to be
safe). We then fill in each portion of field information. We then
move our field record to the |1label'buf| at the correct offset.
This works well in SPL/SPLash!, but is more of a problem in C or
Pascal. In these languages, we would create a record/structure
that was an array of field records and index into the structure
using the current field index. As we filled up the structure, we
would write out a new user label to our self-describing file.
.sub |1Finishing Up|
`|1Finishing Up|
After adding all the fields, we have to see if there is one label
record that has not been written to the Loadfile. If so, we write
it out. Finally, the header record is written out. We do this
last, since some of the variables used by |1put'field| were ones
from the header record.
.sub |1Closing|
`|1Closing the Self-Describing File|
Our next routine closes the self-describing file and handles any
errors from Fclose. Pretty straight-forward MPE programming:
.font 5.opt(f-)
$page "sd'close"
<< Close the loadfile and check for duplicate output files.
>>
logical procedure sd'close(outfile,loadfile'filenum);
value loadfile'filenum;
integer loadfile'filenum;
integer array outfile;
option check 3;
begin
$include localvar.srcinc
sd'close := false;
fclose(loadfile'filenum,2,0); ! Save temp
if <> then
begin
error(outfile,11);
xfileinfo(loadfile'filenum);
end'if
else
sd'close := true;
end'proc; <<sd'close>>
.font 0.opt
.sub |1Providing a Shell|
`|1Providing a Shell|
To make life easier in HowMessy, we provide one routine for the
main module to call. This routine purges any exiting temporary
Loadfile, creates our new Loadfile, writes out the
self-\describing information, and saves the Loadfile. The
controlling HowMessy routine then reopens Loadfile with
write-access (this may seem inefficient, but HowMessy is written
in both SPL/SPLash! and HP Pascal, so it was easier to organize
the code this way):
.font 5.opt(f-)
$page "sdcreate"
<< Create Loadfile with all the self-describing information. We
purge any existing file called Loadfile, create a temporary
one, and then fill in the labels.
>>
integer procedure sdcreate(outfile);
integer array outfile;
option check 3;
begin
$include localvar.srcinc
$include mpecmd.srcinc
integer
loadfile'filenum
;
logical subroutine purge'loadfile;
begin
purge'loadfile := false;
say'str "purge loadfile,temp";
say'add rtn;
if mpecmd'execute(outbuf,mpe'print'buffer) then
purge'loadfile := true
else
error(outfile,9);
end'subr; <<purge'loadfile>>
$page "sdcreate/mainline"
sdcreate := 0;
if purge'loadfile then
if sd'open(outfile,loadfile'filenum) then
if sd'write'labels(outfile,loadfile'filenum) then
if sd'close(outfile,loadfile'filenum) then
sdcreate := 1;
end'proc; <<sdcreate>>
.font 0.opt
.tit
.page.sub
.opt(l- r- f-).skip 2
`|3Understanding Self-Describing Information|
.opt.skip 2
.tit Understanding
.ent `Understanding Self-Describing Information
Our HowMessy example showed the form of the Loadfile using a
format similar to the one Query uses, but the input was an MPE
self-describing file instead of an IMAGE dataset. Here is our
example form again:
.font 5.opt(f-)
File: LOADFILE.GROUP.ACCT (SD Version B.00.00)
Entry: Offset
DATABASE X26 1
DATASET X16 27
DATASETNUM I1 43
DATASETTYPE X4 45
CAPACITY I2 49
ENTRIES I2 53
LOADFACTOR I2 57 << .2 >>
SECONDARIES I2 61 << .2 >>
MAXBLOCKS I2 65
HIGHWATER I2 69
PATHSORT X1 73
PATHPRIMARY X1 74
BLOCKFACTOR I1 75
SEARCHFIELD X16 77
MAXCHAIN I2 93
AVECHAIN I2 97 << .2 >>
STDDEVIATION I2 101 << .2 >>
EXPECTEDBLOCKS I2 105 << .2 >>
AVERAGEBLOCKS I2 109 << .2 >>
INEFFICIENTPTRS I2 113 << .2 >>
ELONGATION I2 117 << .2 >>
FUTUREFIELDS X136 121
Limit: 10000 EOF: 33 Entry Length: 256 Blocking: 35
.font 0.opt
.sub |1Formselfdesc|
`|1Formselfdesc Procedure|
We have developed a stand-alone procedure for producing this
output for a self-describing file. The following is the source
code that we use:
.font 5.opt(f-)
$page "formselfdesc"
<< If the passed filenum is a self-describing file, print a
description of the fields in the file on $stdlist.
>>
logical procedure formselfdesc(sd'filenum);
value sd'filenum;
integer sd'filenum;
option check 3;
begin
$include localvar.srcinc
$include sdequate.srcinc
integer
file'code
,file'userlabels
,file'foptions
,current'labelnum
,field'index
,file'recsize
,file'blkfac
;
double
file'eof
,file'limit
;
byte array
filename(0:bl'local'filename)
;
$include sdheader.srcinc
$include sdfield.srcinc
integer array current'label(0:sd'label'len);
subroutine file'error;
begin
xfileinfo(sd'filenum);
goto error'exit;
end'subr; <<file'error>>
subroutine read'label(labelnum);
value labelnum;
integer labelnum;
begin
freadlabel(sd'filenum,current'label,sd'label'len,labelnum);
if <> then
begin
p "Unable to read label from self-describing file" err;
file'error;
end'if;
end'subr; <<read'label>>
subroutine file'info(blksize);
value blksize;
integer blksize;
begin
b'blank(filename,bl'local'filename);
fgetinfo(sd'filenum <<filenum iv>>
,filename <<filename ba>>
,file'foptions <<foptions l >>
, <<aoptions l >>
,file'recsize <<recsize i >>
, <<devtype i >>
, <<ldnum l >>
, <<hdaddr l >>
,file'code <<filecode i >>
, <<recptr d >>
,file'eof <<eof d >>
,file'limit <<flimit d >>
, <<logcount d >>
, <<physcount d >>
,blksize <<blksize i >>
, <<extsize l >>
, <<numextents i >>
,file'userlabels <<userlabels i >>
);
if <> then
begin
p "Unable to fgetinfo on file" err;
file'error;
end'if;
if file'recsize <> 0 then
file'blkfac := blksize / file'recsize
else
file'blkfac := 1;
if file'recsize < 0 then
file'recsize := \file'recsize\
else
file'recsize := file'recsize * 2;
end'subr; <<file'info>>
logical subroutine file'is'sd;
begin
file'is'sd := false;
file'info(0);
if file'code = 1084 and file'userlabels <> 0 then
file'is'sd := true
else
if file'foptions.(2:3) = 1 or file'foptions.(2:3) = 3 then
if file'userlabels > 1 then
begin <<ksam with extra user labels>>
read'label(file'userlabels-1);
move sd'header := current'label,(sd'label'len);
if sd'version = "@A.00.00" or
sd'version = "@B.00.00" then
file'is'sd := true;
end'else;
end'subr; <<file'is'sd>>
logical subroutine get'field(offset);
value offset;
integer offset;
begin
get'field := false;
if field'index < sd'numfields then
begin
if (field'index mod sd'fieldsperlabel) = 0 then
begin
current'labelnum := current'labelnum - 1;
read'label(current'labelnum);
end'if;
offset := (field'index mod sd'fieldsperlabel) *
sd'entrylen;
move sd'field := current'label(offset),(sd'entrylen);
field'index := field'index + 1;
get'field := true;
end'if;
end'subr; <<get'field>>
subroutine print'outbuf(len);
value len;
integer len;
begin
len := bl'outbuf;
while len > 0 and outbuf'(len-1) = " " do
len := len - 1;
print(outbuf,-len,0);
end'subr; <<print'outbuf>>
subroutine print'header(len);
value len;
integer len;
begin
b'blank(outbuf,bl'outbuf);
move outbuf'(4) := "File: ";
move outbuf'(10) := filename,(bl'local'filename);
len := bl'outbuf;
while len > 0 and outbuf'(len-1) = " " do
len := len - 1;
len := len + 5;
len := len + move outbuf'(len) := "(SD Version";
len := len + move outbuf'(len) := sd'version,(8);
len := len + move outbuf'(len) := ")";
print'outbuf(0);
b'blank(outbuf,bl'outbuf);
move outbuf'(7) := "Entry:";
move outbuf'(34) := "Offset";
print(outbuf,-50,0);
end'subr; <<print'header>>
subroutine print'trailer(len);
value len;
integer len;
begin
b'blank(outbuf,bl'outbuf);
len := move outbuf' := " ";
len := len + move outbuf'(len) := "Limit: ";
len := len + dascii(file'limit,10,outbuf'(len));
len := len + move outbuf'(len) := " EOF: ";
len := len + dascii(file'eof,10,outbuf'(len));
len := len + move outbuf'(len) := " Entry Length: ";
len := len + ascii(file'recsize,10,outbuf'(len));
len := len + move outbuf'(len) := " Blocking: ";
len := len + ascii(file'blkfac,10,outbuf'(len));
print(outbuf,-len,0);
end'subr; <<print'trailer>>
subroutine format'field'type;
begin
if 0 <= sd'field'type <= 9 then
case sd'field'type of begin
<<0>> move outbuf'(31) := "?";
<<1>> move outbuf'(31) := "X";
<<2>> move outbuf'(31) := "?";
<<3>> move outbuf'(31) := "I";
<<4>> move outbuf'(31) := "R";
<<5>> move outbuf'(31) := "P";
<<6>> move outbuf'(31) := "J";
<<7>> move outbuf'(31) := "K";
<<8>> move outbuf'(31) := "Z";
<<9>> move outbuf'(31) := "E";
end'case
else
move outbuf'(31) := "?";
end'subr; <<format'field'type>>
logical subroutine field'is'sorted(sort'index);
value sort'index;
integer sort'index;
begin
field'is'sorted := false;
if sd'field'offset + 1 = sd'sort'keys(sort'index*3) and
sd'field'bytelen = sd'sort'keys(sort'index*3+1) then
field'is'sorted := true;
end'subr; <<field'is'sorted>>
subroutine format'sort'key(sort'index);
value sort'index;
integer sort'index;
begin
sort'index := 0;
while sort'index < sd'sort'num'keys do
begin
if field'is'sorted(sort'index) then
begin
move outbuf'(42) := "<<Sort# ";
ascii(sort'index+1,10,outbuf'(50));
move outbuf'(52) := ">>";
end'if;
sort'index := sort'index + 1;
end'while;
end'subr; <<format'sort'key>>
subroutine format'date'type;
begin
if 1 <= sd'field'date'type <= 10 then
case sd'field'date'type of begin
<<0>> ;
<<1>> move outbuf'(56) := "<<YYMMDD>>";
<<2>> move outbuf'(56) := "<<DDMMYY>>";
<<3>> move outbuf'(56) := "<<MMDDYY>>";
<<4>> move outbuf'(56) := "<<YYMM>>";
<<5>> move outbuf'(56) := "<<CALENDAR>>";
<<6>> move outbuf'(56) := "<<YYYYMMDD>>";
<<7>> move outbuf'(56) := "<<DDMMYYYY>>";
<<8>> move outbuf'(56) := "<<MMDDYYYY>>";
<<9>> move outbuf'(56) := "<<PHDATE>>";
<<10>>move outbuf'(56) := "<<ASK>>";
end'case;
end'subr; <<format'date'type>>
subroutine format'decplaces;
begin
if sd'field'decplaces > 0 then
begin
move outbuf'(56) := "<< .";
ascii(sd'field'decplaces,10,outbuf'(60));
move outbuf'(63) := ">>";
end'if;
end'subr; <<format'decplaces>>
subroutine print'field'desc(field'repeat);
value field'repeat;
integer field'repeat;
begin
b'blank(outbuf,bl'outbuf);
move outbuf'(10) := sd'field'name,(16);
if sd'version = "@B.00.00" then
begin
field'repeat := sd'field'repeat;
sd'field'bytelen := sd'field'bytelen / field'repeat;
end'if
else
field'repeat := 1;
if field'repeat <> 1 then
ascii(field'repeat,-10,outbuf'(30));
format'field'type;
if sd'field'type = 3 or <<integer>>
sd'field'type = 4 or <<real >>
sd'field'type = 7 then <<logical>>
ascii(sd'field'bytelen/2,10,outbuf'(32))
else
if sd'field'type = 5 then <<packed>>
ascii(sd'field'bytelen*2,10,outbuf'(32))
else
ascii(sd'field'bytelen,10,outbuf'(32));
ascii(sd'field'offset+1,-10,outbuf'(39));
if sd'version = "@B.00.00" then
begin
format'sort'key(0);
format'date'type;
format'decplaces;
end'if;
print'outbuf(0);
end'subr; <<print'field'desc>>
$page "formselfdesc/mainline"
formselfdesc := false;
if sd'filenum <> 0 then
begin
if file'is'sd then
begin
read'label(file'userlabels-1);
move sd'header := current'label,(sd'label'len);
current'labelnum := file'userlabels - 1;
print'header(0);
field'index := 0;
while get'field(0) do
print'field'desc(0);
print'trailer(0);
formselfdesc := true;
end'if;
end'if;
error'exit:
end'proc; <<formselfdesc>>
.font 0.opt
.sub |1A Different Structure|
`|1A Different Logical Structure|
Our HowMessy/Loadfile example had a number of separate
procedures. Our Formselfdesc procedure is self-contained,
but we will describe each subroutine in this procedure.
.sub |1Variables|
`|1Formselfdesc Variables|
We include our standard files for the global self-describing
equates, header layout, and field layout. We also have a
number of local variables that are used for indexing through
the field labels and other variables needed to enhance the
output listing (e.g., the number of records in the
self-describing file).
.sub |1File'Is'SD|
`|1File'Is'Sd|
This is our standard |1sd'file| procedure, rewritten to work as a
stand-alone SPL subroutine. |1File'Is'Sd| looks after calling the
|1file'info| subroutine which calls Fgetinfo. We initialize a
number of variables during the |1file'info| call. Some of these
are used for obtaining self-describing information and some are
used to enhance the format of our form output (e.g., the filename
and the file limit).
.skip 1
.sub |1Read'Label|
`|1Read'Label|
The basic strategy we use in this routine is to read a specific
label into a buffer called |1current'label|. We then move this
label to the appropriate self-describing header or field buffer.
|1Read'label| is careful to check for file
system errors and abort if it finds any.
.sub |1Get'Field|
`|1Get'Field|
This subroutine is the key to understanding self-describing
files. When |1get'field| is called the last label of the file has
been read into the |1sd'header| record. The variable
|1field'index| is initialized to zero is used as a counter of
self-describing files. Each call to |1get'field| returns one
field description in the |1sd'field| record.
When first called, |1current'labelnum| contains the number of the
last label (minus one, since MPE numbers labels starting at
zero). We check to see if we need to read in a new label with
the statement:
.font 5
if (field'index mod sd'fieldsperlabel) = 0 then
.font 0
Note that we use |1sd'fieldsperlabel| as the divisor. This is the
value from our |1sd'header| record and not our equate that we use
when creating self-describing files. |1Get'Field| assumes that the
current label record is in the buffer |1current'label|.
Each user label contains one or more field descriptions (in most
cases there are eight per label). We compute an offset in the
label where the current field description is and then we move
the field description from |1current'label| to our |1sd'field|
record.
`|1Print'Field'Desc|
This routine looks after printing out the description of one
field. We use the same routine whether we are dealing with
Query ("@A.00.00") or Robelle ("@B.00.00") self-describing
files. We do have to adjust the byte length for "@B.00.00"
self-describing fields, so that the output looks similar to
what Query would produce for an IMAGE dataset. Note how the
|1format'type| routine handles IEEE floating point for either
type of self-describing file.
For "@B.00.00" self-describing files, we can produce extra
information. This is handled by the |1format'sort'key|,
|1format'date'type|, and |1format'decplaces| routines (which are
only called for "@B.00.00" self-describing files.
.sub |1Format'Sort'Key|
`|1Format'Sort'Key|
The sort information is stored in the |1sd'header| record as an
offset, a length, and a type. There is no direct way for us to
tell that a field is sorted. Instead, we index through all of the
sort keys checking if the sort key matches the current field
definition (there might not be a match). We use the index into
the sort information as our key to print for the user.
.sub |1Field'Is'Sorted|
`|1Field'Is'Sorted|
To make our code clearer, we encapsulate the code for checking if
a specific sort key matches the current field in a subroutine. By
giving this subroutine a descriptive name, we make the intent of
the |1format'sort'key| routine clearer. Our |1field'is'sorted|
routine checks that the offset (adjusted appropriately for
one-based and zero-based offsets) and the byte length of the
field and the sort key match. We decided to ignore the data type
(the sd'type and the sort'type have different values).
`|1Summary|
It's harder to understand self-describing files than it is to
create them. When creating self-describing files you often only
use a few of the self-describing features, but when understanding
them there are no features that you can leave out.
.tit
.page.sub
.skip 3.opt(l- r- f-)
`|3KSAM Self-Describing Files|
.opt.skip 2
.tit Adopting Self-Describing Files
.ent `KSAM Self-Describing Files
Self-describing KSAM files are a little trickier to deal with.
The 1084 filecode used for self-describing MPE files doesn't
work well for KSAM. It is more difficult
to create a new KSAM file, since all of the key information must
be passed to Fopen. Here are a few hints for creating and
understanding self-describing KSAM files.
.sub |1SD (1084) Filecode|
`|1SD (1084) Filecode|
You can create a KSAM file with a filecode of 1084, but the
resulting :listf,2 gives no hint that the file is a KSAM
file. Here's an example Build Command of a compatibility-mode
KSAM file with a filecode of 1084 and the resulting :listf,2.
.font 5.page 5.opt(f-)
:run ksamutil.pub.sys
>build file1;rec=-80,16,f,ascii;keyfile=file1key; &&
key=i,6,2;code=1084
>exit
:listf file1&@,2
FILENAME CODE ----------LOGICAL RECORD--------- ----SPACE----
SIZE TYP EOF LIMIT R/B SECTORS #X MX
FILE1 SD 80B FA 0 1023 16 48 1 *
FILE1KEY KSAMK 128W FB 98 98 1 112 1 8
.font 0.opt
Notice how there is no way to identify |5file1| as being a KSAM file.
For this reason, we don't use the 1084 filecode on self-describing
KSAM files.
.sub |1Creating KSAM|
`|1Creating KSAM Self-describing Files|
We use three steps to create self-describing KSAM
files:
.mar(l+3)
`1.#4Compute the number of labels (used in the KSAMUTIL
or MPE/iX build
command).
You could use our |1sd'compute'labels|
subroutine or you can compute the number of labels as the
truncated value of:
#6|5labels = (fields + 7) / 8 + 11|
`2.#4Build your KSAM/V file with KSAMUTIL and
specify Labels=[the number computed above].
For KSAM/XL, use the Build Command with the userlabel keyword |5;ULABEL=|2x|
(where |2x|0 is the number computed above).
`3.#4Fopen the file as an old file with write access.
.mar
The most difficult part is computing the number of labels.
For example, if we have eight fields:
.beginkey example
.font 5
Labels = (8 + 7) / 8 + 11
= 12
|0MPE V/E:|
:run ksamutil.pub.sys
>build file2;rec=-80,16,f,ascii;keyfile=file2k; &&
key=i,6,2,,duplicate;labels=~12~
>exit
|0MPE/iX:|
:build file2;rec=-80,16,f,ascii;key=(i,6,2,dup); &&
ksamxl;ulabel=~12~
.font 0
.sub |1Understanding KSAM|
`|1Understanding KSAM Self-Describing Files|
Our |1sd'file| routine returns true if a given file is
self-describing. If you examine the code in this routine
carefully, you'll see that for KSAM files we have the
statements:
.font 5
if file'foptions.(2:3) = 1 or file'foptions.(2:3) = 3 then
if file'userlabels > 1 then
.font 0
Note that we check for more than one user label. Why don't we
check for more than zero user labels? All self-describing files
must have at least two labels (one for the header information and
one or more for the field information). When we first implemented
our |1sd'file| routine we only checked for more than zero user
labels. What we found was that many users had accidentally built
KSAM files with one user label (which was almost always empty).
We have no idea why this seemed to be so common, but by
checking for at least two labels we eliminated a lot of KSAM
files that were not self-describing.
.tit
.page.sub
.opt(l- r- f-)
`|3Future Self-Describing Formats|
.opt.skip 2
.tit Future Formats
.ent `Future Self-Describing Formats
We were motivated to create the new Robelle format
self-describing files in order to provide a better interface
between our product Suprtool and ASKPlus from ARES of France.
Pierre Senant of ARES is the R&&D Manager and the two of us
worked out the "@B.00.00" self-describing format (actually we
forced most of the format on poor Pierre).
ARES have been doing significant R&&D work on UNIX and a
portable version of ASKPlus. As an example of how far we can
go with self-describing information, here is an extract of
Pierre's design for a UNIX implementation of self-describing
files.
.sub |1SDASK Files|
`|1SDASK Files|
A C/ISAM file is composed of two files, a data file and an
index file. An SDASK file defines another file called a
'label file' which contains the complete description of the data
file. Like MPE self-describing files, an SDASK file is composed
of a header portion and a description of each field.
The data file and the label file must be located in the same
directory.
.sub |1Header Format|
`|1Header Format|
Pierre's header contains a lot more information than our MPE
header label. Here are the parts of the header:
.mar(l+3)
`*#4Version number.
`*#4File code (1085).
`*#4Checksum (currently unused).
`*#4Number of fields per record.
`*#4Record length (in bytes).
`*#4Number of records.
`*#4Number of sort keys.
`*#4Password.
`*#4Total field area length (in the SDASK file).
`*#4File type (flat, C/ISAM, KSAM, Unibol, ...).
`*#4Data file name.
`*#4Unibol area (for migration from IBM/36 to UNIX)
`*#4Filter: logical expression defining a condition that must
be True for the entries taken into account. Originally
developed for Unibol files, but this feature can be used for
any other system.
.mar
.sub |1Field Description|
`|1Field Description|
Each field description is variable length:
.mar(l+3)
`*#4Field type (U, X, I, J, K, P, R). Additional information for
Ascii fields are: Roman-8, PC-8, ANSI-8, Mac-Apple, EBCIDC, and
ISO7-1@...@ISO7-13. For Integer fields, there is additional
information for Intel versus HP. For Real fields, there is
additional information for IEEE versus Classic.
`*#4Length (in bytes).
`*#4Offset.
`*#4Scale (number of decimal places).
`*#4Repeat factor.
`*#4Flags:
.mar(l+3)
`*#7Null value allowed. If this flag is True, each entry in the
data file is preceded by a bitmap field. Each bit
indicates whether the corresponding field value is Null or not.
`*#7Hidden field.
`*#7Key.
`*#7Duplicate key allowed.
.mar
`*#4Field name length.
`*#4Field name.
`*#4Title length.
`*#4Title.
`*#4Edit mask length.
`*#4Edit mask.
`*#4Key file name length.
`*#4Key file (reserved for future implementation on MS-Dos).
.mar
.sub |1Sort Information|
`|1Sort Information|
Sort descriptors are also variable length:
.mar(l+3)
`*#4Expression length.
`*#4Sort expression (in ASKPlus syntax). For example,
.font 5
cust-name
cust-zipcode cat cust-address
.font 0
`*#4Flag: ascending/descending.
.mar
.tit
.page.sub
.opt(l- r- f-)
`|3Conclusion|
.opt.skip 2
.tit Adopting Self-Describing Files
.ent `Conclusion
Self-describing files are a great idea. As users, we almost
always create MPE and KSAM files with a fixed record structure
in mind. By default, this record structure is lost when we build
a file. With self-describing files, we can retain the structure
of our files.
.sub |1A Final Example|
`|1A Final Example|
The HP 3000 has a rich set of tools based on IMAGE. One reason
that so many good tools could be written for IMAGE was the DBINFO
intrinsic. This intrinsic let any program discover the structure
of an IMAGE database. Self-describing files provide the same
flexibility for MPE and KSAM files.
In this example, we show how two tools can be combined by using
self-describing files. Our HowMessy program reports on database
efficiency. While doing so it creates a self-describing file with
the statistics for a database. Once you have this file, it's possible
to use Suprtool to check for certain boundary cases. For example,
.font 5.page 5
:run howmessy.pub.robelle |0{create "loadfile"}|
Enter database: test.suprtest
.font 0
HowMessy creates the self-describing file called Loadfile (with the
structure that we've shown previously). We now use Suprtool to
create a file that has all detail datasets that are more than 85% full
that also have a capacity greater than one:
.font 5.page 5
:run suprtool.pub.robelle
>input loadfile
>if datasettype = "D" and && |0{detail dataset}|
capacity > 1 and &&
loadfactor > 85.00 |0{more than 85% full}|
>output loaddetl,link |0{create SD file}|
>exit
.font 0
At Robelle, we would use our Xpress electronic mail system to mail
the |5loaddetl| file to the system manager. Another alternative
would be to extract the database and dataset names and use them
to create a batch job to automatically increase the capacity of
detail datasets more than 85% full. The possibilities are endless,
but only because HowMessy could provide information to Suprtool via
the self-describing file.
.sub |1Software Tools|
`|1Software Tools|
Few software tools are capable of creating or understanding
self-describing files. This is a shame, since self-describing files
are a powerful data structure. One reason that so few tools handle
self-describing files is that documentation on self-describing files
has been non-existent.
I hope that by publishing this description and the programming
examples in this paper that more vendors and users start creating
and accepting self-describing files.
.if not outtext
.tit
.sub
.for([// l50 //])
.page
.if outdouble
.align
.endif
.count 3
.contents.com reset
.com The FORM that follows is for Contents/Preface:
.for([ // l55 / #33 pr:3 /])
.skip 8
.opt(l- r- f-)
|3Adopting Self-Describing Files|
.skip 3
|3Contents|
.opt
.skip 2
#4|1Page Topic|
.mar(l+4)
.tit |1Contents|
.sub |1Page Topic|
.for([ 'Adopting Self-Describing Files' #26 T:40 // #4 S // l53 /#33 pr:1 /]
+ [ T #26 'Adopting Self-Describing Files':40 // #4 S // l53 /#33 pr:1 /])
.contents( l3 p+)
.mar
.endif.com if not outtext {skip table of contents}