Jazz On Line Programming, Part
2. Updating Records
Updating
Files – GET UPDATE and Record Locking.
Understanding
our Program’s Logic
Program
Logic Generated by Jazz
Preparing
to Test our CICS Program – z/OS
More
Detail: Referring to Screen Data
Screen
fields: attributes, length, and error indicators
Creating
New Records with Specified Keys
Next:
Dealing with More than one Record
Prerequisite reading: Introduction to Jazz, Jazz On Line Programming, Introduction
Here we continue the tutorial on writing on line programs with Jazz, introducing the topic of updating records. While the basic mechanism is GET … UPDATE, as it is in batch programs, there are some new things that you have to think about in an on line situation.
In an on-line program, as in a batch program, the basic mechanism with which you update a record is GET … UPDATE. Conceptually the logic is the same in both types of program: to update a record from file CustF: -
Read the input that will update the program
GET CustF UPDATE
Make changes to the CustF record, updating it in memory.
Write the changed CustF record.
But where did the input come from? In a batch program we’d have read a transaction file, but in an online program we’ve probably used logic like this: -
Display a screen asking for a key value (e.g. CustF.Account)
GET Custf with this key value
Display CustF data on the screen.
The user can change any of this data and click <Enter>
Change the record from the data returned from the screen
Write the changed CustF record
A key difference between batch and on line systems is that an on line system is used by many users, each wanting the system to act as if they are its sole user. It is vitally important that another user can’t change the record you’re about to update between the GET … UPDATE and “Write the changed record”. If the record has been changed in the meantime, the money may no longer be in the bank to pay the bill, the airline seat may have been sold to somebody else, etc. Consequences can be dire: money may vanish from a bank, the record of a hospital patient may omit vital drugs, the amount of fuel on the aircraft may show as enough to reach the destination when the plane hasn’t been fuelled at all and so on. So this situation must be prevented, whatever the programming cost.
There is a simple solution: VSAM, DB2, IMS, SQL Server, and all other databases provide statements to lock a record when you GET it for UPDATE. While it is locked no other user can read it. Problem solved!
Well, not quite. This is OK for a very small-scale system, like the personal systems you have on your PC or even a small-office system. However this is not a workable solution for a large on line system. Such a system may have hundreds or even thousands of users using the system at the same time. Imagine banking system where credit card transactions are pouring in from thousands of shops. Or a travel agent making an airline reservation, a process that may take up to half a hour as the travel agent discusses flight options with the customer in front of them or on the phone. In the meantime, other agents around the world are also making reservations, possibly on the same flight. If the first agent’s enquiry had locked up all of the flight records involved no other agent would be able to make a booking, or possibly even an enquiry, that involved these flights.
Each user must be able to use the system as if they were its only user. While waiting for the response from one user, the system goes on with servicing others. When you click <Enter> and send your response to the system it must be able to continue as if uninterrupted: you expect a response in a fraction of a second. It simply isn’t acceptable for the system to seize up because it wants to read a record that is locked by another user. While you’ve been thinking about and entering your updates other users may have updated information in records that you’re accessing. The on-line system must therefore be able to cope with potential problems which arise when two or more users attempt to update the same record, and must not halt because locks are being held, or because too many programs remain in memory waiting for users to decide what key to press.
· When you first read the record you take a copy of it, saving it somewhere that only your transaction can use. You do not lock the record.
· Your program then displays the record to the user, and terminates, awaiting a response (which, of course, might never come).
· The user responds to the first display by updating the record as it is displayed on his screen. The program reads the record again, this time for update, i.e. with a lock request. At this moment we have an unchanged record in the database, and changed fields displayed in the response screen that has just been read.
· Before the program actually goes ahead an updates the record from the response screen it compares the re-read record with its saved copy. Has it been changed in the meantime by another user?
o If the record has not been changed, the record is updated to the new values from the response screen, written out, and the lock released.
o If it has changed the transaction is abandoned (and the lock released) and the user told to re-start. This is actually very unlikely, but not completely impossible.
In this way locks are held only briefly so system performance remains high, but there is no risk of updates being lost without warning. The user may be occasionally inconvenienced by having to redo an update, but in practice this is extremely rare and even if it were not it would still be preferable to the dire consequences of loosing updates.
This is
all quite a lot of detail programming if we’re working with low level languages
like COBOL, but it’s very easy with Jazz which takes care of all of the
“plumbing” to make it happen. To see how
easy this all is with JAZZ, we’ll write a CICS update program, based on the previous
enquiry program. In fact, as with the enquiry,
all we have to do is to click [New] and follow a dialog, this time selecting
program type “1 Table Update”, and Jazz will create the screen, Commarea, and
basic logic for us. In this program the
Commarea has become important: that is a data area that only our transaction
can see, so it is the obvious place to save a copy of the record for later
comparison.
Let’s follow through the process as if we’d written the program from scratch, so that we understand what’s going on.
The logic is now: the user enters the account number, and the record (if present) is displayed. The user can then change any of the fields (except the account number) and update it, or, if the record was not found, insert it into the file.
Like the previous example, Jazz will
generate a pseudo-conversational program, but this time we have to include some
explicit management of this. We need the COMMAREA (Communications area) to
carry a function code, and a copy of the record that we are going to update.
The function code will simply be a CHAR(1) field which will
have values “E” = enquiry, “U” = Update, “D” = Delete”, and “A” = Add. We’ll use CODES to define this, both enforcing the values and also fixing
their meaning: -
Function CHAR(1) CODES(E:Enquiry, U:Update, A:Add, D:Delete) VALUE (Enquiry),
The obvious way to create an area to save a copy of the
CUSTF record is to define it using LIKE.
SAVE LIKE Custf.*
Here is the definition of
COMMAREA that Jazz creates for a 1-table update: -
*#
Created by IBMUSER at 18/06/2015 9:06:07 a.m.
COPY Custf;
DEFINE CICS2C TYPE(COMMAREA) DATA(
Function CHAR(1) CODES (E:Enquiry,U:Update,A:Add,D:Delete) VALUE Enquiry,
SAVE LIKE Custf.*,
JZ-XCTL LIKE Jazz.Flag);
So that the
LIKE
can be processed without error COPY CustF; is included before the DEFINE statement. If we were writing the program by hand we’d
probably write COPY CustF; again, not realising that it’s already copied into the
program. No problem, the duplicate would
be silently ignored.
If we were writing
the program manually we’d now create the screen, using the data defined into
this program from COMMAREA(CUSTUPC) and Custf. Here’s the screen created
automatically when we generated the program.
Because the record layout defines Custf.Name with the DKEY property the
automatic layout allows for lookup by either Account or Name, as did the
previous enquiry program. Because this
is an update program it includes the function code to control what the program
will do: -
The remaining fields are then
arranged according to the default layout rules.
This is only a simple single-record update, but it may be the first classical CICS program that you have written with Jazz, it might even be the first CICS program that you have ever written. So in this chapter we’ll take time to look at the way this program works in some detail. First, here’s the program that Jazz has generated.
If we were creating this screen
manually we’d save and process it, then return to the program to complete the
logic. With the New/Logic function Jazz
does this for us. Here is the logic that
Jazz generated for us when we chose the option “1 Table Update”: -
*# Last Updated by IBMUSER at 18/04/2018 4:31:53 p.m.
PROGRAM CICS2 CICS INSCREEN(CICS2S) TRANSID(TRN2) COMMAREA(CICS2C) EXIT(menu1);
ACCEPT (CICS2S.Function);
#562 I CICS2S.Error used as message field
CASE (CICS2C.Function);
WHEN
(Enquiry);
ACCEPT (CICS2S.Account OR
CICS2S.Name);
#562
I CICS2S.Error used as message field
DEFINE TS1 TS DATA(
Account LIKE CustF.Account);
GET Custf KEY(CustF.Account OR CustF.Name) SAVECOPY CICS2C.SAVE
TS(1);
#373 I GET statement returns one record at a time for
Name
END GET Custf RESETFUNCTION;
WHEN
(Update);
GET Custf WHERE(CustF.Account=CICS2C.SAVE.Account)
REWRITE CHECKCOPY(CICS2C.SAVE);
COPY JZSMth;
COPY JZMDays;
ACCEPT (CICS2S.Region,CICS2S.District,CICS2S.Name,CICS2S.SalesThisMonth,CICS2S.SalesYTD,CICS2S.Billingcycle,CICS2S.DateCommenced);
#562 I CICS2S.Error used as message field
END GET Custf REWRITE RESETFUNCTION;
WHEN
(Add);
CustF.Account
= CustF.$LastKey
+ 1; [Will need to be changed if key is not a number
#361
E Assignment to a key field
GET Custf KEY(CustF.Account) CREATE;
ACCEPT (CICS2S.Region,CICS2S.District,CICS2S.Name,CICS2S.SalesThisMonth,CICS2S.SalesYTD,CICS2S.Billingcycle,CICS2S.DateCommenced)
SETMDT;
#562 I CICS2S.Error used as message field
END GET Custf CREATE RESETFUNCTION;
WHEN
(Delete);
DELETE Custf WHERE(CustF.Account=CICS2C.SAVE.Account)
CHECKCOPY(CICS2C.SAVE) RESETFUNCTION;
END CASE;
SEND Inscreen;
The logic of any CICS program can be represented like this: -
Our program starts with
PROGRAM CICS2 CICS INSCREEN(CICS2S) TRANSID(TRN2) COMMAREA(CICS2C) EXIT(Menu1);
“Do Stuff” has two parts, “Do standard stuff” and “Do Our Stuff”. Before we get to “Do Our Stuff” our program does its own stuff: it reads the input, which is the input screen CICS2S and the communication area CICS2C. However either or both of these might be absent – this will be the case if program CICS2 was invoked from a menu. Or there might have been an error with CICS when the program attempted to read the input message. So, before our program gets to our first Jazz statement (ACCEPT (…); in this case) there is some automatic checking –
Was a message received from the terminal?
If not, the program will send screen CICS2S and wait for a response.
Was there a CICS error?
If yes, display an error message and terminate the program
Notice the second test: “Was there a CICS error?” This should never happen once your program
has been tested, but may happen during your initial program development. Jazz checks
almost every CICS command and I/O command to see that they executed
successfully.
CICS programs terminate through either: -
Normal
Exit. No unexpected errors have been detected.
Abnormal
Exit. An unexpected error has been
detected. Error messages will be displayed, and the program will terminate.
There are a number of normal conditions that our program
will check for such as:
·
we attempt to read a record
and it doesn’t exist, or
·
we’ve entered an
invalid input value.
These are not “unexpected errors”, and the program will
deal with them and terminate normally.
Unexpected errors are things like
·
The file can’t be
found, or it has been corrupted
·
You’ve exited to a
program that doesn’t exist
·
You’ve tried to
display a screen that hasn’t been defined.
This sort of error will cause an abnormal exit.
There is a lot of COBOL-level logic that Jazz handles automatically. The program checks that a valid input message has been received (unless the user clicked Clear, PA1, PA2, or PA3) and sets up logic to handle relevant AID keys: in this case PF3 (return), PF10 (previous record), PF11(next record), and PF12 (cancel).
Once all this has be done the program starts to “Do Our Stuff”, executing the logic that we wrote. In program CICS2 our logic starts with ACCEPT (CICS2S.Function). ACCEPT is an important statement in CICS programs. Let’s look at how it works.
You’ll use ACCEPT to get data from an input screen, and almost never refer
directly to the screen fields except within an ACCEPT statement. For example, our program started with:-
ACCEPT (CICS2S.Function);
It then continued with
CASE (CICS2C.Function);
referring to the field in the COMMAREA, not the screen.
The function of ACCEPT is to process the data from an input source – in this case a 3270 screen - checking it against its definition and, if valid, moving it into another field in your program. If field(s) are invalid then the ACCEPT will produce error messages and cause the screen to be re-displayed for correction. Note that ACCEPT does not cause the program to read the input screen: it has already been read. The input data is read and presented to the program because it is named in INSCREEN. ACCEPT however provides a controlled way of ensuring that the data that you get from the screen is what you want.
The logic of any ACCEPT statement is: -
Check
each field for errors
IF
any errors have been found THEN
Re-display
the screen with error messages and the erroneous fields indicated
Whether we created screen CICS2S ourselves or the Jazz program generator created it for us, the screen was created by placing fields on to it that had already been defined in our program. CICS2C was defined like this: -
*#
Created by IBMUSER at 18/06/2015 9:06:07 a.m.
COPY Custf;
DEFINE CICS2C TYPE(COMMAREA) DATA(
Function CHAR(1) CODES (E:Enquiry,U:Update,A:Add,D:Delete) VALUE Enquiry,
SAVE LIKE Custf.*,
JZ-XCTL LIKE Jazz.Flag);
The screen
definition has recorded the association between CICS2S.Function
and CICS2C.Function,
so that ACCEPT
(CICS2S.Function) is equivalent to
ACCEPT (CICS2C.Function
=
CICS2S.Function). We don’t need to write
the assignment as the relationship is built into the screen, but you may find
it convenient to use assignment-form ACCEPT statements in other situations.
ACCEPT will check that Function has a valid value for
this assignment. “Valid” is defined by the
target field’s definition: in this case Function must be a single character
with a value E, U, A,
or D. If the value is not valid
·
The field’s error indicator will be set. Error indicators are the
asterisks appearing after the field: they will be blank if there is no error,
and * if there is an error.
·
A message will be put in the screen’s error field (if there’s
room).
·
The screen will be sent back to the user for correction.
·
The program will then terminate through its normal exit.
For example, here invalid
data has been entered for five fields, and the program has reached the
statement
ACCEPT
(CUSTUPS.Region,
CUSTUPS.District, CUSTUPS.NAME, CUSTUPS.SalesThisMonth, CUSTUPS.SalesYTD,
CUSTUPS.Cardholder, CUSTUPS.DateCommenced);
All invalid fields are
indicated, but the 80-character Error field can’t describe them all.
For numeric fields Jazz will have created a screen definition in which the attribute bytes are set to numeric, allowing the operator only to enter numbers, a - sign, and a decimal point, so you may wonder why Jazz bothers to check again. However it can be very important to check that input data is valid and experienced programmers never trust data that a user enters, regarding the small cost in computer time to run this check as worthwhile if it detects one error in a million. Also, it is possible to enter invalid data even with Numeric set on at the terminal: for example the value “1.2.” that I entered above to force a “Not numeric” error.
If you look at the CICS2 COBOL program you’ll see that ACCEPT statements may generate a great number of COBOL statements. However the logic is very simple: -
Check that the field(s) is/are valid
IF errors are detected THEN
Re-display the screen with appropriate messages
Terminate the program (unless CONTINUE option is used)
ELSE
Continue with the statements after ACCEPT
END IF
Click
here for more information
on the ACCEPT statement.
Typical on-line logic is to receive an input message, process this (check that it’s valid, look up database records based on input data, etc), and send a response. Thus Jazz program logic is basically: -
ACCEPT (input data);
* Do something with the input data – for example look up a file
* Set up an output message
SEND (output message);
Of course this can
get more complicated as we deal with various situations, but essentially all
on-line logic is a variation on this theme. Our program will (normally)
terminate through either an implied SEND, or through an explicit SEND statement. There are implied SENDs when an ACCEPT finds an error, and when
statements like GET … CHECKCOPY
can’t proceed.
You may set the screen up with assignment statements before the SEND, or you may choose to write a data list in the SEND statement. Thus
SEND CICS2S DATA(CustF.*)
is the same as a series of assignment statements
CICS2S.Name = CustF.Name;
CICS2S.
etc, followed by
SEND CICS2S;
Also of course you can name individual fields: -
SEND CICS2S DATA(Account, Name);
Remember that when you created the
screen you dragged the CustF fields to the screen, so that the screen you
defined is carrying the relationship between a field in the screen and a field
somewhere else. In a SEND statement, as in an ACCEPT
statement, Account refers to both the field CICS2S.Account
and CustF.Account. Jazz sorts out when to refer to the screen field and
when to refer to the other field.
The SEND statement is used for sending a screen (as here), and also sending data to another program, so it has various options including display options like ERROR and CLEAR, options like PROGRAM, COMMAREA and TRANSID that are useful when communicating with different programs, and so on. Click here to read more about the SEND statement. Here we are sending the screen named in the PROGRAM statement with INSCREEN so we could have left out the screen name and written simply SEND;. Also, because we’re re-sending the input screen the SEND statement will assume COMMAREA(CICS2C) , using the value from the PROGRAM statement.
Between ACCEPT (CICS2S.Function) and SEND Inscreen processing
continue depends on the Function value, so a CASE statement is
ideal: -
CASE (CICS2C.Function);
WHEN
(Enquiry);
…
WHEN
(Update);
…
WHEN
(Add);
…
WHEN
(Delete);
…
END CASE;
Logic for Enquiry is like that in program
CICS1 in the previous chapter, with only minor changes. In CICS1 we wrote: -
ACCEPT
(CICS2S.Account
OR CICS2S.Name);
GET
Custf KEY(CustF.Account OR CustF.Name);
and Jazz, on detecting that there were possibly
many records that could be retrieved through CustF.name inserted a definition
of a Temporary Storage File, and a TS option was added to the GET
statement. The same is true here, so
that the logic becomes: -
ACCEPT
(CICS2S.Account
OR CICS2S.Name);
DEFINE
TS1 TYPE(TS) DATA(
Account
LIKE CustF.Account);
GET
Custf KEY(CustF.Account OR CustF.Name) TS(1);
#373 I GET
statement returns one record at a time for Name
Also, because this is an update program the Jazz
generator has made some more changes.
Here is the actual code generated: -
WHEN
(Enquiry);
ACCEPT
(CICS2S.Account
OR CICS2S.Name);
DEFINE
TS1 TS DATA(
Account
LIKE CustF.Account);
GET
Custf KEY(CustF.Account OR CustF.Name) SAVECOPY CICS2C.SAVE TS(1);
#373 I
GET statement returns one record at a time for Name
END
GET Custf RESETFUNCTION;
·
SAVECOPY CICS2C.SAVE causes the program to save the record that has just been read into
CICS2C.Save. This gives us a copy in the
Commarea that we can compare when we want to perform an update.
·
The
GET statement is paired with an END GET. END GET gives us a place to write options like RESETFUNCTION that apply at the end of
the GET logic. GET/END Get creates a “GET Block” which in this case is empty.
·
On the END GET statement Jazz puts the option RESETFUNCTION. This causes the program to reset the function code and produce
an action message. In an update program
after an enquiry you’ll want to reset the function code and display a message
to the user. The message and function
value will depend on whether the record was found, so you’ll write something
like this: -
IF Custf.$Found
= 'Y' THEN;
CICS2S.error
= 'Change record and
click Enter to update it';
CICS2C.Function
= Update;
ELSE;
CICS2S.error
= 'Record not
found. Enter details and click Enter to create it';
CICS2C.Function
= Add;
END
IF;
With RESETFUNCTION all this is done automatically for you, and it will be applied with the
default PF10/PF11 logic as well.
We don’t need to write any code to initialise the record
area for the “Record not found” situation.
Standard Jazz logic will have done this for us already. If we attempt to
GET
CUSTF with key (=Account) 1234 and there is no record for 1234 then the
standard GET logic creates an initial record in memory with
Account=1234, and other values set to their defaults (usually blanks and
zeros).
Now for the Update case:-
GET
Custf WHERE(CustF.Account=CICS2C.Save.Account) REWRITE CHECKCOPY(CICS2C.SAVE);
ACCEPT
(CICS2S.Region,CICS2S.District,CICS2S.Name,
CICS2S.SalesThisMonth,CICS2S.SalesYTD,CICS2S.Billingcycle,CICS2S.DateCommenced);
END GET Custf REWRITE RESETFUNCTION;
·
GET Custf REWRITE CHECKCOPY(CICS2C.SAVE); will read the
record for Update, locking it, and setting a flag CustF.$UpdatePending = ‘Y’. Because we’ve used REWRITE instead of UPDATE this GET will set
an error message and terminate our program if no record is found, preventing a
user from attempting to create a new record through the Update case as we want to force them to use Add if they want to create a new record.
The record is checked against the
copy that was saved in CICS2C.SAVE by the preceding GET Custf SAVECOPY
(CICS2C.SAVE); If the record has been changed since it was
previously read then a message is produced: -
'Record has been changed. Sorry, you need
to re-apply the updates'
The program then exits
(normal exit), abandoning the update, displaying the new Custf record (as
updated by another user) and releasing the lock. Hopefully this will be exceedingly rare. Normally users won’t see this error and the
program continues with ACCEPT.
·
ACCEPT (CICS2S.Region, CICS2S.District, CICS2S.Name, CICS2S.SalesThisMonth, CICS2S.SalesYTD, CICS2S.Billingcycle, CICS2S.DateCommenced); validates these fields. If any of them are invalid then their error indicator
field will be set and an error message put into CICS2S.error. If several fields have errors then CICS2S.error will refer to as many as there are room for within the
(normally) 80 characters, and all relevant error indicators will be set.
If we were writing this ACCEPT
statement ourselves we’d probably have written ACCEPT(CICS2S.?); and used the
Jazz query to select the fields that we wanted to validate.
Note that the data list
DOES NOT include the primary key, CICS2S.Account. You cannot change
a primary key value without deleting the record and re-creating it as a new
record with the new key value. You can
only create a new key value by attempting retrieval by your proposed new key
and finding that the record doesn’t exist.
·
The logic then concludes with
END GET Custf REWRITE RESETFUNCTION;
RESETFUNCTION is
here equivalent to
CICS2S.error
= 'Record Updated';
CICS2C.Function
= Enquiry;
END GET Custf
REWRITE will update
the record, but it won’t create a new record.
If you always want Add to be
handled through Update, with an initial Enquiry to confirm that a key value is not in
use, then you can combine Add and Update cases as described below. However this
only works when users know which key values are available, and is usually
unsuitable, so Jazz generates creates a separate Add case like this: -
WHEN
(Add);
CustF.Account
= Custf.$LastKey
+ 1; [Will need to be changed if key is not a number
#361 E Assignment to a key field
GET
Custf KEY(CustF.Account) CREATE;
ACCEPT
(CICS2S.Region,CICS2S.District,CICS2S.Name,CICS2S.SalesThisMonth,CICS2S.SalesYTD,
CICS2S.Billingcycle,CICS2S.DateCommenced) SETMDT;
END
GET Custf CREATE RESETFUNCTION;
·
Add
logic is going to create a new record, so it needs to know what key to
use. It starts by finding the largest
key currently in use for this file, with the function $LastKey. In this case the test file CustF defines its
key, CustF.Account, as PIC 999999, and at the time of
writing this contains records with Custf.Account values from 000001 to 000300. Custf.$LastKey thus has value 000300. We take advantage of the fact that Custf.Account is a number, allowing us to determine the next available key by simply
adding one to Custf.$LastKey.
As the comment notes, you will have to change this Jazz-generated code
if the key is not a number, either because it’s a CHAR field, or because it’s a
structure containing a compound key.
Ignore message #361: assignments to a key field might be invalid because you’re
trying to update a record key. Here the
assignment is correct.
·
GET Custf KEY(CustF.Account) CREATE; attempts to read to read a record
with the new key (000323) from Custf. Of
course this “fails” and the GET creates a record with the key that
we want, and everything else set to the initial values defined in the Custf
definition: usually blanks and zeros.
GET … CREATE doesn’t use CHECKCOPY, as there is no previous record to
compare. However Jazz still has to
ensure that errors can’t occur when two users add records at the same
time. Like GET … UPDATE; and GET … REWRITE;
GET … CREATE; will lock the new key, so that another user can’t simultaneously create
a record with the same key. GET … CREATE will fail, terminating the program
with a message, if it finds a record.
This is exceedingly improbable but not completely impossible.
·
As
in the Update case GET … CREATE; is followed by ACCEPT to get the field values from the screen and update the record, which is
then written at END GET.
Note the use of SETMDT with the ACCEPT statement. Normally only fields that are entered by the
user are sent to the program, but in some situations, particularly record
addition, this can create difficulties.
Suppose that the user enters all seven values, but there is an error: ACCEPT detects the error and returns the
screen to the user, and the record is not created. The user corrects the error and clicks
[Enter], but without SETMDT only the corrected error field is
received by the program. There may be
new errors because of missing fields, or we may create a new record with blanks
and zeros where we were expecting real values because we entered them first
time. With SETMDT “modified data
tags” are set for all of the fields that are in the ACCEPT statement’s data list, causing
unchanged values as well as the corrected fields to be re-transmitted to the
program.
The Delete
case is: -
DELETE Custf WHERE(CustF.Account=CICS2C.Save.Account)
CHECKCOPY(CICS2C.SAVE) RESETFUNCTION;
· CHECKCOPY is optional with DELETE. If present then it functions as for GET, and the DELETE will be abandoned if the record has changed since the earlier enquiry.
· There is no END DELETE, so RESETFUNCTION is coded on the DELETE statement.
· RESETFUNCTION is equivalent to
CICS2S.error
= 'Record Deleted';
CICS2C.Function
= Enquiry;
Whichever case was executed, if the program reaches the END CASE without having been terminated by an error the program executes
SEND Inscreen;
to send back the screen with whatever changes have been made by our program.
Here is the screen map (68 lines), and the COBOL program (1860 lines). Of course Jazz program CICS2 is rather more complicated than the simple enquiry program, although much less complicated than an equivalent COBOL program. Jazz-generates about 1800 lines of equivalent COBOL: not bad for 14 lines of Jazz. Most of this COBOL is checking to see if the record has been changed, or if the input has errors, but the Jazz programmer doesn’t need to think about it. It just happens: Jazz has looked after it. However, it’s worth pausing to understand what is really going on in this program before we go on with more complicated situations such as multiple-record updates and scrolling screens, otherwise it will seem that the rules become more and more arbitrary. Actually it’s simple once you realise what’s actually happening.
As earlier, before we can test our program we must add it to our CICS application group. In this case we only need to define the program and mapset: -
· DEF PROGRAM(CICS2) GROUP(MANAJAZZ)
· DEF MAPSET(CICS2S) GROUP(MANAJAZZ)
· DEF TRANS(TRN2) PROGRAM(CICS2) GROUP(MANAJAZZ)
We could have left out the transaction code if we intended to only run CICS2 from Menu1. The file is already defined, although we should check that the file allows update, add, and delete (the default is read-only).
If we have already done this but now we’ve modified the program then we don’t repeat these, but we must execute: -
· CEMT SET PROGRAM(CICS2) NEWCOPY
and if we have edited the screen
· CEMT SET PROGRAM(CICS2S) NEWCOPY
Instead of using CEDA commands, CICS FCT, PCT, and PPT entries are defined with ESMAC (Micro Focus’ Enterprise Server and Management). Refer to JazzUGMFStep3 for more information.
This material is written for those of you who want to understand a bit more low-level detail about the way COBOL and 3270 screens work together.
With the screen editor we’ve created a screen that contains several items. Some are constants that our program can’t change, some are fields into which a user can type a value, and some are output-only fields. Jazz will have created a definition for CICS2S. In our Jazz program this looks like any other definition. Just as we can refer to CustF.Name we can refer to CICS2S.Name, and with CHAR data we can assign data to/from the screen fields just as we would any other field provided of course that we’re not trying to do something invalid: -
CICS2S.Name = Custf.Name;
Custf.Name = CICS2S.Name;
You are strongly
recommended to use ACCEPT and SEND to move data from/to screens, as these
statements are designed to handle validation and data conversion.
Under the covers
screen fields are more complex than normal fields, and although Jazz is looking
after most of this complexity for us it helps to understand a little of what is
going on.
Firstly, as well as
the value characters, each field in a 3270-type screen has several more fields:
a SMALLINT length field, and three one-byte control characters that are used to
control the field’s appearance and behaviour. The first of these control
characters takes up space on the screen, the other two do not. Adding to the complexity: it is common to use
different formats for input and output.
Jazz manages this
complexity by generating several fields (including redefinitions) within the
COBOL program. We placed a field Region on the screen and it looked like this:
-
In fact, by
dragging CustF.Region on to the screen we didn’t create one field, we created three: a
constant with value “Region” that appears in green, a data area for the field, and
an error-indicator field. Each of these fields corresponds to several fields in
the generated COBOL. For example, here are the definitions generated for these
three fields: -
001080* Constant VALUE
'Region.'
001090 03 CNST10.
001100 05 LTH PIC S9(4) COMP.
001110 05 ATTR PIC X.
001120 05 COLR PIC X.
001130 05 HLIT PIC X.
001140 05 FILLER PIC X(7).
001150* Field ='CustF.Region'
VALUE '---9'
001160 03 Region.
001170 05 LTH PIC S9(4) COMP.
001180 05 ATTR PIC X.
001190 05 COLR PIC X.
001200 05 HLIT
PIC X.
001210 05 INPT PIC X(4).
001220 05 OUT REDEFINES INPT PIC ---9.
001230* ScreenField VALUE '*'
001240 03 EFLD12.
001250 05 LTH PIC S9(4) COMP.
001260 05
ATTR PIC X.
001270 05 COLR PIC X.
001280 05 HLIT PIC X.
001290 05 OUT PIC X(1).
Firstly, notice
that only the second of these COBOL fields, Region, has a name that we know
about. The caption and the error field have a system-assigned names, “CNST10”
and EFLS35, and so we can’t refer to either of these fields in our Jazz
program. We can only refer to Region.
Secondly, notice
that each field is defined as a group. There isn’t just one field, there are 6!
First there is a Length field called “LTH”, an Attributes field called “ATTR”,
then COLR and HLIT which are used to control the colour and intensity (Dark,
Bright, etc). Finally there are two fields, INPT and OUT fields for data, which
have different PIC values. On input EVERY field is treated as CHAR format and will be
explicitly converted to the target format, with a check to see if it is valid
for that format. Captions and Error Flags are almost as bad, requiring 5
fields!
You do not need to
refer to these COBOL-level fields. If you write
Custf.Account = CICS2S.Account;
Jazz gets the value
from the INPT field. If you write
CICS2S.Account = Custf.Account;
The value is put
into the OUT field, using the PIC defined there. If you write
CICS2S.Account = Custf.Account BRIGHT;
then not only will
it put data into the OUT field but it will set values in ATTR so that the field
is displayed with high intensity.
Furthermore, this
processing is automatic when we use the ACCEPT statement.
When you SEND a field the data is formatted as shown in the screen editor. For
example, SalesThisMonth was shown as: -
SalesThisMonth $$$,$$9.99 *
Thus its value will
be displayed in turquoise, formatted with a leading $ sign etc using the COBOL
picture $$$,$$9.99. For example, following
CICS2S.SalesThisMonth = 1234.56;
SEND CICS2S;
we’d see
SalesThisMonth $1,234.56
displayed on the
screen.
So far so normal, this is just like setting any other value, and the data formatting is exactly the same (except for the colour) as when we print SalesThisMonth. However with screen fields we might want to control attributes such as colour, whether data can be entered into the field, or whether it is even visible. Similarly we sometimes want to control the appearance of constants and error fields: although we can’t write out a new value for a constant such as “SalesThisMonth” (if you want to do this, make it a field), you can make this text appear and disappear, and change its appearance.
To specify field attributes we either add the attribute into the SEND statement’s data list: -
SEND CICS2S DATA(CICS2S.SalesThisMonth BLINK);
or we use a SET
statement: -
SET (CICS2S.SalesThisMonth REVERSE RED BLINK);
For a list of field attributes, look up the description of the SET statement.
The logic created by Jazz doesn’t allow you to specify the key of a new record: it determines this for you in the Add case with
WHEN
(Add);
CustF.Account
= Custf.$LastKey
+ 1;
If you read a record by key – say 21 – and find
this record missing, the program will respond with Function set to Add, and the logic will ignore your key value when it creates the new
key. If you attempt to fool the program
by changing the function to Update the transaction will abort with
message
Update aborted: record does not exist
What if you want your program to allow
this? You could change your program to
combine Add and Update, like this: -
WHEN
(Update,Add);
GET
Custf WHERE(CustF.Account=CICS2C.Save.Account) UPDATE CHECKCOPY(CICS2C.SAVE);
ACCEPT
(CICS2S.Region,CICS2S.District,CICS2S.Name,
CICS2S.SalesThisMonth,CICS2S.SalesYTD,CICS2S.Billingcycle,
CICS2S.DateCommenced) SETMDT;
END
GET Custf UPDATE RESETFUNCTION;
We’ve combined Add and Update in WHEN, and we’ve changed REWRITE to UPDATE in both the GET and END statement. We’ve added SETMDT to the ACCEPT statement, and we will have removed the now-redundant WHEN (Add); case.
We’ve now solved the problem of adding a record
with the missing key 21, but this is probably an unsatisfactory solution. Except for some special situations users are
not likely to know which keys are missing in advance, and now the only way that
we’ve given them to add a new record is to first read the file to find a
missing key. Probably a better solution
is to leave our original Add logic, but add a new function code,
say “AddWithKey”.
Start by adding this to the definition of Function in the Commarea: -
Function CHAR(1) CODES (E:Enquiry,U:Update,A:Add,K:AddWithKey,D:Delete) VALUE Enquiry,
Now change our program as above but with
WHEN
(Update,AddWithKey);
and leaving the WHEN (Add); untouched. Amend the screen to
note this new option.
In Jazz On Line Programming,
Introduction the principles of on-line programming were introduced with a simple
enquiry program. In this chapter we’ve extended this to include on line
updating. In both cases we’ve dealt with a single record, looking up a CustF
record from a VSAM file. It could just as easily been
a CustF record from a DB2 table: the programming differences between VSAM and
DB2 are trivial, amounting to adding DATABASE(name) to
the PROGRAM statement and changing TYPE(VSAM) to TYPE(SQL) in the data definitions.
What we haven’t dealt with is more than a
single record at a time. This is covered in the next User Guide chapter, where we’ll learn how to handle
sets of records: for example a customer record, and the customer’s current orders.