home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
World of A1200
/
World_Of_A1200.iso
/
programs
/
database
/
isam
/
rexxisam.doc
< prev
next >
Wrap
Text File
|
1995-02-27
|
28KB
|
623 lines
==================
= ARexx and ISAM =
==================
Except for the addition of two record-handling functions, described later,
rexxisam.library corresponds almost exactly to isam.library, as the ARexx
functions actually call the functions in isam.library. Therefore, ARexx
users should still refer to the AutoDocs file as well as the main documen-
tation file as your principle source of information.
That having been said, there are a few differences in calling the various
functions.
- The most obvious difference is the presence of the new function EndISAM.
EndISAM is used by rexxisam.library as a signal that you no longer need
isam.library. isam.library is therefore closed. isam.library was auto-
matically opened when your first ISAM function was called.
The reason rexxisam.library is necessary, and its functions not just
added to isam.library, is that when ARexx calls a function library,
it opens the library, calls the function, and then closes the library.
Since closing isam.library indicates that you no longer need to use ISAM,
at that point, any ISAM files that are still open are closed automat-
ically.
So, supposing that you call OpenISAMFile: ARexx opens isam.library and
calls OpenISAMFIle, ISAM opens your file for you, ARexx closes isam.
library, ISAM closes your file again. Oops.
We get around this problem by using rexx.library. This library is, of
course, opened/closed just as isam.library was in our example above.
But opening rexx.library doesn't open isam.library, and closing rexx.
library doesn't close isam.library.
rexx.library has been designed to look for isam.library being open
whenever it is asked to call an ISAM function. If it is open, it calls
the function. If not, it opens the library and then calls the function.
Since the closing of isam.library is not automatic, it is necessary to
explicitly tell rexx.library to close it. This is done by calling
EndISAM. EndISAM takes no parameters.
- the next most obvious difference is that ISAMWhy now has two parameters.
ISAMWhy ordinarily has one parameter (the error number) and returns
a string containing the text of the error message. From ARexx, this
has been changed so that the ISAMWhy function now returns in the same
manner as every other function - it returns a numerical value that
is zero if the function succeeds, and non-zero otherwise. The second
parameter is now the error message text.
- note that in calling ISAM functions (or, indeed, ANY functions in ARexx),
there must be NO SPACE between the end of the function name, and the
opening parenthesis.
Ex. CloseISAMFile( ISAMHandle ) is OK, but...
CloseISAMFile ( ISAMHandle ) will cause an ARexx error.
- note also, that since there are often quite a few parameters in ISAM
functions, that if the function call is divided between two or more
lines, that an extra comma is needed at the end of all but the last
line (as one normally divides the line between parameters), because
ARexx will otherwise interpret the parameter-dividing comma as a
continuation comma, and will concatenate the parameter with the first
parameter on the next line.
Ex. error = OpenISAMFile( "SPECS:employee.specs", 1, <---WRONG
'R', 0, "IH" )
error = OpenISAMFile( "SPECS:employee.specs", 1, , <---RIGHT
'R', 0, "IH" )
- ARexx deals with alphanumeric text strings only - numerical data that
a C or assembler programmer would often use in keys and records (such
as UBYTE, WORD, DOUBLE, etc. ) cannot be directly handled in ARexx.
Also, ISAM often needs to know an ADDRESS of an area of memory where
a record or returned value is to be stored, and ARexx generally doesn't
deal with addresses, except in memory "getspace"d or "allocmem"ed.
This necessitated two alterations:
- records exist only in memory obtained by the getspace or allocmem
calls, and are not directly handled by the ARexx user. Two func-
tions, AssembleRecord and DisAssemble record, take as parameters
the variable holding the address of the record, a string denoting
the types and lengths of the record fields, and a string containing
the names of the variables that will/do contain strings representing
the record fields. (This is simpler than it sounds).
key values being sent to (or returned from) ISAM must also be
held in allocated memory, so that their address may be provided.
They are manipulated in the same manner, with the same functions.
Memory allocated with allocmem() can have several attributes, most of
which may be combined. These attributes are specified as the
(optional) second parameter. The attributes are specified as a
four-byte string. The attributes and strings are as follows:
PUBLIC '0000 0001'x
CHIP '0000 0002'x
FAST '0000 0004'x
CLEAR '0001 0000'x
If the attribute CLEAR is specified (alone or in combination with
another attribute), then the memory is cleared (or turned to all
zeros). It is often a good idea to specify CLEAR, as otherwise,
the memory could contain anything, and usually nothing useful.
Attributes may be combined by specifying the addition of two or
more attributes (visually - ARexx won't add this type of variable)
or using the bitwise-OR function. It may be convenient to set
variables to the attribute values first.
Note that memory cannot be both FAST and CHIP.
Ex.
attributes = '0001 0003'x /* combines PUBLIC, CHIP and CLEAR */
memory = allocmem( 25, attributes )
attributes = BITOR( FAST, CLEAR )
attributes = BITOR( attributes, PUBLIC) /* FAST/CLEAR/PUBLIC
memory = allocmem( 25, attributes ) having been
previously set. */
attributes = BITOR( BITOR( FAST, CLEAR ), PUBLIC ) /* '' */
attribute = CLEAR
memory = allocmem( 25, attribute ) /* gimme 25 bytes,
and clear 'em first */
- functions returning numeric data (such as the record number or number
of counted records) must provide as that parameter a STRING containing
the NAME of the variable in which to store the data.
Ex. error = OpenISAMFile( "SPECS:employee.specs", 1, 'R', 0, "IH" )
if this function returns successfully (error = 0), the variable
IH will have been set to the ISAMHandle of the file being opened.
IH would then be provided to the various other functions that
require an ISAMHandle as a parameter, with NO QUOTES
Ex. error = CloseISAMFile( IH )
because, in this case, the ISAMHandle is being provided TO ISAM,
not returned BY ISAM.
The function ISAMWhy, as mentioned above, also uses this technique
to return the error message text.
Ex.
error = ISAMWhy( 35, "Message" )
The variable MESSAGE will contain the text of message 35, or an
empty string (if 35 is not a valid ISAM error number).
- any ISAM function that requires a numeric parameter (ULONG, WORD, etc.)
need only provide a normal ARexx string containing the required value.
Parameters of BOOL type (C/assembler programmers know the two possible
values as TRUE and FALSE) require only a normal ARexx string contain-
ing the value 1 or 0. Probably the best way (most readable) to accom-
plish this is to make two variables TRUE and FALSE, set them to 1 and 0,
respectively, and then use the variables in your function calls:
Ex.
TRUE = 1; FALSE = 0
error = OpenISAMFile( "SPECS:employee.specs", TRUE, "R", FALSE, "IH" )
- As detailed in the main documentation, ISAM functions return zero for
success, and a number larger than zero (an error number) for failure.
It helpful to define a variable "OK" (or some such name) that, like TRUE
and FALSE above, make the program more readable.
Set the value for success to zero.
Ex.
OK = 0
- any ISAM function that requires a parameter of type CHAR (LockType)
should provide a normal ARexx string with the desired value in the first
character position. Anything beyond the first character will be ignored.
- any memory containing record/key data allocated with getspace() or alloc-
mem() should free the memory (with freespace()/freemem(), respectively)
before the program ends. While it is true that memory allocated with
getspace() will automatically be freed at program end, it is always good
programming practice to explicitly free memory you have allocated.
Memory allocated with allocmem() MUST be explicitly freed using freemem(),
or it will be lost until the next reboot.
- as with any function library (like the support library rexxsupport.library)
you must add rexxisam.library to ARexx's list of libraries, before any
functions in it can be called. You may also want to remove the library
from the list when your program has finished using ISAM (probably at the
end of the program).
(For this to work, rexxisam.library must have been placed in the LIBS:
assigned directory.)
Ex.
if ( ~show( 'L', 'rexxisam.library' ) )
then
do
if ( ~addlib( 'rexxisam.library', 0, -30, 0 ) )
then
do
say "Couldn't open rexxisam library."
exit 10
end
end
...
if ( show( 'L', 'rexxisam.library' ) ) then
remlib( 'rexxisam.library' )
- The example program books.c has been re-written in ARexx, with some
small modification. Take a look at the code to get a better idea how
to incorporate ISAM into your programs, and appropriate whatever you
like for your own use.
---------------------
- THE NEW FUNCTIONS -
---------------------
AssembleRecord( Record, RecLen, Types, VarNames )
- takes a string of types and a string of variable names and creates a
record of data with the indicated types at the indicated address, and
limits the record to the indicated length.
Ex.
AssembleRecord( RecAddr, 32, "s20 u4 f8", "Name NumYears Salary" )
takes the first 20 characters of the string contained in the variable
Name, the numerical variable NumYears expressed as a four-byte un-
signed integer (ULONG), and the numerical variable Salary expressed
as an eight-byte floating point (DOUBLE), and places them one directly
after each other at the address RecAddr. If the various type lengths
exceeded 32 (in this case), an error would have been returned.
Note that it does what you say, whether it is wrong or not: using this
example, suppose Name contained the string "five", which is not 20
characters long. Wherever Name is stored, the letters f i v e will
be transferred to RecAddr, along with the next 16 bytes, which are
NOT part of Name, and could be part of some other variable, or some-
thing else entirely... If AssembleRecord is expecting a numeric
string, and gets "fred" instead, the number zero may be stored, or
maybe something else.
Try to make sure strings contain the proper data.
Text strings can be expanded to the correct length with LEFT(),
RIGHT(), and other functions, and variables may be checked for
containing numeric data with DATATYPE( string, <option> ), where
<option> is "numeric" (any number) or "whole" (integer number).
types interpretation C equivilent(s) permissible lengths
----- -------------- ------------------ -------------------
s string CHAR, CHAR[]/UBYTE[] 1 or larger
u unsigned integer UBYTE/UWORD/ULONG 1, 2, 4
i signed integer BYTE/WORD/LONG/int 1, 2, 4
f floating point FLOAT/DOUBLE 4, 8
A particular variable name may be replaced by "*" if you do not wish
to set/change that field. Note that if a record is assembled for the
first time, and * is specified for a field, and getspace() was used
(or allocmem(), without specifying CLEAR), then there is no way to
predict what what value that field will contain. If allocmem() with
CLEAR is specified, then the memory will be set to all zeros, and
fields not set will remain zero.
Possible errors:
ERROR_INVALID_RECORD_LENGTH a negative length was specified
ERROR_INVALID_KEY_TYPE type wasn't s, u, i, or f
ERROR_INVALID_KEY_LENGTH length was negative, or wasn't
valid for the type, or would result
in the record length being exceeded
DisAssembleRecord( Record, RecLen, Types, VarNames [,Formats] )
- takes the same parameters as AssembleRecord, with the addition of the
optional Formats string. Also like AssembleRecord, a variable name
may be replaced with "*" if you don't care what the field contains and
don't wish to set a variable to its value.
Ex.
types = "f8 f4 s3 s30"
varnames = "score avg nickname name"
error = DisAssembleRecord( RecAddr, 45, types, varnames)
sets the variable SCORE to the floating point value stored in the
first 8 bytes, sets AVG to the floating point value stored in the
next four bytes, sets NICKNAME to a string formed by adding a null
character to the next three bytes, and sets NAME to a string formed
by adding a null character to the last 30 bytes. (These null char-
acters are added to a copy of the record field - the record itself
is unaltered).
As mentioned above, another parameter may be entered after the string
containing the variable names.
This parameter contains C-language "printf"-style formatting strings.
Numerical data is automatically formatted by a minimal printf-style
format, as appropriate to the particular type:
Ex.
type 'u' is formatted using "%u"
type 'i' "%d"
type 'f' "%f"
type 's' "%xs" (where x is the stated length)
but you may substitute another format for the given one for each
field by adding the formatting string parameter.
Ex.
types = "f8 f4 s3 s30"
varnames = "score avg nickname name"
formats = ' %20.7f "Average is: %10.3g" %10s "%40s is his name." '
error = DisAssembleRecord( RecAddr, 45, types, varnames, formats)
Note that, as in the example above (for the variables avg and name), if
anything but the number itself occurs in a format (spaces, or explan-
atory text) the format will have to be enclosed in DOUBLE QUOTES,
which therefore means that the whole formatting string will need to be
enclosed in SINGLE QUOTES. (The space after the first single quote,
and the other space before the final single quote are present just to
draw attention to the single quotes. They are unnecessary, and do not
affect the output.)
(If you don't understand this formatting stuff, just ignore it and
leave out the formatting parameter. The fields will be automatically
formatted. You may then change the way the resultant variable strings
look with the various ARexx internal string-altering commands/functions
after you call DisAssembleRecord.)
--------------------------------
- NOTES ON THE OTHER FUNCTIONS -
--------------------------------
CountISAMRecords( ISAMHandle, KeyNo, CountMax, Count )
- remember to enclose the name of the variable to contain the record
count in quotes.
CloseISAMFile( ISAMHandle )
CreateISAMFile( SpecsFileName )
DeleteISAMFile( SpecsFileName )
DeleteISAMRecord( ISAMHandle, RecNo )
DeleteISAMRecord( ISAMHandle, KeyNo, Count )
- remember to enclose the name of the variable to contain the deleted-
record count in quotes.
GetFirstLastISAMKeyValues( ISAMHandle, KeyNo, FKeyValue, FRecNo,
LKeyValue, LRecNo )
- remember to enclose the name of the variables to contain the record
numbers (FRecNo/LRecNo) in quotes.
- remember that FKeyValue/LKeyValue must be address variables (variables
that contain an address obtained from the ARexx function getspace() or
the rexxsupport.library function allocmem() ).
ISAMWhy( ErrNo, ErrText )
- remember to enclose the name of the variable to contain the error
text in quotes.
LockISAMFile( ISAMHandle, LockType )
LockISAMRecord( ISAMHandle, RecNo, LockType )
ModifyISAMRecord( ISAMHandle, RecNo, Record )
- remember that Record must be an address variable (see GetFirst...).
OpenISAMFile( SpecsFileName, LLock, LockType, SaveHead, ISAMHandle )
- remember to enclose the name of the variable to contain the ISAM
Handle in quotes.
- remember that LLock and SaveHead need to be strings containing the
values 1 or 0 (corresponding, respectively, to TRUE or FALSE).
ReadISAMRecord( ISAMHandle, RecNO, LLock, LockType, Record )
- remember that Record must be an address variable (see GetFirst...).
- remember that LLock needs to be a string containing the value 1 or 0
(corresponding, respectively, to TRUE or FALSE).
ReadUniqueISAMRecord( ISAMHandle, KeyNo, KeyValue, LLock,
LockType, RecNo, Record )
- remember that KeyValue/Record must be address variables (see GetFirst...).
- remember that LLock needs to be a string containing the value 1 or 0
(corresponding, respectively, to TRUE or FALSE).
- remember to enclose the name of the variable to contain the record
number in quotes.
ReadNextISAMKey( ISAMHandle, KeyNo, RecNo, KeyValue )
- remember that KeyValue must be an address variable (see GetFirst...).
- remember to enclose the name of the variable to contain the record
number in quotes.
ReadNextISAMRecord( ISAMHandle, KeyNo, LLock, Locktype, RecNo, Record )
- remember that LLock needs to be a string containing the value 1 or 0
(corresponding, respectively, to TRUE or FALSE).
- remember to enclose the name of the variable to contain the record
number in quotes.
- remember that Record must be an address variable (see GetFirst...).
ReIndexISAMFile( SpecsFileName, Counter )
- remember that Counter needs to be a string containing the value 1 or 0
(corresponding, respectively, to TRUE or FALSE).
- Counter is an optional parameter.
ReportISAMStatus()
SetUpISAMIterationRange( ISAMHandle, KeyNo, IterType, KeyFrom, KeyTo )
- remember that KeyFrom/KeyTo must be address variables (see GetFirst...).
- In the AutoDocs, it mentions that, for certain values of IterType,
KeyFrom/KeyTo "should be NULL". This may be accomplished in three ways:
- Parameters may be omitted.
If KeyTo should be NULL, its parameter may be omitted entirely.
If KeyTo AND KeyFrom should be NULL, BOTH parameters may be omitted.
Ex.
(IH, KN, 1, KF ) ---> KeyTo parm is omitted.
(IH, KN, 0 ) ---> both KeyFrom/KeyTo parms omitted.
This method will not work if KeyTo is necessary, but KeyFrom is not.
- "empty" parameters may be used (nothing between/after the commas):
Ex.
(IH, KN, IT, KF, ) ---> KeyTo parm is empty.
(IH, KN, IT, , KT ) ---> KeyFrom parm is empty.
(IH, KN, IT, , ) ---> KeyFrom/KeyTo parms both empty.
Note that the spaces between/after the commas are not necessary,
and may be left out.
- Either/both NULL parameters may be specified by using the value
zero (a string consisting of just the character "0" ).
Ex.
(IH, KN, 1, KF, 0 ) ---> KeyTo parm is zero.
(IH, KN, 2, 0, KT ) ---> KeyFrom parm is zero.
(IH, KN, 0, 0, 0 ) ---> both KeyFrom/KeyTo parms are zero.
This is the preferred method.
SetUpISAMIterationKey( ISAMHandle, KeyNo, KeyValue )
- remember that KeyValue must be an address variable (see GetFirst...).
SetUpISAMIterationPrefix( ISAMHandle, KeyNo, Prefix, Len )
- remember that Prefix must be an address variable (see GetFirst...).
ShutDownISAM()
StoreISAMRecord( ISAMHandle, Record, LLock, LockType, RecNo )
- remember that Record must be an address variable (see GetFirst...).
- remember that LLock needs to be a string containing the value 1 or 0
(corresponding, respectively, to TRUE or FALSE).
- remember to enclose the name of the variable to contain the record
number in quotes.
UnLockISAMFile( ISAMHandle )
UnLockISAMRecord( ISAMHandle, RecNo )
UnLockAllISAMRecords( ISAMHandle )
-------------------------------------------------------------
- CREATING RECORDS/KEYS, AND MAKING THE SPECIFICATIONS FILE -
-------------------------------------------------------------
The ISAM Specifications (Specs) File needs to be created to hold information
about your data. It contains a line holding the name of the data file that
will be created, another line with the name of the index file to be created,
a line with the record length, and one or more lines of key information.
ISAM needs to know what information in the record is important to you,
important enough to keep in sorted order in the index file. This is the key
information. There might be only one key, or there might be many keys.
Each key may be on a different part of the record, or maybe two keys will
be on the same field(s) (one being sorted smallest-to-largest (ascending),
and the other largest-to-smallest (descending) ). Maybe one key is on the
combination of an ID field and the 1st 10 characters of the LastName, and
another key on the full LastName.
ISAM needs to know both where to find the key in the record, and just how
large the key is. Where it is, is called the key's offset, and is counted
starting from the beginning of the record. If the key starts at the begin-
ning of the record its offset is 0 (zero)
Let's have an example record (let's say, an employee):
types = "u2 s30 f8 s9"
names = "BranchID Name Salary SSN"
(Where BranchID is the code for the particular branch of the company, and
SSN is the Social Security Number, without the dashes).
which we will use when we want to be able to call
AssembleRecord( RecAddr, , types, names )
First, let's establish the record length. (We will assume you will be using
ARexx exclusively with this record, and hence won't need to deal with pad-
bytes (see ISAM.doc) )
Well, we see that we have 4 fields, whose lengths are: 2 ("u2"), 30 ("s30"),
8 ("f8"), and 9 ("s9"). Adding: 2+30+8+9 = 49. So, now we can say:
AssembleRecord( RecAddr, 49, types, names )
Now, let's make a table of the fields and their offsets and lengths, so that
determining keys will be easier:
FIELD OFFSET LENGTH
-----------------------------
BranchID 0 2
Name 2 (=0+2) 30
Salary 32 (=2+30) 8
SSN 40 (=32+8) 9
-------------
49 (=40+9)
Note that the offset for a field is the offset for the previous field plus
the length of the previous field. Note also, that if there were another
field after the others, its offset would be the current record length.
This is a good way to check your math for record length: the last offset plus
the last length better equal the record length.
OK. Now we know where and how large the fields are. Now, let's decide
what keys we want.
Well, we probably want to be able to find the employee by name, so let's
make that the first key: offset = 2. The full name is pretty long.
Surely we don't want to waste disk space indexing the full name, so let's
limit it to 10. Length = 10. We'll assume that the last name will be
first, followed by first (and middle?). It'll be a Text ("T") type, (so
it'll always be in alphabetical order, regardless of case) and we'll prob-
ably want it to be ascending, so we'll get the A's first. Finally, it ought
to be a repeatable key ("R"), so that if John Smith is hired and you already
have a John Smith working for you, it won't reject the second one (you don't
want to have to tell the higher-ups that you couldn't hire John Smith because
you already HAD one, do you?) This, the first key, will be referred to as
key #0.
OK. Next, the Social Security Number. The government will want to track
your employee's taxes, and they do everything by number... Offset = 40,
Length = 9. It doesn't matter if the key is of type Ascii ("A") or Text,
as there will be only numbers in this field/key and numbers don't have a
case. Just for the fun of it, let's make this one Descending ("D"). Social
Security Numbers are unique to each person, so we'll make this a Unique
("U") key. Key #1.
Finally, we'd like to be able to list the employees by branch office, so a
key on BranchID would seem to be in order. However, if we just make a key
on that one field, when we list all employees for one branch, the employees
for that branch will be listed randomly (probably in an order similar to
that in which their records were first entered into the system). We can
fix that: extend the key to cover the BranchID AND the name field. Once
again, let's limit the amount of the Name used to 10 characters. So:
Offset = 0, Length = 12 (2 from BranchID + the first 10 of Name). The Type
will have to be Ascii ("A"). Why? If it is type Text, then it is case-
sensitive, and will be case sensitive along its whole length, not just the
Name portion. Thus if one of the two bytes in the BranchID numeric field
happens to correspond to an upper- or lower-case letter, it will be stored
together with those of the other case - ex: (using just one byte) if the
two BranchID's are 82 and 114, they will be listed together, even though
82 and 114 are way apart numerically, because they correspond to the upper-
and lower-case "R". Using type "A" will fix that problem. It will also
give a slightly different order to the names themselves than it would
under type "T", that the earlier Name key used. (If this were an actual
assignment, you might reconsider your choice of type "T" earlier for Key #0,
just for the sake of consistancy. Or you might not.) We'll want Ascending
("A") again, so we'll still get the names in A-Z order. Once again, we'll
want to allow keys to repeat (so more than one Jane Doe can work at the same
branch) ("R"). Last (Third) Key: Key #2.
So, we may now make the Specs File (remembering that the key lines have
their info in this order: offset, length, type, Ascending/Descending,
Unique/Repeatable):
----------------------------------------------------
BR32:Employee.data
BR32:Employee.index
49
2 10 T A R
40 9 A D U
0 12 A A R
----------------------------------------------------
Where BR32 is an Assign'd logical device name.
If we called the specs file Employee.specs, and placed it in the same loca-
tion as the data/index files, we would then call the CreateISAMFile command
with "BR32:Employee.specs" as the command argument. The Employee ISAM file
would then be ready to use.