# Compaq ACMS for OpenVMS
Writing Server Procedures

Previous | Contents | Index

## 4.3.5 Handling Errors

You typically write an error handler to process errors returned by DBMS when accessing records in a database. The examples in Section 4.3.3 and Section 4.3.4 illustrate how to handle some of the standard errors, such as record-not-found, that DBMS can return when you read, write, or update a record in a database. In addition, also be aware of the error conditions that can occur when you are using DBMS in distributed transactions.

Some DBMS errors are expected and are handled by resuming normal program execution. For example, DBMS returns an end-of-collection error if a procedure reads past the last record in a set. In this case, the program can resume execution and process the records that have been read. DBMS can also return a number of recoverable errors that the program should check for and handle. For example, if DBMS returns a deadlock error, you might want to roll back the transaction and process the transaction again. Finally, DBMS can return a number of nonrecoverable errors. For example, a disk on which one of the database storage areas resides might fail. In this case, the program cannot continue until the problem has been resolved.

A distributed transaction can abort at any time. If a transaction aborts while a step procedure is executing, DBMS automatically rolls back an active database transaction. However, the step procedure will receive an error the next time it executes a DML statement in a database transaction that was participating in the distributed transaction. Therefore, an error handler for a step procedure should check for and handle the errors that DBMS returns in this situation.

Typically, you want to retry a transaction automatically in the event of a recoverable error condition such as a deadlock, lock-timeout or transaction timeout error. If DBMS detects deadlock or lock-timeout error conditions, it returns an error to your step procedure when you access the database. In contrast, if a distributed transaction times out, the distributed transaction is aborted, and ACMS raises a transaction exception in the task. In this case, DBMS returns an error if the step procedure accesses the file after the transaction has aborted.

There is an easy technique, illustrated in examples in this section, that allows you to simplify an exception handler that handles recoverable transaction exceptions in a task definition. The following list indicates how the error handler in the step procedure handles each type of error returned by DBMS:

- Handling recoverable errors
  If an error handler in a step procedure detects a recoverable error condition, such as a deadlock or lock-timeout error, it calls the ACMS$RAISE_TRANS_EXCEPTION service to raise a transaction exception using the ACMS$_TRANSTIMEDOUT exception code. If a distributed transaction does not complete within the specified time limit, ACMS also raises a transaction exception using the ACMS$_TRANSTIMEDOUT exception code. Therefore, using ACMS$_TRANSTIMEDOUT as the exception code in the step procedure means that the exception handler in the task definition has to test for only a single exception code in order to handle all recoverable transaction exceptions.
  If you detect a recoverable error in a step procedure that is using an independent database transaction that is not participating in a distributed transaction, you can roll back the database transaction and repeat the transaction in the step procedure.
- Handling transaction aborts
  If a distributed transaction aborts while a step procedure is executing, DBMS returns one of a number of error status values. If a step procedure detects one of these errors, it raises a transaction exception using the error status. If the error was due to a distributed transaction aborting, ACMS overrides the exception in the task. However, if DBMS returns the error due to some other problem, the task is canceled with the specified exception code.
- Handling nonrecoverable errors
  If an unexpected error occurs, the procedure signals the error information returned by DBMS. If the procedure signals a fatal OpenVMS exception, ACMS writes the error to the audit trail log, cancels the task, and runs down the server process. However, if the procedure signals an error or a warning OpenVMS status, ACMS continues executing the step procedure after writing the error to the audit trail log. The error handler also calls the ACMS$RAISE_NONREC_EXCEPTION service to ensure that the task is canceled.

The following example illustrates the error handler for the COBOL example in Section 4.3.4. If a record with the same badge number already exists in the database, the procedure returns a failure status. If the employee record set is locked, it raises a transaction exception using ACMS$_TRANSTIMEDOUT as the exception code. If the distributed transaction has aborted, it raises a transaction exception using the DBMS error status as the exception code. If any other error condition occurred, the procedure calls DBM$SIGNAL to signal the error and then raises a nonrecoverable exception.

           .
           .
           .
    WORKING-STORAGE SECTION.

    01 return_status                PIC S9(5) COMP.
    01 error_cond                   PIC S9(5) COMP.
    01 dist_tid.
       03 tid_data                  PIC X(16).
           .
           .
           .
    PROCEDURE DIVISION USING emp_wksp_record GIVING return_status.

    DECLARATIVES.
    dml-failure SECTION.
        USE FOR DB-EXCEPTION.

    010-dbm-failure.
        CALL "LIB$MATCH_COND" USING
                    BY REFERENCE DB-CONDITION,
                    BY REFERENCE DBM$_DUPNOTALL,
                    BY REFERENCE DBM$_DEADLOCK,
                    BY REFERENCE DBM$_LCKCNFLCT,
                    BY REFERENCE DBM$_TIMEOUT,
                    BY REFERENCE DBM$_PARTDTXNERR,
                    BY REFERENCE DBM$_NOTIP,
                    BY REFERENCE DBM$_DTXNABORTED
                    GIVING error_cond

        EVALUATE error_cond
            WHEN 1
                MOVE persmsg_empexists TO return_status
            WHEN 2 THRU 4
                CALL "ACMS$RAISE_TRANS_EXCEPTION" USING
                                BY REFERENCE ACMS$_TRANSTIMEDOUT
            WHEN 5 THRU 7
                CALL "ACMS$RAISE_TRANS_EXCEPTION" USING
                                BY REFERENCE DB-CONDITION
            WHEN OTHER
                CALL "DBM$SIGNAL"
                CALL "ACMS$RAISE_NONREC_EXCEPTION" USING
                                BY REFERENCE DB-CONDITION

        END-EVALUATE
        EXIT PROGRAM.
    END DECLARATIVES.
           .
           .
           .

The following example illustrates the error handler for the BASIC example in Section 4.3.4. If the employee's record has been deleted, the procedure returns a failure status. If the record is locked by another user, it raises a transaction exception using ACMS$_TRANSTIMEDOUT as the exception code. If the distributed transaction has aborted, it raises a transaction exception using the DBMS error status as the exception code. If any other error condition occurred, the procedure uses the EXIT HANDLER statement to resignal the error.

           .
           .
           .
        WHEN ERROR IN
            employee_record::emp_badge_number = emp_wksp::emp_badge_number
            # READY CONCURRENT UPDATE FOR TRANSACTION dist_tid
            # FETCH FIRST WITHIN ALL_EMPLOYEES -
                          USING emp_badge_number

            IF employee_record::emp_last_update =                           &
               emp_wksp::emp_last_update                                    &
            THEN
                employee_record = emp_wksp
                sts = SYS$GETTIM( employee_record::emp_last_update BY REF )
                IF ( sts AND 1% ) = 0%                                      &
                THEN
                    CALL LIB$STOP( sts )
                END IF
                # MODIFY employee_record
                pers_change_employee_proc = persmsg_success

            ELSE
                pers_change_employee_proc = persmsg_empchanged
            END IF

        USE
            SELECT LIB$MATCH_COND( DBM_COND, DBM$_END,                      &
                                             DBM$_DEADLOCK,                 &
                                             DBM$_LCKCNFLCT,                &
                                             DBM$_TIMEOUT,                  &
                                             DBM$_PARTDTXNERR,              &
                                             DBM$_NOTIP,                    &
                                             DBM$_DTXNABORTED )

                CASE 1          ! DBM$_END
                    pers_change_employee_proc = persmsg_empdeleted

                CASE 2, 3, 4    ! DBM$_DEADLOCK, DBM$_LCKCNFLCT, DBM$_TIMEOUT
                    CALL ACMS$RAISE_TRANS_EXCEPTION( ACMS$_TRANSTIMEDOUT )

                CASE 5, 6, 7    ! DBM$_PARTDTXNERR, DBM$_NOTIP, DBM$_DTXNABORTED
                    CALL ACMS$RAISE_TRANS_EXCEPTION( VMSSTATUS  )

                CASE ELSE
                    EXIT HANDLER
            END SELECT
        END WHEN
           .
           .
           .

## 4.3.6 Compiling DBMS Procedures

If you are using COBOL, use the COBOL compiler to compile your procedure. However, if you are using another programming language, such as BASIC, use the DBMS DML precompiler when you compile a procedure containing embedded DML statements. The DML precompiler processes the embedded DML statements in your program, producing an intermediate host language source file, which it then submits to the host language compiler to produce an object module.

The DML precompiler command line includes both precompiler and host language compiler qualifiers. For the precompiler, use the /LANGUAGE qualifier to specify in which host language the source is written; you can, optionally, include other qualifiers. On the command line, include any language compiler qualifiers (such as LIST or DEBUG) that you want in effect when the precompiler submits the preprocessed source file to the language compiler using the /OPTION qualifier. For more information on DML precompiler qualifiers, see the DBMS documentation.

The following command line precompiles a procedure called PERS_CHANGE_EMPLOYEE_PROC:

    $ DML/LANGUAGE=BASIC/OPTION="/LIST" PERS_CHANGE_EMPLOYEE_PROC

Note Do not make changes to the language source module created by the DML precompiler and then use the language compiler directly to compile that source module. This rule applies even if you want to make source changes that do not affect DML statements because the next precompilation of the original embedded DML module overwrites the changes you make to the temporary language source module generated by the precompiler.

Chapter 6 explains how to link procedures that use DML.

# 4.4 Using RMS

This section describes how to write step procedures that access RMS files. A step procedure that accesses an RMS file on behalf of an ACMS task is similar to any other program that uses RMS to access a file.

The RMS Journaling layered product provides recovery-unit journaling, after-image journaling, and before-image journaling for RMS sequential, relative, and Prologue 3 indexed files. If you have installed the RMS Journaling product, you can use recovery-unit journaling and distributed transactions to coordinate modifications to records in RMS files with modifications to records in Rdb and DBMS databases. If you do not have the RMS Journaling product, modifications to RMS files will not be coordinated with modifications to Rdb and DBMS databases. See RMS Journaling for OpenVMS Manual for more information on RMS journaling.

This section first discusses how to access RMS files that are marked for recovery-unit journaling. The section then illustrates how to read, write, and modify records in an RMS file. Note that there are no special considerations for using RMS files that are marked for after-image journaling or before-image journaling or for using files that are not journaled.

## 4.4.1 Using Files Marked for RMS Recovery-Unit Journaling

There are no special considerations for using RMS recovery-unit journaling in a distributed transaction started by a task or an agent program. If an RMS file that is marked for recovery-unit journaling is accessed by a step procedure that is participating in a distributed transaction, RMS automatically associates the record stream with the default transaction established by ACMS for the server process. See Chapter 3 for more information on the participation of a step procedure in a distributed transaction.

Note Processing steps that participate in a distributed transaction must not make calls to the RMS Recovery Unit services ($START_RU, $PREPARE_RU, $COMMIT_RU, $END_RU, and $ABORT_RU). Any attempt to intermix these services with distributed transactions leads to unpredictable results.

In contrast, if you access an RMS file marked for recovery-unit journaling outside a distributed transaction, you must start a transaction in the step procedure. Use the OpenVMS transactions services $START_TRANS, $END_TRANS, and $ABORT_TRANS to start and end a transaction in a step procedure. Note that the OpenVMS transaction services have superseded the RMS Recovery Unit services. See RMS Journaling for OpenVMS Manual for more information on using the OpenVMS transaction services and RMS recovery-unit journaling.

## 4.4.2 Reading RMS Records

The examples in this section illustrate how to read a record from an RMS file and return the data to the task in a workspace.

Each example reads a record from a file containing employee records using a key from a field in a workspace. If the record exists, the procedure returns a success status to the task. If the record does not exist, the procedure returns a failure status. Because the PERS_FIND_EMPLOYEE_PROC procedure executes in a server that opens the employee file for read-only access, there is no need to use manual locking statements.

Example 4-12 is a step procedure in COBOL that reads an RMS record.

Example 4-12 Step Procedure in COBOL that Reads an RMS Record

    IDENTIFICATION DIVISION.
    PROGRAM-ID. pers_find_employee_proc.
           .
           .
           .
    DATA DIVISION.

    FILE SECTION.
    FD      emp_file
            EXTERNAL
            DATA RECORD IS employee_record
            RECORD KEY emp_badge_number OF employee_record.
    COPY "pers_cdd.employee_record" FROM DICTIONARY.
           .
           .
           .

    LINKAGE SECTION.
    COPY "pers_cdd.employee_record" FROM DICTIONARY
        REPLACING ==employee_record== BY ==emp_wksp_record==.

    PROCEDURE DIVISION USING emp_wksp_record GIVING return_status.
    MAIN SECTION.

    000-start.
        MOVE persmsg_success TO return_status.
        MOVE emp_badge_number OF emp_wksp_record TO
             emp_badge_number OF employee_record.
        READ emp_file RECORD INTO emp_wksp_record
            KEY IS emp_badge_number OF employee_record
            INVALID KEY
                MOVE persmsg_empnotfound TO return_status
                GO TO 999-end
        END-READ.

           .
           .
           .
    999-end.
        EXIT PROGRAM.

Example 4-13 is a step procedure in BASIC that reads an RMS record.

Example 4-13 Step Procedure in BASIC that Reads an RMS Record

        FUNCTION LONG pers_find_employee_proc( employee_record emp_wksp )

        %INCLUDE "pers_files:pers_common_defns"
        %INCLUDE %FROM %CDD "pers_cdd.employee_record"

        MAP ( emp_map ) employee_record emp_rec

        WHEN ERROR IN
            GET # emp_file,                                         &
                KEY # 0 EQ emp_wksp::emp_badge_number
            MOVE FROM # emp_file, emp_wksp
            pers_find_employee_proc = persmsg_success

        USE
            SELECT ERR
                CASE basicerr_record_not_found
                    pers_find_employee_proc = persmsg_empnotfound
                CASE ELSE
                    CALL ACMS$RAISE_NONREC_EXCEPTION( RMSSTATUS( emp_file ) )
                    EXIT HANDLER
            END SELECT
        END WHEN

        END FUNCTION

## 4.4.3 Writing and Updating RMS Records

This section explains how to write a new record into an RMS file and how to update a record in an RMS file.

Example 4-14 illustrates how to write a new record to a file. The PERS_ADD_EMPLOYEE_PROC procedure is used to store a new record in an employee file using the information entered by the user and passed to the procedure in a task workspace. The procedure first stores the current time in the employee record; the time-stamp field is used for consistency checking by the update procedure. It then initializes the return status to success and writes the new record to the file. Because this procedure is executing in a server that opens the file for read and write access with explicit lock control, the procedure must unlock the record if the write operation completes successfully. If the write operation fails with a duplicate key, the procedure returns an error status to the task.

Example 4-14 Step Procedure in COBOL that Writes an RMS Record

    IDENTIFICATION DIVISION.
    PROGRAM-ID. pers_add_employee_proc.
           .
           .
           .
    DATA DIVISION.

    FILE SECTION.
    FD      emp_file
            EXTERNAL
            DATA RECORD IS employee_record
            RECORD KEY emp_badge_number OF employee_record.

    COPY "pers_cdd.employee_record" FROM DICTIONARY.
           .
           .
           .

    LINKAGE SECTION.
    COPY "pers_cdd.employee_record" FROM DICTIONARY
        REPLACING ==employee_record== BY ==emp_wksp_record==.

    PROCEDURE DIVISION USING emp_wksp_record GIVING return_status.
    MAIN SECTION.

    000-start.
        CALL "SYS$GETTIM" USING
                BY REFERENCE emp_last_update OF emp_wksp_record
                GIVING return_status.
        IF return_status IS FAILURE
        THEN
            CALL "LIB$STOP" USING BY VALUE return_status
        END-IF.

        MOVE persmsg_success TO return_status.
        WRITE employee_record FROM emp_wksp_record
            ALLOWING NO
            INVALID KEY
                MOVE persmsg_empexists TO return_status
            NOT INVALID KEY
                UNLOCK emp_file ALL RECORDS
        END-WRITE.
           .
           .
           .
    999-end.
        EXIT PROGRAM.

Example 4-15 illustrates how to update a record in an RMS file. The PERS_CHANGE_EMPLOYEE_PROC procedure updates a record in an employee file using the information that is entered by the user and passed to the procedure in a task workspace. To conserve resources, the task does not retain server context while the user is modifying the employee's information. Therefore, the procedure must ensure that the information in the record has not changed while the user was updating the information on the screen.

The procedure first rereads the original record in the file and then uses a time-stamp stored in the record to ensure that the version read in this procedure is the same as the version read previously by the PERS_FIND_EMPLOYEE_PROC procedure. If the record has been updated, the procedure returns an error and unlocks the record. If the record has not been changed, the procedure copies the data from the task workspace record to the file record, calls SYS$GETTIM to retrieve the current system time, and updates the current record.

Because the employee file was opened using explicit lock control, the procedure must unlock the record after updating it. The error handling in this procedure checks for record-locked and record-lock timeout errors in case another user is trying to update the employee's record at the same time. In addition, it also checks for a record-not-found error in case the employee's record was deleted while the user was modifying the information. In both cases, the procedure returns an error status so the task can retrieve the error message text and inform the user of the problem.

Example 4-15 Step Procedure in BASIC that Updates an RMS Record

        FUNCTION LONG pers_change_employee_proc( employee_record emp_wksp )

        %INCLUDE "pers_files:pers_common_defns"
        %INCLUDE %FROM %CDD "pers_cdd.employee_record"

        DECLARE LONG sts

        MAP ( emp_map ) employee_record emp_rec

        WHEN ERROR IN
            GET # emp_file,                                         &
                KEY # 0 EQ emp_wksp::emp_badge_number,              &
                ALLOW NONE,                                         &
                WAIT 20
            IF emp_rec::emp_last_update = emp_wksp::emp_last_update &

            THEN
                MOVE TO # emp_file, emp_wksp
                sts = SYS$GETTIM( emp_rec::emp_last_update BY REF )
                IF ( sts AND 1% ) = 0%                              &
                THEN
                    CALL LIB$STOP( sts )
                END IF
                UPDATE # emp_file
                pers_change_employee_proc = persmsg_success

            ELSE
                pers_change_employee_proc = persmsg_empchanged
            END IF
            UNLOCK # emp_file

        USE
            SELECT ERR
                CASE    basicerr_record_not_found
                    pers_change_employee_proc = persmsg_empdeleted
                CASE    basicerr_record_locked,                             &
                        basicerr_deadlock,                                  &
                        basicerr_wait_exhausted
                    pers_change_employee_proc = persmsg_emplocked
                CASE    ELSE
                    CALL ACMS$RAISE_NONREC_EXCEPTION( RMSSTATUS( emp_file ) )
                    EXIT HANDLER
            END SELECT
        END WHEN

        END FUNCTION

## 4.4.4 Handling Errors

You typically write an error handler to process errors returned by RMS when accessing records in a file. The examples in Section 4.4.2 and Section 4.4.3 illustrate how to handle some standard errors, such as record-not-found, that RMS can return when you read, write, or update a record in an RMS file. In addition, also be aware of the error conditions that can occur when you use RMS files in distributed transactions.

Some RMS errors are expected and are handled by resuming normal program execution. For example, RMS returns an end-of-file error if a procedure reads past the last record in a file. In this case, the program can resume execution and process the records that have been read. RMS can also return a number of recoverable errors that the program should check for and handle. For example, if RMS returns a deadlock error, you might want to roll back the transaction and process the transaction again. Finally, RMS can return a number of nonrecoverable errors. For example, a disk on which a file resides might fail. In this case, the program cannot continue until the problem has been resolved.

A distributed transaction can abort at any time. For example, if the PERS_CHANGE_EMPLOYEE_PROC procedure shown in Section 4.4.3 participates in a distributed transaction, the transaction could time out while the procedure is reading the original copy of the employee's record or while updating the record with the new information. If a transaction aborts while a step procedure is executing, RMS automatically rolls back an active recovery unit. If a step procedure reads a record from the file after a distributed transaction has aborted, RMS completes the operation successfully if the record exists and is not locked by another process. However, the step procedure receives an error if it executes a recoverable operation, such as a write or update operation, on the file. Therefore, an error handler for a step procedure should check for and handle the errors that RMS returns in this situation.

If you use RMS in a distributed transaction, you must write a server cancel procedure to release any records that might be read and locked by a step procedure after a distributed transaction aborts. See Chapter 2 for more information on writing server cancel procedures.

Typically, you want to retry a transaction automatically in the event of a recoverable error condition such as a deadlock, lock-timeout or transaction timeout error. RMS returns deadlock and lock-timeout errors to your step procedure when you access the file. In contrast, if a distributed transaction times out, the distributed transaction is aborted, and ACMS raises a transaction exception in the task. In this case, RMS returns an error if the step procedure accesses the file after the transaction has aborted.

There is an easy technique, illustrated in examples in this section, that allows you to simplify an exception handler that handles recoverable transaction exceptions in a task definition. The following list indicates how the error handler in the step procedure handles each type of error returned by RMS:

- Handling recoverable errors
  If an error handler in a step procedure detects a recoverable error condition, such as a deadlock or lock-timeout error, it calls the ACMS$RAISE_TRANS_EXCEPTION service to raise a transaction exception using the ACMS$_TRANSTIMEDOUT exception code. If a distributed transaction does not complete within the specified time limit, ACMS also raises a transaction exception using the ACMS$_TRANSTIMEDOUT exception code. Therefore, using ACMS$_TRANSTIMEDOUT as the exception code in the step procedure means that the exception handler in the task definition has to test for only a single exception code in order to handle all recoverable transaction exceptions.
- Handling transaction aborts
  If a distributed transaction aborts while a step procedure is executing, RMS returns one of a number of error status values. If a step procedure detects one of these errors, it raises a transaction exception using the error status. If the error was due to a distributed transaction aborting, ACMS overrides the exception in the task. However, if RMS returns the error due to some other problem, the task is canceled with the specified exception code.
  If you detect a recoverable error in a step procedure that is using an independent recovery unit that is not participating in a distributed transaction, you can roll back the recovery unit and repeat the recovery unit in the step procedure.
- Handling nonrecoverable errors
  If an unexpected error occurs, the procedure signals the error information returned by RMS. If the procedure signals a fatal OpenVMS exception, ACMS writes the error to the audit trail log, cancels the task, and runs down the server process. However, if the procedure signals an error or warning OpenVMS status, ACMS continues executing the step procedure after writing the error to the audit trail log. The error handler also calls the ACMS$RAISE_NONREC_EXCEPTION service to ensure that the task is canceled.

The following example illustrates how to handle RMS errors using COBOL. In this example, the error-handling code in the Declaratives section uses the RMS error status when checking for record locks because COBOL returns error 30 for all but the record-locked error.

       .
       .
       .
    PROCEDURE DIVISION USING emp_wksp_record GIVING return_status.

    DECLARATIVES.
    employee_file SECTION.
        USE AFTER STANDARD ERROR PROCEDURE ON emp_file.

    employee_file_handler.
        EVALUATE TRUE
            WHEN    ( ( RMS-STS OF emp_file = RMS$_RLK ) OR
                      ( RMS-STS OF emp_file = RMS$_DEADLOCK ) )
                CALL "ACMS$RAISE_TRANS_EXCEPTION" USING
                            BY REFERENCE ACMS$_TRANSTIMEDOUT
            WHEN    ( ( RMS-STS OF emp_file = RMS$_NRU ) OR
                      ( RMS-STS OF emp_file = RMS$_DDTM_ERR ) )
                CALL "ACMS$RAISE_TRANS_EXCEPTION" USING
                            BY REFERENCE RMS-STS OF emp_file

            WHEN    OTHER
                CALL "LIB$SIGNAL" USING
                            BY REFERENCE RMS-STS OF emp_file,
                            BY REFERENCE RMS-STV OF emp_file
                CALL "ACMS$RAISE_NONREC_EXCEPTION" USING
                            BY REFERENCE RMS-STS OF emp_file
        END-EVALUATE.
    END DECLARATIVES.

    MAIN SECTION.
    000-start.
        MOVE persmsg_success TO return_status.

        MOVE emp_badge_number OF emp_wksp_record TO
             emp_badge_number OF employee_record.
        READ emp_file RECORD
            ALLOWING NO OTHERS
            KEY IS emp_badge_number OF employee_record
            INVALID KEY
                MOVE persmsg_empdeleted TO return_status
                GO TO 999-end
        END-READ.

        IF emp_last_update OF employee_record = emp_last_update OF emp_wksp_record
        THEN
            CALL "SYS$GETTIM" USING
                    BY REFERENCE emp_last_update OF emp_wksp_record
                    GIVING return_status
            IF return_status IS FAILURE
            THEN
                CALL "LIB$STOP" USING BY VALUE return_status
            END-IF

            REWRITE employee_record FROM emp_wksp_record
                ALLOWING NO OTHERS
                INVALID KEY
                    CALL "ACMS$RAISE_NONREC_EXCEPTION"
                                USING RMS-STS OF emp_file
            END-REWRITE

        ELSE
            MOVE persmsg_empchanged TO return_status
        END-IF.

        UNLOCK emp_file ALL RECORDS.

    999-end.
        EXIT PROGRAM.

Previous | Next | Contents | Index