Jazz On Line Programming, Part 2.  Updating Records

Jazz On Line Programming, Part 2.  Updating Records. 1

Updating Files – GET UPDATE and Record Locking. 1

A Single-Record Update. 3

The COMMAREA.. 3

The 3270 Screen. 4

Understanding our Program’s Logic. 4

Program Logic Generated by Jazz. 5

Standard Logic. 5

ACCEPT. 7

Overall Program Logic. 9

WHEN (Enquiry); 10

WHEN (Update); 10

WHEN (Add); 12

WHEN (Delete); 13

Preparing to Test our CICS Program – z/OS.. 13

More Detail: Referring to Screen Data. 14

Screen fields: attributes, length, and error indicators. 15

Creating New Records with Specified Keys. 16

Next: Dealing with More than one Record. 16

 

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 GETUPDATE, as it is in batch programs, there are some new things that you have to think about in an on line situation.

Updating Files – GET UPDATE and Record Locking

In an on-line program, as in a batch program, the basic mechanism with which you update a record is GETUPDATE. 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 GETUPDATE 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.

To prevent problems we must lock the records, but if we lock the records our system may grind to a halt.  Neither seems acceptable, and we seem to have a choice between shooting ourselves in the left foot or the right foot.  How do we solve this large-system dilemma?   Fortunately there’s a standard solution to this problem.  It works like this: -

·         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.

A Single-Record Update

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 COMMAREA

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.

The 3270 Screen

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.

Understanding our Program’s Logic

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.

Program Logic Generated by Jazz

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;

 

Standard Logic

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.

ACCEPT

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.

Overall Program Logic

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.Balance = CustF.Balance;

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;

WHEN (Enquiry);

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).

WHEN (Update);

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. 

WHEN (Add);

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.

WHEN (Delete);

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.

Preparing to Test our CICS Program

z/OS

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

Micro Focus

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.  

More Detail: Referring to Screen Data

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.

Screen fields: attributes, length, and error indicators

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.

Creating New Records with Specified Keys

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.

Next: Dealing with More than one Record

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.