COBOL Textbook

COBOL indexed files: ORGANIZATION INDEXED, START, READ KEY, REWRITE

A GnuCOBOL program loads a parts master into an indexed file, reads by key, rewrites a record, browses a key range with START and READ NEXT, and deletes; data ships.

Published
Updated
Dialect
GnuCOBOL 3.x, free format
Status
Compiled and run with GnuCOBOL 3.2
Length
3,070 words, 14 min

A sequential file gives you the records in the order they were written and nothing else. An indexed file gives you a record by its key, the records in key order from any starting point, and the ability to replace or remove one record in place. That is what a master file is, and most of the COBOL that runs a business is a batch program reading transactions sequentially and applying them to a master file by key. This tutorial builds a small parts master from a fixed-width file, then performs every operation the file organization exists for: a random READ by key, a READ followed by REWRITE, a key-range browse with START and READ NEXT, a browse on an alternate key, a DELETE, and a final pass from the lowest key to end of file. The load step reads its input exactly as reading a sequential file to end of file did, and the table of look-up keys is the REDEFINES pattern of three literals viewed as one OCCURS.

The listing was desk-checked against the GnuCOBOL Programmer's Guide and the IBM Enterprise COBOL 6.4 Language Reference, and the expected output quoted below came from the Python companion, which writes the data file and replays every step of the program against an ordinary dictionary. On 29 October 2026 we compiled and ran the program with GnuCOBOL 3.2, built with the Berkeley DB handler for indexed files (cobc --info reports "indexed file handler : BDB"). It printed the output below exactly, once one status code the companion had taken from IBM's table was corrected; the REWRITE section explains it. Where the two compilers document the same statement differently, the difference is stated with its source rather than smoothed over.

The SELECT that makes a file indexed

    SELECT Parts-File
        ASSIGN TO "parts-master"
        ORGANIZATION IS INDEXED
        ACCESS MODE IS DYNAMIC
        RECORD KEY IS Part-No
        ALTERNATE RECORD KEY IS Part-Bin WITH DUPLICATES
        FILE STATUS IS WS-Parts-Status.
Listing 3.3-1. Lines 30 to 36 of cobol-indexed-file.cob.

Four clauses do the work. ORGANIZATION IS INDEXED says records are located by the value of a field inside them; section 5.2.1.4 of the Programmer's Guide puts it as "the actual location of a record in an indexed file is calculated automatically based upon the value(s) of one or more alphanumeric fields within records of the file". RECORD KEY IS Part-No names that field, and the guide adds that "No two records in the file will be allowed to have the same PRIMARY KEY field value". IBM says the same on page 150 of the Language Reference: the values "must be unique among records in the file", and whatever the key's category, "the key is treated as an alphanumeric item" whose collation is its binary value order.

ACCESS MODE IS DYNAMIC is the choice that lets one program do both kinds of reading. The guide's description is that DYNAMIC "allows the file will be processed either in RANDOM or SEQUENTIAL mode; the program may switch between the two modes as needed. The START statement is used to make the switch between modes". With ACCESS MODE SEQUENTIAL, the default, only READ NEXT is available; with RANDOM, only READ by key. IBM's ACCESS MODE clause on page 148 describes DYNAMIC identically, and the next page adds a performance note: dynamic access "is tuned for positioning to a record and then performing sequential reads", so a program that only ever reads by key should say RANDOM.

ALTERNATE RECORD KEY IS Part-Bin WITH DUPLICATES builds a second index, on the storage bin, in which several parts may share a value. Both documents describe the alternate key as a second path for direct access and for sequential processing; IBM's WRITE statement page (477) adds that with DUPLICATES "the system stores the records so that later sequential access to the records allows retrieval in the same order in which they were stored".

The record itself is the copybook /code/cobol-indexed-file-start-read-key-rewrite.cpy, 48 bytes:

Table 3.3-1
Field Positions PIC Role
Part-No 1 to 8 X(8) Prime record key, unique
Part-Desc 9 to 32 X(24) Description
Part-Bin 33 to 36 X(4) Alternate record key, duplicates allowed
Part-On-Hand 37 to 41 9(5) Quantity on hand
Part-Unit-Cost 42 to 48 9(5)V99 Unit cost, implied decimal point

The key is alphanumeric on purpose. IBM's START page carries a warning that a numeric key must be searched with EQUAL TO only, "otherwise, unexpected results can happen", and the guide describes indexed keys as alphanumeric fields throughout. Part numbers like BRK-0210 sort as text, which is what a range browse on a prefix needs.

The program

       >>SOURCE FORMAT IS FREE
*> cobol-indexed-file.cob — build an indexed parts
*>          master from a fixed-width file, then read it by key, rewrite
*>          a record, browse a key range with START, browse an alternate
*>          key, delete a record, and take a final sequential pass
*> Dialect: GnuCOBOL 3.x, free format
*> Status : desk-checked; compiled and run with GnuCOBOL 3.2 on 2026-10-29
IDENTIFICATION DIVISION.
PROGRAM-ID. PARTS-MASTER-DEMO.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
    SELECT Load-File
        ASSIGN TO "datasets/cobol-indexed-file-start-read-key-rewrite.dat"
        ORGANIZATION IS LINE SEQUENTIAL
        FILE STATUS IS WS-Load-Status.
    SELECT Parts-File
        ASSIGN TO "parts-master"
        ORGANIZATION IS INDEXED
        ACCESS MODE IS DYNAMIC
        RECORD KEY IS Part-No
        ALTERNATE RECORD KEY IS Part-Bin WITH DUPLICATES
        FILE STATUS IS WS-Parts-Status.
DATA DIVISION.
FILE SECTION.
FD  Load-File.
01  Load-Line            PIC X(48).
FD  Parts-File.
    COPY "cobol-indexed-file-start-read-key-rewrite.cpy".
WORKING-STORAGE SECTION.
77 WS-Load-Status        PIC XX.
77 WS-Parts-Status       PIC XX.
01 WS-Flags.
   05 WS-EOF-Flag        PIC X VALUE "N".
      88 End-Of-File           VALUE "Y".
   05 WS-Browse-Flag     PIC X VALUE "N".
      88 Browse-Done           VALUE "Y".
77 WS-Loaded             PIC 9(5)     VALUE 0.
77 WS-Rejected           PIC 9(5)     VALUE 0.
77 WS-Count              PIC 9(5)     VALUE 0.
77 WS-Before             PIC 9(5)     VALUE 0.
77 WS-Value              PIC 9(9)V99  VALUE 0.
77 WS-Total              PIC 9(9)V99  VALUE 0.
77 WS-Show-Count         PIC ZZ,ZZ9.
77 WS-Show-Before        PIC ZZ,ZZ9.
77 WS-Show-Money         PIC Z,ZZZ,ZZ9.99.
77 WS-Search-Key         PIC X(8).
77 WS-Search-Bin         PIC X(4).
01 WS-Lookup-Keys.
   05 FILLER             PIC X(8) VALUE "FLT-0305".
   05 FILLER             PIC X(8) VALUE "GSK-0042".
   05 FILLER             PIC X(8) VALUE "BRK-9999".
01 WS-Lookup-Table REDEFINES WS-Lookup-Keys.
   05 WS-Lookup-Key      PIC X(8) OCCURS 3 TIMES.
77 WS-Lookup-Ix          PIC 9    VALUE 0.
77 WS-Receive-Key        PIC X(8) VALUE "BRK-0210".
77 WS-Receive-Qty        PIC 9(5) VALUE 25.
77 WS-Ghost-Key          PIC X(8) VALUE "ZZZ-0000".
77 WS-Browse-Prefix      PIC X(4) VALUE "BRK-".
77 WS-Browse-Bin         PIC X(4) VALUE "B-07".
77 WS-Delete-Key         PIC X(8) VALUE "GSK-0042".
PROCEDURE DIVISION.
Main-Paragraph.
    PERFORM Load-Master
    OPEN I-O Parts-File
    IF WS-Parts-Status NOT = "00"
       DISPLAY "Cannot open parts master for I-O, status " WS-Parts-Status
       STOP RUN
    END-IF
    PERFORM Look-Up-Keys
    PERFORM Receive-Stock
    PERFORM Rewrite-Ghost
    PERFORM Browse-Prefix
    PERFORM Browse-Bin
    PERFORM Delete-Part
    PERFORM Final-Pass
    CLOSE Parts-File
    IF WS-Parts-Status NOT = "00"
       DISPLAY "Close failed, status " WS-Parts-Status
    END-IF
    STOP RUN.

Load-Master.
    OPEN INPUT Load-File
    IF WS-Load-Status NOT = "00"
       DISPLAY "Cannot open the load file, status " WS-Load-Status
       STOP RUN
    END-IF
    OPEN OUTPUT Parts-File
    IF WS-Parts-Status NOT = "00"
       DISPLAY "Cannot create the parts master, status " WS-Parts-Status
       STOP RUN
    END-IF
    PERFORM Read-Load-Line
    PERFORM UNTIL End-Of-File
       MOVE Load-Line TO Part-Record
       WRITE Part-Record
          INVALID KEY
             ADD 1 TO WS-Rejected
             DISPLAY "Load: duplicate key " Part-No
                     " rejected, status " WS-Parts-Status
          NOT INVALID KEY
             ADD 1 TO WS-Loaded
       END-WRITE
       PERFORM Read-Load-Line
    END-PERFORM
    CLOSE Load-File Parts-File
    MOVE WS-Loaded   TO WS-Show-Count
    MOVE WS-Rejected TO WS-Show-Before
    DISPLAY "Loaded " WS-Show-Count " parts, rejected " WS-Show-Before.

Read-Load-Line.
    READ Load-File
       AT END
          SET End-Of-File TO TRUE
    END-READ
    IF WS-Load-Status NOT = "00" AND WS-Load-Status NOT = "10"
       DISPLAY "Load file read failed, status " WS-Load-Status
       STOP RUN
    END-IF.

Look-Up-Keys.
    PERFORM VARYING WS-Lookup-Ix FROM 1 BY 1 UNTIL WS-Lookup-Ix > 3
       MOVE WS-Lookup-Key(WS-Lookup-Ix) TO WS-Search-Key
       PERFORM Look-Up-One
    END-PERFORM.

Look-Up-One.
    MOVE WS-Search-Key TO Part-No
    READ Parts-File
       INVALID KEY
          DISPLAY "Look-up " WS-Search-Key
                  ": not found, status " WS-Parts-Status
       NOT INVALID KEY
          MOVE Part-On-Hand TO WS-Show-Count
          DISPLAY "Look-up " Part-No ": " Part-Desc
                  " bin " Part-Bin " on hand " WS-Show-Count
    END-READ.

Receive-Stock.
    MOVE WS-Receive-Key TO Part-No
    READ Parts-File
       INVALID KEY
          DISPLAY "Receive " WS-Receive-Key
                  ": not found, status " WS-Parts-Status
       NOT INVALID KEY
          MOVE Part-On-Hand TO WS-Before
          ADD WS-Receive-Qty TO Part-On-Hand
          REWRITE Part-Record
             INVALID KEY
                DISPLAY "Receive " WS-Receive-Key
                        ": rewrite failed, status " WS-Parts-Status
             NOT INVALID KEY
                MOVE WS-Before     TO WS-Show-Before
                MOVE Part-On-Hand  TO WS-Show-Count
                DISPLAY "Receive " Part-No ": on hand " WS-Show-Before
                        " -> " WS-Show-Count ", rewrite status "
                        WS-Parts-Status
          END-REWRITE
    END-READ.

Rewrite-Ghost.
    MOVE SPACES         TO Part-Record
    MOVE WS-Ghost-Key   TO Part-No
    MOVE "GHOST"        TO Part-Desc
    MOVE "Z-99"         TO Part-Bin
    MOVE 0              TO Part-On-Hand
    MOVE 0              TO Part-Unit-Cost
    REWRITE Part-Record
       INVALID KEY
          DISPLAY "Rewrite " WS-Ghost-Key
                  ": no such record, status " WS-Parts-Status
       NOT INVALID KEY
          DISPLAY "Rewrite " WS-Ghost-Key ": unexpectedly succeeded"
    END-REWRITE.

Browse-Prefix.
    MOVE 0 TO WS-Count WS-Total
    MOVE "N" TO WS-Browse-Flag
    MOVE WS-Browse-Prefix TO Part-No
    START Parts-File KEY IS GREATER THAN OR EQUAL TO Part-No
       INVALID KEY
          DISPLAY "Browse " WS-Browse-Prefix ": nothing at or after it, status "
                  WS-Parts-Status
          SET Browse-Done TO TRUE
    END-START
    PERFORM UNTIL Browse-Done
       READ Parts-File NEXT RECORD
          AT END
             SET Browse-Done TO TRUE
          NOT AT END
             IF Part-No(1:4) = WS-Browse-Prefix
                ADD 1 TO WS-Count
                COMPUTE WS-Value = Part-On-Hand * Part-Unit-Cost
                ADD WS-Value TO WS-Total
                MOVE Part-On-Hand TO WS-Show-Count
                MOVE WS-Value     TO WS-Show-Money
                DISPLAY "  " Part-No " " Part-Desc " " WS-Show-Count
                        " " WS-Show-Money
             ELSE
                SET Browse-Done TO TRUE
             END-IF
       END-READ
    END-PERFORM
    MOVE WS-Count TO WS-Show-Count
    MOVE WS-Total TO WS-Show-Money
    DISPLAY "Browse " WS-Browse-Prefix ": " WS-Show-Count
            " parts, stock value " WS-Show-Money.

Browse-Bin.
    MOVE 0 TO WS-Count
    MOVE "N" TO WS-Browse-Flag
    MOVE WS-Browse-Bin TO Part-Bin
    START Parts-File KEY IS GREATER THAN OR EQUAL TO Part-Bin
       INVALID KEY
          DISPLAY "Bin " WS-Browse-Bin ": nothing at or after it, status "
                  WS-Parts-Status
          SET Browse-Done TO TRUE
    END-START
    PERFORM UNTIL Browse-Done
       READ Parts-File NEXT RECORD
          AT END
             SET Browse-Done TO TRUE
          NOT AT END
             IF Part-Bin = WS-Browse-Bin
                ADD 1 TO WS-Count
                MOVE Part-On-Hand TO WS-Show-Count
                DISPLAY "  " Part-Bin " " Part-No " " Part-Desc " "
                        WS-Show-Count
             ELSE
                SET Browse-Done TO TRUE
             END-IF
       END-READ
    END-PERFORM
    MOVE WS-Count TO WS-Show-Count
    DISPLAY "Bin " WS-Browse-Bin ": " WS-Show-Count " parts".

Delete-Part.
    MOVE WS-Delete-Key TO Part-No
    DELETE Parts-File RECORD
       INVALID KEY
          DISPLAY "Delete " WS-Delete-Key ": not found, status "
                  WS-Parts-Status
       NOT INVALID KEY
          DISPLAY "Delete " WS-Delete-Key ": status " WS-Parts-Status
    END-DELETE
    MOVE WS-Delete-Key TO WS-Search-Key
    PERFORM Look-Up-One.

Final-Pass.
    MOVE 0 TO WS-Count WS-Total
    MOVE "N" TO WS-Browse-Flag
    MOVE LOW-VALUES TO Part-No
    START Parts-File KEY IS GREATER THAN OR EQUAL TO Part-No
       INVALID KEY
          DISPLAY "Final pass: file is empty, status " WS-Parts-Status
          SET Browse-Done TO TRUE
    END-START
    PERFORM UNTIL Browse-Done
       READ Parts-File NEXT RECORD
          AT END
             SET Browse-Done TO TRUE
          NOT AT END
             ADD 1 TO WS-Count
             COMPUTE WS-Value = Part-On-Hand * Part-Unit-Cost
             ADD WS-Value TO WS-Total
       END-READ
    END-PERFORM
    MOVE WS-Count TO WS-Show-Count
    MOVE WS-Total TO WS-Show-Money
    DISPLAY "Final pass: " WS-Show-Count " parts, stock value "
            WS-Show-Money.

The shipped listing carries a fuller header comment with build and run lines; the body is identical.

Loading: OPEN OUTPUT and WRITE

An indexed file is created by opening it for OUTPUT and writing records. Section 7.8.28 of the guide describes OUTPUT as writing "new content (which will completely replace any previous file contents)", so running the program twice starts clean each time. Under DYNAMIC access the records may arrive in any order; IBM's WRITE page states that only "When ACCESS IS SEQUENTIAL is specified in the file-control entry, records must be released in ascending order of RECORD KEY values". The load file happens to be in key order anyway.

The load file carries 21 lines, and one of them, the second CBL-1150, has a key that is already in the file. Both compilers refuse it. The guide's status list gives 22 as "Key already exists"; IBM's table on page 301 says 22 means "an attempt was made to write or rewrite a record that would create a duplicate prime record key or a duplicate alternate record key without the DUPLICATES phrase in an indexed file". The INVALID KEY branch counts it and moves on, which is the right behaviour for a load: reject the line, keep going, report the count at the end.

READ by key and the file status codes that matter

A random READ has no NEXT. The value to find goes into the RECORD KEY field, and the statement either fills the record area or raises INVALID KEY. Section 7.8.30.2 of the guide is precise about the absent KEY phrase: for an indexed file "the contents of the field declared as the file's RECORD KEY will be used to identify a record". The same section makes a point that catches people writing DYNAMIC-mode programs: the bare statement READ file-name is legal in both formats, so to get a sequential read you "must add either NEXT or PREVIOUS to the statement (otherwise it will be treated as a random READ)". IBM says the same from the other side, on page 428: "You must specify the NEXT RECORD phrase to retrieve records sequentially from files in dynamic access mode".

The look-up of BRK-9999 fails with status 23. The guide's list reads "23 Key not found"; IBM's table reads "An attempt was made to randomly access a record that does not exist in the file". A program that reads a master by key checks for exactly two outcomes, 00 and 23, and treats anything else as an error worth stopping for. The codes that appear in this program:

Table 3.3-2
Status GnuCOBOL Programmer's Guide (5.2.1 SELECT) IBM Enterprise COBOL 6.4 (LR pp. 300-302)
00 Success Successful completion, no further information
02 Success (Duplicate Record Key Written) Also set on a READ when the next record carries the same alternate key value
10 End of file reached if reading forward A sequential READ was attempted and no next logical record existed
21 Key invalid Sequence error: the prime key was changed between a READ and the REWRITE, or ascending order was violated on a sequential-access WRITE; GnuCOBOL 3.2 with the BDB handler also returns it for a REWRITE whose key matches no record
22 Key already exists Duplicate prime key, or duplicate alternate key without DUPLICATES
23 Key not found Random access to a record that does not exist; also a START on an unavailable optional file
35 File not found OPEN INPUT, I-O or EXTEND on a nonoptional file that was unavailable
43 Read not done In sequential access mode, the statement before a DELETE or REWRITE was not a successful READ

The 02 row is a genuine difference in what the documents say. The guide's wording covers only a write that created a duplicate alternate key; IBM's table says the code is also returned by a READ when "the key value for the current key of reference was equal to the value of the same key in the next record". A browse on Part-Bin in this program would therefore see 02 rather than 00 on z/OS for every record but the last in a bin, which is why the NOT AT END branch tests nothing about the status and the loop ends only on AT END or a change of bin.

REWRITE: read it, change it, put it back

Receive-Stock is the update pattern: READ by key, change a field, REWRITE the same record. The guide's section 7.8.35 requires the file to be "currently open for I-O", and IBM's REWRITE page says the same, adding that the record "is logically replaced". The prime key is left alone; only Part-On-Hand changes, from 42 to 67.

What identifies the record being replaced depends on the access mode, and the two documents agree on the rule while stating it differently. For DYNAMIC or RANDOM access, the guide says "no READ is required before a record may be rewritten — the RELATIVE KEY or RECORD KEY definition for the file, respectively, will specify the record to be updated". IBM: "When the access mode is random or dynamic, the record to be replaced is specified by the value contained in the prime RECORD KEY". Rewrite-Ghost leans on that: it fills the record area with a key nobody has and issues a REWRITE with no READ before it. IBM lists this case explicitly as an invalid key condition, "The value contained in the prime RECORD KEY does not equal that of any record in the file", and IBM's table gives it status 23. GnuCOBOL 3.2 takes the INVALID KEY branch too, but with status 21: that is what the program printed, and a short test program got 21 for the same kind of REWRITE with no READ before it, after a successful READ, and after a failed one. We ran only the BDB handler. A program meant for both compilers should treat 21 and 23 alike at this point.

Under SEQUENTIAL access the rules tighten. The record rewritten is the one from the last READ, and IBM's page 433 adds a rule the guide does not spell out: the key value at REWRITE time "must equal the value of the prime record key data item in the last record read from this file", failing with status 21 otherwise. A REWRITE that is not preceded by a successful READ gets 43 in both dialects. One more difference in what is documented: the guide notes that a rewrite "does not cause the contents of the file to be physically updated until the next block of the file is read, a COMMIT or UNLOCK statement is issued or that file is closed", and we found no equivalent statement in the IBM reference.

START and READ NEXT: the key-range browse

The browse is the operation that makes indexed files worth the trouble. Browse-Prefix wants every part whose number begins BRK-. It moves the four-character prefix into the eight-character key, which pads it with spaces; a space sorts below every digit and letter in both ASCII and EBCDIC, so BRK- followed by four spaces is lower than any real BRK part number and higher than any BLT one. START ... KEY IS GREATER THAN OR EQUAL TO Part-No then positions on the first BRK record. The guide describes START as positioning "an internal logical record pointer to a particular record in the file, but does not actually transfer any of that record's data into the record buffer"; IBM's phrasing is that the file position indicator moves to "the logical record in the file whose key field satisfies the comparison". Data only arrives with the READ NEXT that follows, and the loop stops when a key with a different prefix comes back or the file ends.

The worked numbers, from the Python companion: five BRK parts, and because the rewrite already ran, BRK-0210 shows 67 on hand and a value of 1,232.80. The five values add to 3,265.45.

  BRK-0210 BRAKE PAD SET FRONT          67     1,232.80
  BRK-0211 BRAKE PAD SET REAR           37       627.15
  BRK-0330 BRAKE DISC 280MM             12       528.00
  BRK-0331 BRAKE DISC 300MM              9       472.50
  BRK-0500 BRAKE FLUID DOT4 500ML       60       405.00
Browse BRK-:      5 parts, stock value     3,265.45

Browse-Bin is the same shape on the alternate key. The START names Part-Bin, which changes the order READ NEXT travels in: the guide says the sequence follows "the RECORD KEY or the ALTERNATE RECORD KEY most-recently referenced on a START statement", and IBM says the key named in a successful START "becomes the key of reference for subsequent READ statements". Bin B-07 holds three parts, two brake pad sets and a spark plug, and they come back in the order they were written.

Two things about START differ between the compilers and are worth knowing before writing one for both. First, the relation operators. The guide's syntax for 3.x lists EQUAL TO, GREATER THAN, GREATER THAN OR EQUAL TO, NOT LESS THAN, LESS THAN, LESS THAN OR EQUAL TO and NOT GREATER THAN, plus FIRST and LAST, and explains that the "less than" family positions on the last record satisfying the condition, searching last-to-first. The IBM 6.4 format on page 455 lists EQUAL TO, GREATER THAN, NOT LESS THAN and GREATER THAN OR EQUAL TO, and nothing else; there is no LESS THAN, no FIRST, no LAST. The program uses GREATER THAN OR EQUAL TO everywhere, and Final-Pass positions on the first record by moving LOW-VALUES into the key rather than saying START Parts-File FIRST, which GnuCOBOL accepts and IBM does not.

Second, what the KEY operand may be. The guide says "identifier-1 must be the defined RECORD KEY of the file or any of the ALTERNATE RECORD KEY fields for the file". IBM allows those and one more: "A data item within a record description for a file whose leftmost character position corresponds to the leftmost character position of that record key", no longer than the key, with the comparison truncated to the shorter length. That is the classic mainframe generic-key START, where a four-byte field overlaying the front of the key is named directly. The space-padding trick in Browse-Prefix gets the same effect with the full key field, so it works under both.

READ PREVIOUS, which the guide offers for indexed files, has no counterpart in the IBM 6.4 READ statement; a program that needs it is GnuCOBOL-only.

DELETE

Delete-Part removes GSK-0042. Under DYNAMIC access the guide says "the record to be deleted is the one whose primary key is currently the value of the field specified as the RECORD KEY", with no READ required; IBM's page 332 matches, and adds that the record area is not disturbed and that "the key can then be reused for record addition". Both say INVALID KEY may not be coded when the access mode is SEQUENTIAL, where the record deleted is the one from the preceding READ. The look-up that follows the DELETE returns 23, which is the proof that it is gone.

What comes out

The full output, produced by running python code/cobol-indexed-file-start-read-key-rewrite.py and, line for line, by the compiled program:

Load: duplicate key CBL-1150 rejected, status 22
Loaded     20 parts, rejected      1
Look-up FLT-0305: AIR FILTER PANEL         bin F-01 on hand     96
Look-up GSK-0042: HEAD GASKET 1.6L         bin G-05 on hand      6
Look-up BRK-9999: not found, status 23
Receive BRK-0210: on hand     42 ->     67, rewrite status 00
Rewrite ZZZ-0000: no such record, status 21
  BRK-0210 BRAKE PAD SET FRONT          67     1,232.80
  BRK-0211 BRAKE PAD SET REAR           37       627.15
  BRK-0330 BRAKE DISC 280MM             12       528.00
  BRK-0331 BRAKE DISC 300MM              9       472.50
  BRK-0500 BRAKE FLUID DOT4 500ML       60       405.00
Browse BRK-:      5 parts, stock value     3,265.45
  B-07 BRK-0210 BRAKE PAD SET FRONT          67
  B-07 BRK-0211 BRAKE PAD SET REAR           37
  B-07 SPK-0010 SPARK PLUG COPPER           400
Bin B-07:      3 parts
Delete GSK-0042: status 00
Look-up GSK-0042: not found, status 23
Final pass:     19 parts, stock value     7,709.80

The final pass counts 19 parts: 21 lines loaded, one rejected as a duplicate, one deleted. The stock value of 7,709.80 includes the 25 received units of BRK-0210 at 18.40 and excludes the six head gaskets at 38.00 that were deleted. Every edited field is printed through the same pictures the COBOL uses, ZZ,ZZ9 for counts and Z,ZZZ,ZZ9.99 for money, and the compiled program's lines matched the Python lines byte for byte.

Compile and run

python code/cobol-indexed-file-start-read-key-rewrite.py
cobc -x -I code -o parts-master-demo code/cobol-indexed-file.cob
./parts-master-demo

Run all three from the folder that holds code/ and datasets/. ASSIGN TO "parts-master" is a relative name, and the GnuCOBOL Manual's runtime configuration documents COB_FILE_PATH as the "default location where data files are stored", defaulting to the current directory, so the indexed file lands beside the executable unless that variable says otherwise.

Limitations

What the indexed file looks like on disk is not something the Programmer's Guide states. Our GnuCOBOL 3.2 run, with the BDB handler, left two files in the folder, parts-master and parts-master.1. The 3.x NEWS file names the backend in its 3.2 entry, "ORGANIZATION INDEXED (with BDB backend): internal changes in record and file locking", and its planned 3.3 entry adds COLLATING SEQUENCE on indexed files "currently only with the BDB backend", so the file format belongs to whichever library the build was configured with, and a file written by one build should not be assumed readable by another. On z/OS the equivalent is a VSAM key-sequenced data set, which IBM's Programming Guide lists as supporting sequential, random and dynamic access; how such a data set is defined before the program runs is outside this lesson.

The guide used throughout says on its own first page that it documents the July 2020 build of GnuCOBOL 3.1 RC-1, so anything about indexed files that changed in 3.2 would not appear in it. The record-locking clauses on READ and REWRITE, and the note that the file is not physically updated until a block is read or the file is closed, are in the guide but were not exercised here, and a program that shares a master with other processes needs them.

Finally, the load step trusts the data file. Nothing checks that Part-On-Hand and Part-Unit-Cost hold digits before they are moved into PIC 9 fields; a non-numeric byte there would corrupt the arithmetic in the browse without any file status telling you so. The input validation lesson later in the queue is where that check belongs.

Correction, 29 October 2026: the ghost REWRITE was shown failing with status 23, the code IBM documents for a REWRITE whose key matches no record. A real GnuCOBOL 3.2 run returns 21, so the output line, the companion that printed it and the REWRITE section are corrected, and the status table notes it. The build command shown before also named the source cobol-indexed-file-start-read-key-rewrite.cob, and GnuCOBOL 3.2's cobc refuses a source file whose base name is longer than 31 characters ("invalid file base name ... length exceeds maximum"), even with -o, so it failed as printed; the listing is now shipped as cobol-indexed-file.cob, and the old download address redirects there. With the status corrected, the output shown matches a real run line for line, so the page now says the program was compiled and run.

Index terms cobol, gnucobol, indexed file, start, rewrite