Relative Files
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against ISO/IEC 1989:2023 (edition 3, January 2023; an upcoming 4th edition is in ballot), in which relative files are part of the file-handling facility inherited from COBOL 85; against IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026), where a relative file is a VSAM relative-record data set (RRDS) on z/OS 3.2; and against GnuCOBOL 3.2 (28 July 2023, the latest release), with which every program on it was compiled with cobc -x -Wall and run on 2026-10-20 (and again with -std=ibm, which printed the same results).
A relative file is a file of numbered record areas, called slots. The program does not search for a record: it names the slot number, and the access method goes straight to that slot. This page covers the SELECT entry for a relative file, the three access modes, the statements that work on it (WRITE, READ, REWRITE, START, DELETE), the way a business key is turned into a slot number, and the z/OS side, where the file is a VSAM RRDS defined with IDCAMS. The general FD, SELECT and OPEN rules are on File Concepts, SELECT and FD; the other two file organizations are on Sequential Files and Indexed Files and VSAM; the full two-character status table is on File Status Codes. The examples use the Circulation public-library scenario: the member master is kept in a relative file whose slot number is derived from the member’s card number.
Where the book is outdated: Taulli’s Modern Mainframe Development (2022, chapter 5) lists the VSAM data set types, KSDS, ESDS and RRDS, and shows the IDCAMS DEFINE CLUSTER command, but it does not cover the COBOL ORGANIZATION RELATIVE clause itself (no RELATIVE KEY, no START on a relative file, no DELETE by slot). This page supplies them from the official manuals. The book’s examples also run on GnuCOBOL through OpenCobolIDE, an unmaintained IDE (see Getting Started).
The idea: a file of numbered slots
Think of the file as a row of record areas. Slot 1 is the first, slot 10 is the tenth, and the number of a slot never changes when other slots are filled or emptied. A slot either holds a record or is empty. Reading slot 4 when it is empty is not an error in the file, it is a normal "no such record" answer (status 23).
| Sequential | Indexed | Relative | |
|---|---|---|---|
How a record is found |
Read in the order written |
By the value of a key field inside the record |
By the slot number, which is not part of the record |
Gaps |
None |
None; the index decides the order |
Yes; empty slots between records are normal |
Delete |
Not possible (rewrite the file) |
|
|
Order of sequential reads |
Order written |
Ascending key |
Ascending slot number, skipping empty slots |
z/OS data set |
QSAM or VSAM ESDS |
VSAM KSDS |
VSAM RRDS |
The SELECT entry
SELECT [OPTIONAL] file-name ASSIGN TO assignment-name
ORGANIZATION IS RELATIVE
[ACCESS MODE IS {SEQUENTIAL | RANDOM | DYNAMIC}]
[RELATIVE KEY IS data-name]
[FILE STATUS IS data-name].
-
ORGANIZATION IS RELATIVEmarks the file as a relative file. The organization is fixed when the file is created and cannot be changed afterwards. -
ACCESS MODEdefaults toSEQUENTIAL.RANDOMandDYNAMICare the two modes that use the slot number to reach a record. -
RELATIVE KEYnames the data item that holds the slot number. It is required forRANDOMandDYNAMICaccess. ForSEQUENTIALaccess it is optional, but needed if the program usesSTARTor wants to know which slot eachREADreturned. In GnuCOBOL the wordsIS,KEYandMODEare optional; write them anyway for readability and portability. -
The key item must be an unsigned integer without the
Ppicture symbol, and it must not be a field of the file’s own record: the slot number is not part of the record. Enterprise COBOL states both rules; GnuCOBOL enforces them at compile time (see the diagnostics below). -
The key item must be large enough for the highest slot number. A
WRITEwhose slot number has more significant digits than the key item can hold raises an invalid-key condition. -
FILE STATUSis not specific to relative files but is strongly advised: it is how a program tells "empty slot" (23), "slot already in use" (22) and a real I/O error apart. -
A relative file cannot be assigned to the console, a printer or a line-sequential device. Records may be fixed or variable length; on GnuCOBOL a variable-length record still reserves room for the largest record in the
FD.
Access modes and the statements they allow
| Statement | SEQUENTIAL |
RANDOM |
DYNAMIC |
|---|---|---|---|
|
Yes, next occupied slot |
Not allowed |
|
|
Not allowed |
Yes, slot in the relative key |
Yes, when the |
|
Next slot after the last written (output or extend) |
Any empty slot, in any order |
Any empty slot, in any order |
|
After a successful |
Slot in the relative key |
Slot in the relative key |
|
After a successful |
Slot in the relative key |
Slot in the relative key |
|
Yes, needs a |
Not allowed |
Yes |
-
Sequential access reads the occupied slots in ascending slot number. After every successful
READthe relative key item, if one was named, holds the slot number of the record just read. In sequential output, eachWRITEputs the record in the next slot, and the key item receives that number. -
Random access takes the slot number from the key item for every
READ,WRITE,REWRITEandDELETE. A program that never walks the file in order should use random rather than dynamic access. -
Dynamic access lets one program mix both: a keyed
READ, thenSTARTto position, thenREAD NEXTfor a run of records. This is the mode used for "find the slot, then list from there". -
On z/OS the order in which slots are written when a file is loaded does not matter with random or dynamic access; with sequential output the records must be written in ascending order. Enterprise COBOL notes that loading sequentially is faster than loading randomly.
Loading the file by slot number
The member master has card numbers that start at 100, so the slot number is MEMBER-ID - 99: card 100 goes into slot 1, card 101 into slot 2. One late member, card 105, is stored too, which leaves slots 4 and 5 empty. A last WRITE repeats slot 6 on purpose to show the invalid-key path. The input is the line-sequential MEMBMAST of the sample data; the output file is MEMBSLOT.
IDENTIFICATION DIVISION.
PROGRAM-ID. SLOTLOAD.
* Load the member slot file (RRDS-style) from MEMBMAST.
* Slot number = MEMBER-ID - 99: card numbers start at 100.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT MEMBER-IN ASSIGN TO "MEMBMAST"
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-IN-STATUS.
SELECT MEMBER-SLOTS ASSIGN TO "MEMBSLOT"
ORGANIZATION IS RELATIVE
ACCESS MODE IS RANDOM
RELATIVE KEY IS WS-SLOT
FILE STATUS IS WS-SLOT-STATUS.
DATA DIVISION.
FILE SECTION.
FD MEMBER-IN.
COPY MEMBREC REPLACING ==MEMBER-RECORD== BY ==IN-RECORD==
==MEMBER-ID== BY ==IN-ID==
==MEMBER-NAME== BY ==IN-NAME==
==MEMBER-JOINED== BY ==IN-JOINED==
==MEMBER-FINES== BY ==IN-FINES==
==MEMBER-STATUS== BY ==IN-STATUS==
==MEMBER-ACTIVE== BY ==IN-ACTIVE==
==MEMBER-SUSPENDED== BY ==IN-SUSPENDED==.
FD MEMBER-SLOTS.
COPY MEMBREC.
WORKING-STORAGE SECTION.
01 WS-IN-STATUS PIC XX.
01 WS-SLOT-STATUS PIC XX.
01 WS-SLOT PIC 9(4).
01 WS-EOF PIC X VALUE "N".
88 END-OF-MEMBERS VALUE "Y".
01 WS-LOADED PIC 9(3) VALUE ZERO.
PROCEDURE DIVISION.
0000-MAIN-LINE.
OPEN INPUT MEMBER-IN
OPEN OUTPUT MEMBER-SLOTS
PERFORM 8000-READ-MEMBER
PERFORM 2000-LOAD-MEMBER UNTIL END-OF-MEMBERS
* A member who joined later and was given card 105.
INITIALIZE MEMBER-RECORD
MOVE 105 TO MEMBER-ID
MOVE "KATHERINE JOHNSON" TO MEMBER-NAME
MOVE 20261020 TO MEMBER-JOINED
SET MEMBER-ACTIVE TO TRUE
PERFORM 2100-WRITE-SLOT
* Writing the same slot again is an error.
WRITE MEMBER-RECORD
INVALID KEY
DISPLAY "Second write of slot " WS-SLOT
" failed, status " WS-SLOT-STATUS
END-WRITE
CLOSE MEMBER-IN MEMBER-SLOTS
DISPLAY WS-LOADED " members stored"
STOP RUN.
2000-LOAD-MEMBER.
MOVE IN-RECORD TO MEMBER-RECORD
PERFORM 2100-WRITE-SLOT
PERFORM 8000-READ-MEMBER.
2100-WRITE-SLOT.
COMPUTE WS-SLOT = MEMBER-ID - 99
WRITE MEMBER-RECORD
INVALID KEY
DISPLAY "Cannot store member " MEMBER-ID
", status " WS-SLOT-STATUS
NOT INVALID KEY
ADD 1 TO WS-LOADED
DISPLAY "Member " MEMBER-ID " -> slot " WS-SLOT
", status " WS-SLOT-STATUS
END-WRITE.
8000-READ-MEMBER.
READ MEMBER-IN
AT END SET END-OF-MEMBERS TO TRUE
END-READ.
Run on GnuCOBOL 3.2.0, compiled with cobc -x -Wall, no warnings:
Member 000100 -> slot 0001, status 00
Member 000101 -> slot 0002, status 00
Member 000102 -> slot 0003, status 00
Member 000105 -> slot 0006, status 00
Second write of slot 0006 failed, status 22
004 members stored
Source: Enterprise COBOL for z/OS 6.5 Language Reference: WRITE for relative files; GnuCOBOL Programmer’s Guide: WRITE
Points to notice:
-
The program fills the record, computes the slot and executes
WRITE; the record contains no slot number, only the card number. -
A
WRITEto a slot that already holds a record fails with status22, and the data in the file is unchanged. WithINVALID KEYthe phrase runs; without it, Enterprise COBOL and GnuCOBOL fall back on a declarative or on theFILE STATUSitem, as explained on Error Handling and Declaratives. -
OPEN OUTPUTcreates an empty file (or empties an existing one). WithACCESS MODE RANDOMit is the right way to start a new file. -
On GnuCOBOL 3.2.0 with the default file handler, the resulting
MEMBSLOTis 408 bytes: six slots of 68 bytes, each an 8-byte header that holds the record length followed by the 60 data bytes. Slots 4 and 5, which were never written, are zero bytes, which is how the run time knows they are empty. This layout is an implementation detail of the run time and should not be relied on; a program must go through the COBOL statements.
Reading in slot order
A sequential READ returns the occupied slots in ascending order and skips the empty ones. The relative key item, if given, is updated after each READ.
IDENTIFICATION DIVISION.
PROGRAM-ID. SLOTLIST.
* List the member slot file in slot order (sequential access).
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT MEMBER-SLOTS ASSIGN TO "MEMBSLOT"
ORGANIZATION IS RELATIVE
ACCESS MODE IS SEQUENTIAL
RELATIVE KEY IS WS-SLOT
FILE STATUS IS WS-STATUS.
DATA DIVISION.
FILE SECTION.
FD MEMBER-SLOTS.
COPY MEMBREC.
WORKING-STORAGE SECTION.
01 WS-STATUS PIC XX.
01 WS-SLOT PIC 9(4).
01 WS-EOF PIC X VALUE "N".
88 END-OF-SLOTS VALUE "Y".
PROCEDURE DIVISION.
0000-MAIN-LINE.
OPEN INPUT MEMBER-SLOTS
PERFORM 8000-READ-SLOT
PERFORM UNTIL END-OF-SLOTS
DISPLAY "slot " WS-SLOT " " MEMBER-ID " "
MEMBER-NAME
PERFORM 8000-READ-SLOT
END-PERFORM
DISPLAY "last status " WS-STATUS ", slot key still "
WS-SLOT
CLOSE MEMBER-SLOTS
STOP RUN.
8000-READ-SLOT.
READ MEMBER-SLOTS
AT END SET END-OF-SLOTS TO TRUE
END-READ.
slot 0001 000100 ADA LOVELACE
slot 0002 000101 GRACE HOPPER
slot 0003 000102 ALAN TURING
slot 0006 000105 KATHERINE JOHNSON
last status 10, slot key still 0006
Source: Enterprise COBOL for z/OS 6.5 Language Reference: READ, sequential access mode; GnuCOBOL Programmer’s Guide: Sequential READ
Slots 4 and 5 do not appear, and slot 6 follows slot 3 directly. When AT END is reached (status 10) the key item is not meaningful; the program above shows only that GnuCOBOL leaves it at the last slot read, which is not a documented guarantee.
Deleting in sequential access
In sequential access DELETE removes the record that the last READ returned. The INVALID KEY phrase must not be coded in this mode. The program below purges suspended members; it then shows what happens to a DELETE and a READ after the end of the file.
IDENTIFICATION DIVISION.
PROGRAM-ID. SLOTPURGE.
* Sequential access: delete the suspended members.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT MEMBER-SLOTS ASSIGN TO "MEMBSLOT"
ORGANIZATION IS RELATIVE
ACCESS MODE IS SEQUENTIAL
RELATIVE KEY IS WS-SLOT
FILE STATUS IS WS-STATUS.
DATA DIVISION.
FILE SECTION.
FD MEMBER-SLOTS.
COPY MEMBREC.
WORKING-STORAGE SECTION.
01 WS-STATUS PIC XX.
01 WS-SLOT PIC 9(4).
01 WS-EOF PIC X VALUE "N".
88 END-OF-SLOTS VALUE "Y".
PROCEDURE DIVISION.
0000-MAIN-LINE.
OPEN I-O MEMBER-SLOTS
PERFORM 8000-READ-SLOT
PERFORM UNTIL END-OF-SLOTS
IF MEMBER-SUSPENDED
DELETE MEMBER-SLOTS RECORD
DISPLAY "deleted slot " WS-SLOT " "
MEMBER-NAME " status " WS-STATUS
END-IF
PERFORM 8000-READ-SLOT
END-PERFORM
* The file position is at the end, so there is no current
* record to delete and nothing more to read.
DELETE MEMBER-SLOTS RECORD
DISPLAY "DELETE after end: status " WS-STATUS
READ MEMBER-SLOTS
DISPLAY "READ after end: status " WS-STATUS
CLOSE MEMBER-SLOTS
STOP RUN.
8000-READ-SLOT.
READ MEMBER-SLOTS
AT END SET END-OF-SLOTS TO TRUE
END-READ.
deleted slot 0003 ALAN TURING status 00
DELETE after end: status 43
READ after end: status 46
Source: Enterprise COBOL for z/OS 6.5 Language Reference: DELETE statement; GnuCOBOL Programmer’s Guide: DELETE
Status 43 means that a DELETE or REWRITE was issued with no successful READ just before it; status 46 means a sequential READ was attempted with no valid next record. Both are logic errors in the program, not data conditions. The file now has an empty slot 3, and the listing program shows slots 1, 2 and 6 only.
Random and dynamic access: READ, START, REWRITE, DELETE, WRITE
The next program opens the file in dynamic access mode and exercises every statement in turn.
IDENTIFICATION DIVISION.
PROGRAM-ID. SLOTDYN.
* Dynamic access: random READ, START, READ NEXT, REWRITE, DELETE.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT MEMBER-SLOTS ASSIGN TO "MEMBSLOT"
ORGANIZATION IS RELATIVE
ACCESS MODE IS DYNAMIC
RELATIVE KEY IS WS-SLOT
FILE STATUS IS WS-STATUS.
DATA DIVISION.
FILE SECTION.
FD MEMBER-SLOTS.
COPY MEMBREC.
WORKING-STORAGE SECTION.
01 WS-STATUS PIC XX.
01 WS-SLOT PIC 9(4).
01 WS-EOF PIC X VALUE "N".
88 END-OF-SLOTS VALUE "Y".
PROCEDURE DIVISION.
0000-MAIN-LINE.
OPEN I-O MEMBER-SLOTS
* 1. Random READ of an occupied slot, then REWRITE it.
MOVE 2 TO WS-SLOT
READ MEMBER-SLOTS
INVALID KEY DISPLAY "slot 2 empty?"
NOT INVALID KEY
DISPLAY "read slot " WS-SLOT ": " MEMBER-NAME
MOVE ZERO TO MEMBER-FINES
REWRITE MEMBER-RECORD
INVALID KEY DISPLAY "rewrite failed " WS-STATUS
END-REWRITE
END-READ
* 2. Random READ of an empty slot and of a slot past the end.
PERFORM VARYING WS-SLOT FROM 4 BY 3 UNTIL WS-SLOT > 7
READ MEMBER-SLOTS
INVALID KEY
DISPLAY "slot " WS-SLOT " has no record, status "
WS-STATUS
NOT INVALID KEY
DISPLAY "slot " WS-SLOT " holds " MEMBER-NAME
END-READ
END-PERFORM
* 3. START at the first occupied slot not less than 4, then
* read sequentially from there.
MOVE 4 TO WS-SLOT
START MEMBER-SLOTS KEY IS NOT LESS THAN WS-SLOT
INVALID KEY DISPLAY "nothing from slot 4 on"
END-START
READ MEMBER-SLOTS NEXT RECORD
AT END SET END-OF-SLOTS TO TRUE
END-READ
IF NOT END-OF-SLOTS
DISPLAY "next after START: slot " WS-SLOT " "
MEMBER-NAME
END-IF
* 4. START beyond the last slot.
MOVE 7 TO WS-SLOT
START MEMBER-SLOTS KEY IS GREATER THAN WS-SLOT
INVALID KEY DISPLAY "START past the end, status "
WS-STATUS
END-START
* 5. DELETE by key, then READ the emptied slot.
MOVE 6 TO WS-SLOT
DELETE MEMBER-SLOTS RECORD
INVALID KEY DISPLAY "delete failed " WS-STATUS
NOT INVALID KEY DISPLAY "slot 6 deleted"
END-DELETE
READ MEMBER-SLOTS
INVALID KEY DISPLAY "slot 6 now empty, status "
WS-STATUS
END-READ
* 6. A slot freed earlier (by the purge) can be written again.
MOVE 3 TO WS-SLOT
MOVE 103 TO MEMBER-ID
MOVE "EDSGER DIJKSTRA" TO MEMBER-NAME
MOVE 20261020 TO MEMBER-JOINED
MOVE ZERO TO MEMBER-FINES
SET MEMBER-ACTIVE TO TRUE
WRITE MEMBER-RECORD
INVALID KEY DISPLAY "write failed " WS-STATUS
NOT INVALID KEY DISPLAY "slot " WS-SLOT " reused"
END-WRITE
CLOSE MEMBER-SLOTS
STOP RUN.
read slot 0002: GRACE HOPPER
slot 0004 has no record, status 23
slot 0007 has no record, status 23
next after START: slot 0006 KATHERINE JOHNSON
START past the end, status 23
slot 6 deleted
slot 6 now empty, status 23
slot 0003 reused
Source: Enterprise COBOL for z/OS 6.5 Language Reference: START statement; READ, random access mode; REWRITE statement; GnuCOBOL Programmer’s Guide: START
What each step demonstrates:
-
Random
READandREWRITE. The slot number goes into the key item,READfills the record, the program changes it (clears Grace Hopper’s fine) andREWRITEreplaces it in the same slot. In random and dynamic access aREWRITEdoes not need a precedingREAD: it replaces whatever slot the key item names. The replacement may have a different length from the original for a relative file. -
Empty slot, slot past the end. Both produce the invalid-key condition with status
23. From the program’s point of view an empty slot and a slot beyond the end of the file are the same answer. -
STARTthenREAD NEXT.STARTdoes not read anything; it only positions the file. The comparison is made on the relative key item with numeric rules.KEY IS NOT LESS THAN 4selects the first occupied slot whose number is 4 or more, which is slot 6 because 4 and 5 are empty. The followingREAD NEXT RECORDreturns that record and sets the key item to 6. -
STARTthat finds nothing. No occupied slot is greater than 7, so the invalid-key condition (status23) is raised and the file position is undefined. A program must notREAD NEXTafter a failedSTART; it mustSTARTagain or read by key. -
DELETE. The slot named by the key item is emptied and the record area is left untouched. AREADof the same slot now gives status23. -
WRITEinto an emptied slot. The slot freed by the purge earlier is reused, with a different member, and no other slot changes. This is the point of relative files: a slot number is stable for the life of the file.
The relations that START accepts on a relative file are equal to, greater than, not less than (equivalent to greater than or equal to) and the longer spellings of those. The key phrase is optional: with no KEY phrase, equal is assumed. On a relative file the named key data item must be the file’s RELATIVE KEY. The GnuCOBOL manual documents further forms (FIRST, LAST, LESS THAN, LESS THAN OR EQUAL TO, NOT GREATER THAN), which are not in the Enterprise COBOL 6.5 START format; they were not run for this page, so code only the IBM relations if the program must also compile on z/OS.
The listing after the dynamic run:
slot 0001 000100 ADA LOVELACE
slot 0002 000101 GRACE HOPPER
slot 0003 000103 EDSGER DIJKSTRA
last status 10, slot key still 0003
Turning a business key into a slot number
A relative file only works well when the program can compute the slot number from the key it has.
Direct mapping. The key is already a small dense integer: slot = key - offset. Library card numbers issued from 100 upwards, or branch numbers 1 to 40, work this way, as in the load program above. Gaps in the number range cost disk space, not time.
Division/remainder (hashing). When the key is large or sparse, the Enterprise COBOL manual describes a randomizing routine, most often division/remainder: divide the key by the number of slots and add one to the remainder, which is always a valid slot number. The price is that two keys can land in the same slot (a collision), and the program must then choose another slot or keep an overflow area. The following program uses a five-slot file, where cards 100 and 105 both leave remainder 0.
IDENTIFICATION DIVISION.
PROGRAM-ID. SLOTHASH.
* Division/remainder randomizing: slot = MOD(id, slots) + 1.
* Ids 100 and 105 collide when the file has five slots.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT MEMBER-HASH ASSIGN TO "MEMBHASH"
ORGANIZATION IS RELATIVE
ACCESS MODE IS RANDOM
RELATIVE KEY IS WS-SLOT
FILE STATUS IS WS-STATUS.
DATA DIVISION.
FILE SECTION.
FD MEMBER-HASH.
COPY MEMBREC.
WORKING-STORAGE SECTION.
01 WS-STATUS PIC XX.
01 WS-SLOT PIC 9(4).
01 WS-SLOTS PIC 9(4) VALUE 5.
01 WS-IDS-VALUES.
05 FILLER PIC 9(6) VALUE 100.
05 FILLER PIC 9(6) VALUE 101.
05 FILLER PIC 9(6) VALUE 102.
05 FILLER PIC 9(6) VALUE 105.
01 WS-IDS REDEFINES WS-IDS-VALUES.
05 WS-ID PIC 9(6) OCCURS 4 TIMES.
01 WS-N PIC 9.
PROCEDURE DIVISION.
0000-MAIN-LINE.
OPEN OUTPUT MEMBER-HASH
PERFORM VARYING WS-N FROM 1 BY 1 UNTIL WS-N > 4
INITIALIZE MEMBER-RECORD
MOVE WS-ID (WS-N) TO MEMBER-ID
COMPUTE WS-SLOT =
FUNCTION MOD (MEMBER-ID, WS-SLOTS) + 1
WRITE MEMBER-RECORD
INVALID KEY
DISPLAY "Member " MEMBER-ID " collides in slot "
WS-SLOT ", status " WS-STATUS
NOT INVALID KEY
DISPLAY "Member " MEMBER-ID " -> slot " WS-SLOT
END-WRITE
END-PERFORM
CLOSE MEMBER-HASH
STOP RUN.
Member 000100 -> slot 0001
Member 000101 -> slot 0002
Member 000102 -> slot 0003
Member 000105 collides in slot 0001, status 22
Source: Enterprise COBOL for z/OS 6.5 Programming Guide: Specifying relative organization for VSAM files; Language Reference: FUNCTION MOD
The collision is reported by the file system itself (status 22 on WRITE), so the program needs no table to detect it. Resolving it is the program’s job, and this is why a hashed relative file is more work than an indexed file, which handles all of this internally. After the run the file holds three records and is 204 bytes on GnuCOBOL (three 68-byte slots), because the highest slot ever written was 3.
File status codes for relative files
| Status | Meaning on a relative file |
|---|---|
|
Successful. |
|
Sequential |
|
|
|
|
|
Boundary violation: the slot number is outside what the file can hold (on GnuCOBOL 3.2.0, |
|
|
|
|
|
Sequential |
|
|
The codes 10, 22, 23, 24, 43 and 46 were produced by the programs on this page; 35 and 48 by small separate tests. The exact assignment of every code, and the VSAM feedback codes that Enterprise COBOL adds, are in File Status Codes.
On z/OS: the file is a VSAM RRDS
A COBOL relative file on z/OS is a VSAM relative-record data set. Nothing in the COBOL source says so: the SELECT entry names a ddname, and the DD statement points at a data set that was defined as an RRDS.
//DEFRRDS EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DEFINE CLUSTER (NAME(USERID.CIRC.MEMBSLOT) -
NUMBERED -
RECORDSIZE(60 60) -
RECORDS(100 20))
/*
//*
//STEP1 EXEC PGM=SLOTLOAD
//MEMBMAST DD DISP=SHR,DSN=USERID.CIRC.MEMBMAST
//MEMBSLOT DD DISP=SHR,DSN=USERID.CIRC.MEMBSLOT
This job is z/OS; not run here. The SELECT clauses in SLOTLOAD would read ASSIGN TO MEMBSLOT instead of the quoted file name used on GnuCOBOL, and the FILE STATUS clause may name a second item to receive the VSAM return and feedback codes. NUMBERED is the IDCAMS keyword that makes the cluster an RRDS; the space and volume parameters depend on the installation, so check them against the DFSMS access method services manual before running anything like it.
How the COBOL concepts map to the RRDS:
-
Slot = relative record number. In a fixed-length RRDS each record occupies one fixed-size slot, the first slot has relative record number 1, and slots can be skipped when the data set is loaded and left empty.
-
Fixed-length RRDS. Define the cluster with
RECORDSIZE(n n)where n is at least the largest COBOL record. All records are padded to n. If theFDusesRECORD IS VARYING ... DEPENDING ON, aWRITEorREWRITEtransfers the length from theDEPENDING ONitem, VSAM pads it to the slot size, and aREADalways returns the full slot size in that item. -
Variable-length RRDS. There are no slots; free space defined for the cluster makes insertions cheaper, and relative record numbers can still be skipped. Define it with
RECORDSIZE(avg max), where the average is smaller than the maximum, describe the variable-length records in theFD, and use VSAM variable-length RRDS support. -
No alternate indexes. Alternate indexes are not supported for an RRDS. A second way into the data needs a second file, or a KSDS (see Indexed Files and VSAM).
-
Opening. A VSAM file is opened
INPUT,OUTPUT,I-OorEXTEND; on z/OSOPEN OUTPUTandOPEN EXTENDhave the same effect for a KSDS or RRDS. AREWRITEon a file not openedI-Osets status49. -
Sequential load. When an RRDS is loaded in sequence, the records go in ascending relative record number, and the key item receives the number of each record written.
-
DELETEandREWRITEby key. To replace a record randomly or dynamically, move the slot number to the relative key item and issueREWRITE, without a priorREAD.DELETEneeds the file openI-O.
Enterprise COBOL and GnuCOBOL compared
| Point | Enterprise COBOL 6.5 | GnuCOBOL 3.2.0 |
|---|---|---|
File on disk |
VSAM RRDS (fixed or variable length) |
Run-time file with an 8-byte header per slot (default file handler, 3.2.0) |
|
Invalid key condition |
Status |
|
Invalid key condition |
Status |
|
Not allowed by the Language Reference |
Compiles without any diagnostic (tested) |
|
Not allowed for sequential access |
Compiles silently and is never used (tested) |
|
Not allowed |
|
|
Equal, greater than, not less than (and their spellings) |
Also |
Source: Enterprise COBOL for z/OS 6.5 Language Reference: DELETE statement; GnuCOBOL Programmer’s Guide: START
The two rows on DELETE and REWRITE matter in practice: a program that relies on the invalid-key condition to detect "slot not in use" works on z/OS but not on GnuCOBOL 3.2.0, as the next program shows. Slot 4 lies inside the file and is empty; slot 12 is beyond the end.
IDENTIFICATION DIVISION.
PROGRAM-ID. SLOTQUIRK.
* Operations on slots that hold no record (GnuCOBOL 3.2.0).
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT MEMBER-SLOTS ASSIGN TO "MEMBSLOT"
ORGANIZATION IS RELATIVE
ACCESS MODE IS DYNAMIC
RELATIVE KEY IS WS-SLOT
FILE STATUS IS WS-STATUS.
DATA DIVISION.
FILE SECTION.
FD MEMBER-SLOTS.
COPY MEMBREC.
WORKING-STORAGE SECTION.
01 WS-STATUS PIC XX.
01 WS-SLOT PIC 9(4).
PROCEDURE DIVISION.
0000-MAIN-LINE.
OPEN I-O MEMBER-SLOTS
* Slot 4 lies inside the file but holds no record.
MOVE 4 TO WS-SLOT
READ MEMBER-SLOTS
INVALID KEY DISPLAY "READ slot 4: status " WS-STATUS
END-READ
DELETE MEMBER-SLOTS RECORD
INVALID KEY DISPLAY "DELETE slot 4: status " WS-STATUS
NOT INVALID KEY
DISPLAY "DELETE slot 4: accepted, status "
WS-STATUS
END-DELETE
MOVE "NOBODY" TO MEMBER-NAME
REWRITE MEMBER-RECORD
INVALID KEY DISPLAY "REWRITE slot 4: status " WS-STATUS
NOT INVALID KEY
DISPLAY "REWRITE slot 4: accepted, status "
WS-STATUS
END-REWRITE
* Slot 12 lies beyond the end of the file.
MOVE 12 TO WS-SLOT
REWRITE MEMBER-RECORD
INVALID KEY DISPLAY "REWRITE slot 12: status " WS-STATUS
END-REWRITE
MOVE 0 TO WS-SLOT
WRITE MEMBER-RECORD
INVALID KEY DISPLAY "WRITE slot 0: status " WS-STATUS
END-WRITE
CLOSE MEMBER-SLOTS
STOP RUN.
READ slot 4: status 23
DELETE slot 4: accepted, status 00
REWRITE slot 4: accepted, status 00
REWRITE slot 12: status 23
WRITE slot 0: status 24
Source: Enterprise COBOL for z/OS 6.5 Language Reference: REWRITE, relative files; GnuCOBOL Programmer’s Guide: REWRITE
To stay portable, READ the slot first and test for status 23 before a REWRITE or DELETE in a program that must run on both systems.
Compiler diagnostics for mistakes in the SELECT entry
These fragments were compiled on their own; the messages are what cobc (GnuCOBOL) 3.2.0 printed.
A missing RELATIVE KEY with ACCESS MODE RANDOM:
bad1.cob:6: error: RELATIVE KEY clause is required for file 'MEMBER-SLOTS'
START on a file opened with ACCESS MODE RANDOM:
bad3.cob:21: error: START not allowed with ACCESS MODE RANDOM
A RELATIVE KEY that is a field of the file’s own record (RELATIVE KEY IS MEMBER-ID):
bad6.cob:9: error: RELATIVE KEY MEMBER-ID cannot be in file record belonging to MEMBER-SLOTS
A signed key (PIC S9(4)) and a key with decimals (PIC 9(2)V9):
bad7.cob:9: error: file MEMBER-SLOTS: RELATIVE KEY WS-SLOT must be unsigned
bad8.cob:9: error: file MEMBER-SLOTS: RELATIVE KEY WS-SLOT must be integer
A key item too small for the slot number (PIC 99 and slot 100) is not a file error at all, the value is truncated before the file statement runs:
bad9.cob:20: warning: value size exceeds data size [-Wtruncate]
Mistakes that GnuCOBOL 3.2.0 does not diagnose (tested): READ ... NEXT RECORD on an ACCESS MODE RANDOM file, READ ... KEY IS on a relative file, and INVALID KEY on a sequential READ. The Enterprise COBOL Language Reference does not allow them (the KEY phrase must not be specified for relative files, and the INVALID KEY phrase must not be specified for sequential access), so code review has to catch them.
Opening a file that was never created gives status 35 on both systems:
open missing 35
When a relative file fits, and why it is rare now
A relative file is a good fit when all of these hold:
-
the lookup key is, or can cheaply become, a dense integer (card number, branch number, terminal number, fixed code table);
-
nearly every access is by slot, in random order, and a sequential pass in slot order is the only other access;
-
records are added and removed over time, but a record keeps its slot;
-
the file is mostly full (empty slots still use space).
Examples are small code tables, work files numbered by a sequence, and checkpoint records numbered by job step.
They are rare in new code for these reasons:
-
a hashed key needs collision handling that an indexed file already provides;
-
a gap-heavy range wastes space;
-
an RRDS cannot have an alternate index, so a second search path needs another data set;
-
a table in
WORKING-STORAGEwithSEARCH ALL, a KSDS with a normal key, or a Db2 table covers nearly every case with less code (see Searching and Sorting Tables, Indexed Files and VSAM and Embedded SQL and Db2).
You will still meet them in old batch systems that use them as direct-access tables, and VSAM RRDS support continues in Enterprise COBOL 6.5. For modernization, a relative file normally becomes a keyed file or a table, with the slot-number computation turned into the key; see Legacy Features and Migration.
Common mistakes
-
Treating an empty slot as an error. Status
23on a keyedREADis the normal answer for "not there". Test for it withINVALID KEYor the status item. -
No
FILE STATUS. Without one, an unexpected status falls to a declarative or ends the run; with one, the program must check it after every statement. -
Reusing the key item between statements. A sequential
READand a successfulREAD NEXToverwrite the relative key item. Move the slot number back before the next keyed statement. -
Reading after a failed
START. The file position is undefined; do notREAD NEXT. -
Placing the slot number inside the record and naming it as the key. The relative key must be outside the record; keep a copy in the record if the record has to carry it.
-
A key item that is too small. The slot number is truncated silently by a
MOVEorCOMPUTEwithoutON SIZE ERROR. -
Relying on
INVALID KEYfor an empty slot onREWRITEorDELETE. It works on z/OS and not on GnuCOBOL 3.2.0, see above. -
Expecting a slot to be found by content. Searching a relative file for a name means reading all of it sequentially.
References
-
Enterprise COBOL for z/OS 6.5 Language Reference: RELATIVE KEY clause
-
Enterprise COBOL for z/OS 6.5 Language Reference: START statement
-
Enterprise COBOL for z/OS 6.5 Language Reference: DELETE statement
-
Enterprise COBOL for z/OS 6.5 Language Reference: WRITE for relative files
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Specifying relative organization for VSAM files
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Fixed-length and variable-length RRDS
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Defining VSAM files
-
Tom Taulli, Modern Mainframe Development, O’Reilly Media, 2022 (chapter 5, consulted for the topics only)