DocumentationEndless PL/I for POSIX SystemsUser's Manual  ·  ESS-PFUUM-001

Chapter 4

File Organizations and Input/Output

Record I/O is performed by the READ, WRITE, DELETE, and REWRITE statements; each statement processes an entire record. In stream I/O, more than one line or record can be processed by a single statement. In addition, some forms of record I/O allow you to access records in the file by record number or by a key field contained in the record.

This chapter includes the following topics:

  • File organizations—stream and relative

  • Access modes—sequential, random, and random/sequential, and record identification

  • Record formats—variable-length, fixed-length

  • Stream files

  • Relative files

For position information and descriptions of the READ, WRITE, DELETE, and REWRITE statements, see the Endless PL/I for POSIX Systems Reference Manual.

4.1. File Organizations

The UNIX file system supports only stream files; however, the PL/I run-time system simulates the behavior of relative files by imposing restrictions on the access of files having these organizations.

One of the consequences of this is that (unlike VAX PL/I) the Endless PL/I for POSIX Systems run-time system relies on the file organization and access method specified in the DECLARE, OPEN, and READ statements of your PL/I program to determine the file organization. Thus it is up to the programmer to ensure that the correct file organization is specified in the input/output statements of the PL/I program; otherwise, results are unpredictable. To avoid problems when specifying the file, always open a file with the same file attributes.

Restrictions

Endless PL/I for POSIX Systems does not support some file organizations or combinations of file organizations and access methods due to limitations of the file system. These are:

  • Stream access of relative file with fixed or variable length records

  • Sequential access of stream file with variable length records (supported by VAX PL/I)

  • Direct access of stream file with variable length records (supported by VAX PL/I)

The following file organizations are supported by PL/I:

  • Stream

  • Relative

Stream Files

In a stream file, characters are ordered one after the other, in the order in which they are written. New characters can be added only at the end of an existing file. Stream files are discussed in more detail in Section 4.4, “Stream Files”.

Relative Files

A relative file contains a specified number of fixed-length cells, numbered from 1 to n, where n is a user-specified maximum. Any record written to the file is written to a specific numbered cell; the cell number is also the relative record number of the record in the file.

Normally, the records in a relative file have an implied numeric data field, for example, an account number or employee number, which corresponds to the cell in which the record will be placed. When you read or write a record in a relatively organized file, you specify the record by its relative record number. Relative files are discussed in more detail in Section 4.5, “Relative Files”.

4.2. Access Modes for Record I/O

In standard PL/I, you can specify one of the following sets of attributes to define the way a program is going to access the records in a file:

  • SEQUENTIAL indicates sequential access to a sequential or keyed file.

  • DIRECT indicates random access to a keyed file.

  • KEYED SEQUENTIAL indicates sequential and random access to a keyed file.

In PL/I, there is an additional way to access a file. Specify the ENVIRONMENT option RECORD_ID_ACCESS to indicate that records can be read or written randomly with the file address of the record.

Although you cannot change the organization of the records in a file after you have created the file, you can select a different type of access mode for reading or processing the file. You can also access a file using more than one access mode; for example, you can read a record in an indexed file by specifying a key, and then read records sequentially beginning at that position in the file.

4.2.1. Sequential Access

You can use sequential access with any type of file organization except stream access to a variable length record file. When you access a file sequentially, each read or write operation reads or writes the next record in the file. As you process a file sequentially, PL/I keeps track of the current record (that is, the record just read or written) and the next record (the record that follows the record just read or written).

When you access a relative file sequentially, the records are read or written in order by relative record number. In a file in which not all cells contain records, sequential input operations involve only cells that contain data records.

4.2.2. Direct Access

When you access a file randomly by key, each I/O request must contain the KEY or KEYFROM option. For a relative file, the key is the relative record number of the record to be accessed.

By default, a READ statement accesses a record based on an exact match of the key specified. In PL/I, you have the option of requesting that the READ statement match any record with an equal or greater key value, or any record with a greater key value.

4.2.3. Direct and Sequential Access

When you access a file for random and sequential access, you can read records sequentially or randomly. For example, you can use a keyed READ statement to position the file at a specified record and then read or process records sequentially from that position.

4.2.4. Access by Record Identification

You can use a record identification to access records in any type of disk file (except stream files with variable length records) that is opened for any type of access. To access records by record identification:

  1. Specify the RECORD_ID_ACCESS option in the ENVIRONMENT attribute for the file.

  2. Supply a 2-element array variable to save the record identification of records you want to access by record identification.

  3. Specify the array variable in the RECORD_ID_TO option on a READ, REWRITE, or WRITE statement that accesses a record in the file, and save the value that is returned.

  4. Specify the array variable that contains a valid record identification in a RECORD_ID option on a READ, REWRITE, or DELETE statement.

For example:

DECLARE LIBFILE FILE ENVIRONMENT(RECORD_ID_ACCESS),
               SAVED_ID(2) FIXED BINARY(31);
OPEN FILE(LIBFILE) DIRECT;
READ FILE(LIBFILE) KEY(MODULE_NAME) INTO(INREC)
               OPTIONS(RECORD_ID_TO(SAVED_ID));

The READ statement in this example returns the value of the record identification associated with the record whose key is indicated by the variable MODULE_NAME. The value of SAVED_ID may subsequently be used in a REWRITE or DELETE statement to modify the record.

4.3. Record Formats

The following types of record formats are allowed for all file organizations:

  • Fixed length

  • Variable length

You must specify the format when you create a file and each time you open the file. This is necessary because PL/I cannot determine the format of the records in the file.

4.3.1. Fixed-Length Records

In a file containing fixed-length records, all records have the same length. Thus, when you create a file with fixed-length records, you must specify the length of each record in the file; this size cannot be changed after the file is created.

To create a file with fixed-length records in a PL/I program, use the FIXED_LENGTH_RECORDS option of the ENVIRONMENT attribute. The MAXIMUM_RECORD_SIZE option specifies the size of each record. For example:

DECLARE FIXED_FILE FILE RECORD KEYED OUTPUT
       ENVIRONMENT(FIXED_LENGTH_RECORDS,
       MAXIMUM_RECORD_SIZE(80));

When the file FIXED_FILE is opened, its record format is established as having fixed-length 80-character records.

When a file that has fixed-length records is processed by READ and WRITE statements, the file system checks the length of the variable specified in the INTO or FROM option to see if it is the same as the length of the records in the file. If not, the ERROR condition is signaled.

4.3.1.1. IBM Dialect Fixed Length Environment Options

The following environment options are available in the IBM dialect only:

  • F, FB, FS, or FBS—all equivalent to FIXED_LENGTH_RECORDS

  • RECSIZE—equivalent to MAXIMUM_RECORD_SIZE

4.3.2. Variable-Length Records

PL/I supports only record I/O on variable-length record files with relative file organizations. In a file containing variable-length records, each record can have a different size. PL/I places a count field at the beginning of each record to indicate its size; however, this count field is not considered a part of the data record, nor is the length of the count field included in the size of the record.

Variable length is the default record format for PL/I files. Use the MAXIMUM_RECORD_SIZE option to specify the maximum length that any record can have. For example:

DECLARE VAR_FILE FILE RECORD OUTPUT
       ENVIRONMENT(MAXIMUM_RECORD_SIZE(80));

This declaration indicates that the file VAR_FILE has variable-length records, each with a maximum length of 80 characters.

When a file that has variable-length records is processed by READ statements, the file system checks the length of the variable specified in the INTO option to see if it is large enough to hold the record being read. If not, the ERROR condition is signaled.

4.3.2.1. IBM Dialect Variable Length Environment Options

The following environment options are available in the IBM dialect only:

  • V, VB, VS, or VBS—all equivalent to the variable length record format

  • RECSIZE—equivalent to MAXIMUM_RECORD_SIZE

4.4. Stream Files

Record I/O on stream files is limited to those with fixed-length records. This section shows examples of some typical sequential file I/O operations on stream disk files.

4.4.1. Stream Files Organization

Stream input and output are performed by the statements GET and PUT, respectively. Both statements can perform either list-directed or edit-directed operations.

A file has the STREAM attribute if it meets any of the following conditions:

  • The file was declared explicitly with a DECLARE statement and the STREAM attribute.

  • The file was declared explicitly with a DECLARE statement and with neither the STREAM nor the RECORD attribute (as the default is STREAM).

  • The file was specified in and opened implicitly by a GET or PUT statement.

Files declared with the STREAM attribute have the following characteristics:

  • They are a stream of bytes.

  • They have variable length records. The records are delimited by the newline character (\n).

Stream files contain only ASCII data. The ASCII format used to represent program data in a stream output file differs depending on the attributes given to the file. For example, the representation of character strings differs depending on the presence or absence of the PRINT attribute in the file declaration.

Unlike VAX VMS, an existing stream file cannot be reopened and accessed by the READ and WRITE statements. Stream files are true byte streams on UNIX. If READ and WRITE open a file implicitly, the attributes RECORD INPUT SEQUENTIAL and RECORD OUTPUT SEQUENTIAL are implied, respectively. These attributes are not compatible with an existing disk file created with the STREAM and OUTPUT attributes, and these operations will produce unpredictable results.

4.4.2. Creating a Stream File

Whenever a PL/I program opens a file with the SEQUENTIAL OUTPUT attributes, PL/I creates a new stream file. By default, records are 510-byte, variable-length records. If record I/O is done on this file, you must specify the FIXED_LENGTH_RECORDS attribute (or the IBM dialect equivalent attribute).[1]

4.4.2.1. Appending Records to an Existing File

In PL/I, you can open a file with the APPEND option of ENVIRONMENT to add new records to the end of an existing sequential file. This overrides the default action of PL/I, which is to overwrite an existing file when the existing file is opened for output. For example:

OPEN FILE (BIRD_FILE) OUTPUT SEQUENTIAL
       TITLE ('BIRDS.DAT')
       ENVIRONMENT (APPEND, FIXED_LENGTH_RECORDS);
WRITE FILE (BIRD_FILE) FROM (NEWDATA);

This OPEN statement opens the file BIRD_FILE and positions it at its current end-of-file. The WRITE statement adds a new record at the end of the file.

4.4.2.2. Superseding an Existing File

The ENVIRONMENT option SUPERSEDE lets you create a new version of a file each time you open it, simultaneously deleting an existing version. This is the default behavior for output files in PL/I. For example, the following OPEN statement opens the file CONTROL.DAT, deleting any pre-existing versions of the file.

OPEN FILE(CONTROL) OUTPUT RECORD TITLE('CONTROL.DAT')
       ENVIRONMENT(SUPERSEDE);

4.5. Relative Files

This section describes the organization of a relative file, suggests considerations for creating and using relative files, and shows examples of some typical relative file I/O operations.

4.5.1. Relative File Organization

The relative file organization is suitable for files with data that can be arranged serially and be uniquely identified by an integer value, for example, a part number or an employee identification number. Within the file, records are written into cells that are numbered; there is a one-to-one correspondence between the cell number and the integer value associated with the data in the record. This number, called the relative record number, is the key by which records are accessed.

Figure 4.1, “Relative File” illustrates a relative file in which not all cells contain records. The first record written to the file was relative record number 1 (which may have been data for a part numbered 1 or an employee whose number is 1, for example). The second record written was relative record number 2. The third record written was relative record number 4; thus cell number 3 does not contain a record.

Figure 4.1. Relative File
Relative File 1 2 3 4 5 n-1 n Record 1 Record 2 Empty Record 4 Empty … Record 999 Empty First Record Written Second Record Written Third Record Written

Although the cells in a relative file have the same length, the records need not be fixed-length records. However, when a record is smaller than the length of a cell, the unused space is wasted.

4.5.2. Creating a Relative File

In PL/I, relative file organization is the default organization for files that are opened with the KEYED attribute. Thus, when a WRITE statement is directed to a file with the KEYED and OUTPUT attributes, PL/I creates a relative file.

When you initially create a relative file in a PL/I program, consider using the ENVIRONMENT option MAXIMUM_RECORD_SIZE (or the IBM dialect RECSIZE option) to maximize the efficiency of I/O operations on the file.

Maximum Record Size

When you specify the length of the records in a file, the value you specify is used in the MAXIMUM_RECORD_SIZE option (or the IBM dialect RECSIZE option) to calculate a cell size. It uses the following formulas to calculate the size:

  • For all records—cell size = 2 + MAXIUM_RECORD_SIZE (two bytes are required for overhead; they contain the deletion indicator)

  • For variable-length records—cell size = 3 + maximum record size (three bytes are required for overhead: two bytes for the individual record size, and one byte for a deletion indicator)

When you select a record size for a relative file, try to specify a size that is no greater than the largest record that will be written. Otherwise, any unused space in each cell will be wasted. If you do not specify a maximum record size for either fixed- or variable-length records, PL/I uses the default length of 480 bytes.

4.5.3. Using Relative Files

You can create a relative file from any existing file that is suitable for relative file organization. Example 4.1, “Creating a Relative File” illustrates copying a sequential file with fixed-length records into a relative file.

Example 4.1. Creating a Relative File
COPY_TO_RELATIVE: PROCEDURE OPTIONS(MAIN);

       %INCLUDE PARTLIST; ❶ /* Declaration of PARTLIST */
       DECLARE OLDFILE FILE INPUT RECORD SEQUENTIAL ENVIRONMENT(
               FIXED_LENGTH_RECORDS,
               MAXIMUM_RECORD_SIZE(36));
       DECLARE RECORD_NUMBER FIXED BINARY(15);
       DECLARE
               PARTS FILE OUTPUT KEYED RECORD ENVIRONMENT(
                       FIXED_LENGTH_RECORDS,  ❷
                       MAXIMUM_RECORD_SIZE(36));

       ON ENDFILE(OLDFILE) BEGIN;  ❸
               CLOSE FILE(OLDFILE), FILE(PARTS);
               STOP;
               END;

       OPEN FILE(OLDFILE), FILE(PARTS);

       DO WHILE('1'B);
               READ FILE(OLDFILE) INTO(PARTLIST);  ❹
               RECORD_NUMBER = PARTLIST.NUMBER;
               WRITE FILE(PARTS) FROM(PARTLIST) KEYFROM(RECORD_NUMBER); ❺
               END;
       END COPY_TO_RELATIVE;

The following notes are keyed to Example 4.1, “Creating a Relative File”:

❶

The structure PARTLIST describes the layout of the records in the file. The records will be ordered in the relative file according to the field PARTLIST.NUMBER.

❷

The relative file PARTS is declared with fixed-length, 36-byte records.

❸

The file OLDFILE is the sequential file containing the records to be copied to a relative file. When the end-of-file is reached, the file is closed and the STOP statement terminates the program.

❹

As each record is read into the structure PARTLIST, the value of NUMBER is copied to the fixed binary integer RECORD_NUMBER. The part number is maintained in each record in its character-string form.

❺

Each WRITE statement copies the record to the output file, specifying the value of the part number as a relative record number.

Records in this file can subsequently be accessed either sequentially or by part number. To access a record by part number, you specify the number as a key. For example:

GET LIST(INPUT_NUMBER) OPTIONS(PROMPT('Part? '));
READ FILE(PARTS) INTO(PARTLIST) KEY(INPUT_NUMBER);

Here, the value entered in response to the GET statement is used as a key value to access a record in the file.

4.5.3.1. Populating a Relative File

In Example 4.1, “Creating a Relative File”, the file PARTS is opened with the KEYED and OUTPUT attributes. When this program is executed, the amount of space allocated for the file PARTS depends on the relative record numbers of the records that are written to the file. For example, if the largest record number specified for a record is 200, only enough space for 200 records is allocated.

4.5.3.2. Updating a Relative File

To add or modify records in a relative file, open the file with the DIRECT and UPDATE attributes. For example, a procedure that updates the file PARTS when new stock is ordered might contain the following:

ORDER_PARTS: PROCEDURE(ORDERED_AMOUNT,PART_NUMBER);
%INCLUDE PARTLIST;                                             /* Declaration of PART.LIST */
DECLARE (ORDERED_AMOUNT,PART_NUMBER) FIXED BIN(15);
DECLARE PARTS FILE RECORD DIRECT UPDATE;

       OPEN FILE(PARTS);
       READ FILE(PARTS)                                INTO(PARTLIST)
                                       KEY(PART_NUMBER);
       PARTLIST.ON_ORDER =                                     PARTLIST.ON_ORDER +
                                               ORDERED_AMOUNT;
       REWRITE FILE(PARTS) FROM(PARTLIST);
       CLOSE FILE(PARTS);
END;

In this example, the procedure ORDER_PARTS receives as its parameters the order quantity and the part number. It reads the record associated with the part number from the file, adds the order quantity to the existing quantity, and rewrites the record.

4.5.3.3. Reading a Relative File Sequentially

You can access a relative file sequentially as well as by key. When you access the file sequentially, each READ statement returns the record in the next cell that contains a record, skipping empty cells. The following example illustrates reading a relative file sequentially:

PRINT_PART: PROCEDURE OPTIONS(MAIN);
%INCLUDE PARTLIST;                                     /* Declaration of PARTLIST */

DECLARE PARTS FILE,
       CHECK_NUM FIXED;

DECLARE EOF BIT(1) ALIGNED INIT('0'B);

       OPEN FILE(PARTS) INPUT SEQUENTIAL RECORD KEYED;
       ON ENDFILE(PARTS) EOF = '1'B;
       READ FILE(PARTS) INTO(PARTLIST) KEYTO(CHECK_NUM);
       DO WHILE(^EOF);
               PUT SKIP EDIT(PARTLIST.NAME, /* Output data */
                               UNIT_PRICE,
                               IN_STOCK,
                               ON_ORDER)
                       (A,X,A,X,A,X,A);
               PUT SKIP EDIT('Relative record number',CHECK_NUM,
                       'Part number:',PARTLIST.NUMBER)
                       (X(10),A,X,F(5),A,X,A);         /* Output
                               verification */
               READ FILE(PARTS) INTO(PARTLIST) KEYTO(CHECK_NUM);
END;

This procedure outputs the contents of the file PARTS, listing each field in the data records described by PARTLIST. The READ statement specifies the KEYTO option; the procedure outputs the value returned to the variable CHECK_NUM.

4.5.4. Error Handling

PL/I signals the KEY condition when errors occur during the processing of record numbers for relative files. For example, it signals the KEY condition when the number of a record that already exists is specified in a KEYFROM option in a WRITE statement.

The following sample ON-unit shows how to detect whether a record already exists in a relative file.

ON KEY(PARTS) BEGIN;
       %INCLUDE $RMSDEF;
       /* Check for duplicate records */
       IF ONCODE() = RMS$_REX                                                          /* If duplicate */
       THEN DO;
               PUT SKIP EDIT('Part number',
                       PARTLIST.NUMBER,'exists. Reenter')
                       (A,X,A,X,A);
               GET LIST(PARTLIST.NUMBER);                                                      /* Get new value */
               GOTO GET_DATA;                                                  /* Go get other data */
               END;
       END;
GET_DATA:

In this example, the ON-unit gets symbolic names for a specific status value returned by ONCODE, from RMSDEF, a system include file provided for compatibility with VAX PL/I. The value RMS$_REX indicates that a record already exists.

In an ON-unit for the KEY condition for a relative file, ONCODE can also return the values associated with the following status codes:

  • RMS$_RNF indicates that there is no record in the file with the relative record number specified by a KEY option.

  • RMS$_KEY indicates that a key value is invalid, for example, if it is not an integer.

The symbolic names for these status codes are included from the system include file rmsdef.in, from /usr/lib/pl1/include.



[1] The default is preserved for compatibility with VAX PL/I. Endless PL/I for POSIX Systems does not support SEQUENTIAL (record) access to variable length stream files.