|
by George Stachnik
In the previous article in this series, we began to look at the
intrinsic interface to IMAGE/SQL. This time, we'll explore some of the
IMAGE/SQL intrinsics in a bit more detail. In particular, we'll see how
IMAGE/SQL application programs handle errors of various kinds.
We first covered the idea of intrinsics way back in Part 17 of this series, in
an article entitled "Using Intrinsics." Since it's been a while
since we discussed intrinsics in this series, it's worth taking a few moments
now to review what intrinsics are and why we use them in our application
programs.
In HP 3000 terminology, all software is built from building blocks called
procedures. A procedure is a piece of software that can be
"called" by other procedures. The idea of callable procedures is a
very common programming concept that is found on virtually all computer
platforms. If you learned about programming on Microsoft Windows, on UNIX, or
on some other operating system or platform, you may have learned different
terminology, but the concept is the same on all of these. The terms
routines, subroutines, and modules have all been used at
one time or another to describe things that are conceptually identical to
MPE's callable procedures.
An intrinsic is a special kind of procedure. MPE/iX is made up of
hundreds of procedures, most of which cannot be called by user programs, at
least not directly. The intrinsics are unique in that they can be
called by ordinary user programs. Each intrinsic is designed to do a specific
job. Earlier in this series, we examined some of the intrinsics that can be
used to open files (FOPEN and HPFOPEN). In this article, we're going to look
at a few of the intrinsics that you'd use in order to access an IMAGE/SQL
database on an HP 3000. In particular, we're going to see how your application
program should handle errors that it may encounter along the way.
We saw last time that DBOPEN is used to open IMAGE/SQL databases. When you
call DBOPEN, you must tell it the name of the database you want to open by
passing it to the procedure as a parameter. A parameter is a data item
that is defined by the calling procedure and made available to the called
intrinsic. When an application program calls an intrinsic, it uses the same
mechanism that is used by programmers to call user-written procedures
(subroutines). This is true in virtually every third-generation programming
language (COBOL, Fortran, C, etc.). Since most HP 3000 applications were
written in COBOL, we'll use COBOL examples to show how IMAGE/SQL intrinsics
are typically used.
In the COBOL language, intrinsics can be invoked using the industry standard
CALL verb. Figure 1 shows some
sample COBOL code that calls DBOPEN. The statement that actually passes
control from the user-written COBOL procedure to the DBOPEN system procedure
is the "CALL INTRINSIC" statement that appears in lines 26600 to 26900.
DBOPEN is a general-purpose tool for opening databases. It doesn't
"know" what database you want to open, or how. Therefore, when you
call DBOPEN, you must give it this information. Intrinsics such as DBOPEN
expect to have this information passed to them via parameters. In a
COBOL program, a parameter is a data item in the calling procedure that
is made available to the called intrinsic or procedure.
In the example shown in Figure 1, we're passing DBOPEN four
parameters:
- DB-NAME: This data item contains the name of the database you are opening.
- DB-PASSWORD: This is the database password (as defined in the schema).
- DB-MODE: The mode in which we want the database opened.
- STATUS-ARRAY: A status array.
Whenever you call DBOPEN, you must always pass it the four parameters defined
above, and always in the order shown in Figure 1. DBOPEN always expects
to see these four parameters in this order. Calling DBOPEN without a correct
parameter list will generate an error.
Let's look at each of these parameters individually. The top parts of
Figure 1 contain sample COBOL code from the DATA DIVISION of our
program. Parameters are defined in the DATA DIVISION. The first parameter,
DB-NAME, is defined in line 001930 as "X(10) VALUE SPACES." This
means that 10 bytes have been reserved for DB-NAME, and initialized to ASCII
space characters.
Returning to the lower portion of Figure 1, line 25800 moves the string
" ORDERS;" to DB-NAME. Note the semicolon at the end of the database
name. DBOPEN always expects the string containing the database name to be
terminated with a semicolon. When DBOPEN is called in the way shown in
Figure 1, it will attempt to open a database named ORDERS. If no such
database exists, an error will be generated.
The second parameter passed to DBOPEN contains the database password. Earlier
in this series, we saw how database passwords are defined in the schema.
Figure 2 contains a review of this material. In the partial schema shown
in Figure 2
each password is associated with a class number. When you open an IMAGE/SQL
database, you must specify one of the database passwords that was defined in
the schema. The password that you specify determines your class number, based
on the rules defined in the schema. Your assigned class number, in turn,
determines which parts of the database you can access and how. For example, if
we open the database using a password of CLERK, IMAGE/SQL will assign us a
class number of 4.
Each data item and each dataset in the database is associated with a list of
class numbers in the schema. The READ numbers appear first, followed by the
WRITE numbers. The READ list and the WRITE lists are separated by a
forward-slash ("/"). In Figure 2 the class number 4 appears
in the READ list for the items ACCOUNT, CITY, and ZIPCODE and for the CUSTOMER
dataset. So, if we open the database with a password of CLERK, we have READ
access to all these items and datasets. The sample COBOL code shown in
Figure 1 is passing the string "CLERK;" to DBOPEN in the
password parameter. Like the database name, the password must be terminated
with a semicolon.
Note that the passwords specified in the schema can be hard coded in
application programs (as shown in Figure 1), or entered by the user in
response to a prompt (as we saw last month when we opened a database using the
QUERY program).
Returning to the call to DBOPEN shown in Figure 1, the third parameter
passed to the intrinsic is DB-MODE. This mode parameter is a 16-bit binary
number. In the sample code shown in the figure, it is defined in statement
001920 as "S9(04) COMP," and initialized in statement 026000 to a
value of 3. We discussed the database open mode in Part 24 of this series, and
Table 1 shows the values that are recognized by IMAGE/SQL. A mode of 3
specifies that we are asking for exclusive access to the database in question,
and that we want to be able to modify the database.
By hard coding this value into our program, we are effectively saying that
this program will execute correctly only if it can get exclusive access to the
ORDERS database. When our program calls DBOPEN, it will attempt to open ORDERS
in mode 3. As long as nobody else is running a program that has ORDERS opened
(and remember, this includes 4GLs and programs like QUERY), the call to DBOPEN
will work fine.
What happens if something goes wrong? For example, suppose that somebody else
does have the database open, making our request for exclusive (mode 3)
access fail. That brings us to the fourth and final parameter that we are
passing to DBOPEN: the STATUS-ARRAY. In the sample code shown in Figure
1, the STATUS ARRAY is defined in the middle box (from lines 002380
through 002440), and passed to DBOPEN as the fourth parameter (line 026900).
The STATUS-ARRAY is an array of six binary numbers. The code in Figure
1 assigns the following names to the six parts of the status array:
- CONDITION-CODE
- ENTRY-LENGTH
- RECORD-NUMBER
- CHAIN-LENGTH
- BACKWARD-POINTER
- FORWARD-POINTER
The status array will be used by every IMAGE/SQL intrinsic that we call. That
is, whether you are calling DBOPEN, DBGET, DBPUT, DBCLOSE, or any of the other
IMAGE/SQL intrinsics, you will always pass the STATUS-ARRAY as a parameter.
When a call to an IMAGE/SQL intrinsic succeeds, control is returned to the
calling program, and a value of zero is placed in the first word of the
status-array (in the sample code shown in Figure 1, this is the data
item called CONDITION-CODE). If our call to DBOPEN were to fail for any
reason, IMAGE/SQL would place a non-zero value in the condition-code
item, and return control to the calling program. It is important for
programmers to remember to check the value in CONDITION-CODE after every
IMAGE/SQL intrinsic call and handle errors appropriately.
Figure 3 contains some sample COBOL code that shows how this might be
done. A standard COBOL "IF" statement is used to test the value in CONDITION-CODE.
This statement should always be the first thing that's executed after an
intrinsic call. If the value found in CONDITION-CODE is anything other than
zero, the code shown in Figure 3 will pass control to a user-written
procedure, which in this case we've called IMAGE-ERROR. Otherwise, control
falls through to the next statement, which in this case displays a message
indicating that the ORDERS database has been successfully opened.
The IMAGE-ERROR routine shown in Figure 3
calls another IMAGE/SQL intrinsic called DBEXPLAIN. DBEXPLAIN is a general-purpose
error-handling intrinsic whose purpose is to analyze errors that might be
reported back by any of the other IMAGE/SQL intrinsics. It contains a complete
and up-to-date table of possible error messages. When you call DBEXPLAIN, you
must pass it one parameter only: the status array containing the non-zero
condition code that was returned by one of the other IMAGE/SQL intrinsics.
DBEXPLAIN will analyze the error, and display an appropriate error message on
$STDLIST.
When DBEXPLAIN is finished, it will return control to the calling application
program so that any additional error handling logic can be performed at that
time. In the example shown in Figure 3,
there is no additional error handling shown; we simply execute the COBOL "STOP
RUN." verb, which terminates execution of this program. Usually, a
failure to open a database successfully is a catastrophic error--there's not
much else that a program can do if it cannot open the database. Hence, the
error handling routine shown in Figure 3 takes the drastic action of
terminating the program.
Once a database has been successfully opened, our application program is free
to begin accessing the data stored in the database. Here, too, the path is
fraught with possible errors, and this time around the situation is a bit more
complicated. When DBOPEN failed, there wasn't much for us to do but to
terminate the program. But once we begin accessing the database, many of the
errors we might encounter are less drastic. In many cases, our COBOL program
can be perfectly capable of handling the error and continuing. Figure 4
shows an example of how a COBOL program might handle some less catastrophic errors.
Beginning at line 052500, the sample code shown in Figure 4 prompts the
user to enter an order number. The value that the user types is placed in a
data item called SEARCH-ITEM-VALUE. Next, we call the DBFIND intrinsic. It's
important to understand that DBFIND will not retrieve any records from the
database. (That job is handled by DBGET, as we'll see in just a moment.)
DBFIND's job is simply to determine whether there are any records in the
database that meet a specified criterion. In the last article, we learned that
a collection of records in a detail dataset that share a common search-item
value is referred to as a chain. The purpose of DBFIND is to locate
chains of data entries.
The code shown in Figure 4
will locate a chain of records in the dataset called ORDER-DETAIL. We are looking
for a chain of data entries in which the data item called ORDER-NO contains a
common value (specifically, the value that the user typed, which is now to be
found in SEARCH-ITEM-VALUE). The call to DBFIND is located in line 053700, and
the error handling logic begins in line 054500.
In line 054500, we test the condition code returned by DBFIND. If it is zero,
then all is well. Control will fall through to line 055300, where we will
retrieve the first record in the chain. But if the condition code is not zero,
we do not want to call IMAGE-ERROR immediately as we did in Figure 3.
Instead, we first test CONDITION-CODE for a value of 17 (this test appears in
Figure 4 in line 054600).
A value of 17 in the condition code is IMAGE/SQL's way of telling us that
there are no records that meet the criteria we passed to DBFIND. This is
hardly a catastrophic error; it probably just means that the user mistyped
something. So instead of calling IMAGE-ERROR and terminating the program, we
simply display an appropriate error message in line 054700. At this point, the
program should loop back and prompt the user for another value (this logic is
not shown in the figure).
If, on the other hand, the CONDITION-CODE value returned by DBFIND is anything
other than 17, then this programmer has assumed that whatever has gone wrong,
it's nothing that he wants to try to handle himself. Since most of the other
errors that DBFIND might return concern corruption in the database, this is
not a bad assumption to make. So the code shown in Figure 4 simply
performs the IMAGE-ERROR routine (line 054900), which, as we saw in
Figure 3, will call DBEXPLAIN and terminate the program.
The remainder of the code shown in Figure 4 is used to read the first
record in the chain that was found by DBFIND.
In looking through these examples, you can begin to get a feel for the kind of
logic frequently found in IMAGE/SQL application programs. Next time, we'll
explore that logic in greater detail.
George Stachnik works in technical
training in HP's Network Server Division.
|