Chapter 6
Record 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 for record files-sequential, relative, and indexed sequential
Access modes-sequential, random, and random/sequential, as well as access through block I/O and record identification
Record formats-variable-length, fixed-length, and variable with fixed-length control
Carriage control
Sequential files
Relative files
Indexed sequential files
For detailed description of record positional information and the READ, WRITE, DELETE, and REWRITE statements, see the Endless PL/I for OpenVMS Reference Manual.
6.1. File Organizations
OpenVMS Record Management Services (RMS) supports the following three file organizations for record files:
Sequential
Relative
Indexed sequential
The relative and indexed sequential file organizations are valid only for disk devices. To read or write files on tape or unit record devices, you must use sequential organization.
The type of organization you select for a file and the attributes of the file, that is, the record format and size, the file size, and so on, are set when you create a file and need not be specified again. When a program subsequently accesses an existing file, the file's organization and attributes are known to the file system. Table 6.1, “Attributes and Access Modes for Record Files” shows the attributes and access modes for record files.
Sequential Files
In a sequential file, records are ordered one after the other, in the order in which they are written. New records can be added only at the end of an existing file. Existing records can be updated or replaced only if the replacement record is exactly the same length as the original record. Sequential files are discussed in more detail in Section 6.5, “Sequential 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 6.6, “Relative Files”.
Indexed Sequential Files
In an indexed sequential file, each record has one or more data keys that define a sort order for the file. The file system maintains an index for each key in the file and uses these indexes to locate a record when a program specifies the key of interest.
The records can be accessed either in the sort order defined by a key, or individually by key specification. Indexed sequential files are discussed in more detail in Section 6.7, “Indexed Sequential Files”.
Table 6.1, “Attributes and Access Modes for Record Files” shows the attributes and access modes for record files.
|
Attributes Specified |
Attributes Implied |
Valid Devices and File Organizations | Usage |
|---|---|---|---|
|
SEQUENTIAL OUTPUT | RECORD |
Any output device or file except indexed | Records can be added to the end of the file with WRITE statements. Each WRITE statement adds a single record to the file. |
|
SEQUENTIAL INPUT | RECORD |
Any input device or file | Records in the file are read with READ statements. Each statement reads a single record. |
|
SEQUENTIAL UPDATE | RECORD |
Relative, indexed, sequential disk | READ statements read a file's records in order. PL/I maintains the current record, which is the record just read. This record can be replaced in a REWRITE statement.[a] In a relative or indexed sequential file, the current record can also be deleted with a DELETE statement. Each statement processes a single record. |
|
DIRECT OUTPUT |
KEYED RECORD |
Relative, indexed, sequential disk[b] | WRITE statements insert records into the file at positions specified by keys. Each statement inserts a single record. |
|
DIRECT INPUT |
KEYED RECORD |
Relative, indexed, sequential disk[b] | READ statements specify records to be read randomly by key. Each statement reads a single record. |
|
DIRECT UPDATE |
KEYED RECORD |
Relative, indexed, sequential disk[b] | READ, WRITE and REWRITE statements specify records randomly by key. In a relative or indexed file, records can also be deleted by key. |
|
KEYED SEQUENTIAL OUTPUT | RECORD |
Relative, indexed, sequential disk[b] | WRITE statements insert records into the file at positions specified by keys. Each statement inserts a single record. This mode is identical to DIRECT OUTPUT. |
|
KEYED SEQUENTIAL INPUT | RECORD |
Relative, indexed, sequential disk[b] | READ statements access records in the file randomly by key or sequentially. |
|
KEYED SEQUENTIAL UPDATE | RECORD |
Relative, indexed, sequential disk[a] | Any record I/O operation is allowed except a WRITE statement that does not specify a key or a DELETE statement for a sequential disk file with fixed-length records. |
[a] For a file with sequential organization, the record being written must have the same length as the one that was read. [b] The file must have fixed-length records. | |||
6.2. Access Modes
In standard PL/I, you can specify one of the following sets of attributes to define how a program accesses 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.
Two additional ways to access a file exist in PL/I. You specify an ENVIRONMENT option to indicate that you are going to process a file in either of these ways:
BLOCK_IO indicates that the file will be accessed in terms of blocks, rather than records.
RECORD_ID_ACCESS indicates 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.
6.2.1. Sequential Access
You can use sequential access with any type of file organization. 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.
When you access an indexed sequential file sequentially, you can specify the number of the index on which to base the sequence. The next record in the input operation is the next ordered record in the specified index.
6.2.2. Random Access
When you access a file randomly by key, each I/O request must contain the KEY or KEYFROM option. The contents of the specified key depend on the file's organization, as follows:
For a relative file, the key is the relative record number of the record to be accessed.
For an indexed sequential file, the key is the portion of the record defined as a key field.
In a disk file with fixed-length records, the key value is the record number relative to the beginning of the file. The first record in the file is relative record number 1, the second record is relative record number 2, and so on.
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.
6.2.3. Random 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.
6.2.4. Block Input/Output
A block is a physical extent on a disk volume. You can elect to perform all of your I/O operations in terms of disk blocks: the file system transfers blocks of data at a time to your program. Your program is responsible for interpreting and deblocking the data in the blocks.
To perform block I/O, specify the BLOCK_IO option of the ENVIRONMENT attribute. This option is valid for disk files and for magnetic tape files. You can access a disk file for block I/O using either sequential or random access. If you are performing sequential block I/O, blocks are transferred to your program in sequential order. If you use keyed access, you must specify a virtual block number in the KEY or KEYFROM option. The first block in the file is number 1, the second block is number 2, and so on.
Block I/O to magnetic tape files can only be performed sequentially.
When a file is opened for block I/O, the SPACEBLOCK built-in subroutine can be used to move the file forward or backward a given number of blocks.
6.2.5. Access by Record Identification
You can access records in any type of disk file that is opened for any type of access using a record identification. You must do the following:
Specify the RECORD_ID_ACCESS option in the ENVIRONMENT attribute for the file.
Supply a 2-element array variable to save the record identification of records you want to access by record identification.
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.
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.
6.3. Record Formats
OpenVMS Record Management Services (RMS) allows the following types of record formats:
Fixed length
Variable length
Variable with fixed-length control
Fixed-length records and variable-length records are allowed for all file organizations. Variable with fixed-length control records are allowed in sequential and relative files only.
You need specify the format only when you create a file. Thereafter, each time you open the file PL/I determines the format of the records in the file.
6.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 thereafter.
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.
When you process a file with fixed-length records, you can specify the SCALARVARYING option of the ENVIRONMENT attribute (see Chapter 7, Options of the ENVIRONMENT Attribute) to process records in the standard PL/I manner.
6.3.2. Variable-Length Records
In a file consisting of variable-length records, each record can have a different size. RMS 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. You can specify 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.
6.3.3. Variable-Length Records with a Fixed-Length Control Area
Variable-length records with a fixed-length control area are similar to variable-length records. However, they also contain a fixed-length control field. The control field size is the same for every record in the file.
The fixed control area in a record can contain data that is not related to the other data in the file. For example, a line-oriented editing program could use the fixed control area for sequence numbers.
To create a file of variable-length records with a fixed-length control area, specify the FIXED_CONTROL_SIZE option of the ENVIRONMENT attribute. The length of the control area you specify is a permanent attribute of the file; it cannot be changed later.
When you specify the maximum record length for a file containing variable-length with fixed-length control records, the length you specify does not include the length of the fixed control area. For example:
DECLARE VFC_FILE FILE RECORD ENVIRONMENT ( MAXIMUM_RECORD_SIZE(250), FIXED_CONTROL_SIZE(2));
When the file VFC_FILE is opened for writing, the size of the fixed-length control area is set at 2 bytes; the maximum size of the data portion of any record is set at 250 bytes.
I/O operations on the file can process the fixed control area. The READ statement returns the contents of the fixed control area to a variable specified in the FIXED_CONTROL_TO option. The REWRITE and WRITE statements modify the fixed control area using the variable specified in the FIXED_CONTROL_FROM option, if any.
If this file is printed by the OpenVMS system, the contents of the fixed control area are printed to the left of each record.
6.4. Carriage Control
PL/I provides a default carriage control for files that will be printed. This format, called carriage return format, can be specified in the ENVIRONMENT option list (see Chapter 7, Options of the ENVIRONMENT Attribute) with the CARRIAGE_RETURN_FORMAT option; this option is never required.
When a file has carriage return format, the file can be output to a printer or terminal on a record-by-record basis. On
output, each record is automatically preceded by a line feed (<LF>) character and followed by a
carriage return (<CR>) character; these characters are not stored in the record. Thus, each record in
the file occupies one line of output. This type of carriage control is valid for any file or record organization.
An alternative form of carriage control is the PRINTER_FORMAT option of the ENVIRONMENT attribute, which provides more explicit control of the output format and printing. Using printer format, you can specify such things as overprinting, skipping multiple lines, and so on. In a PL/I program, you will almost never need to use printer format; the PUT statement provides the same functions when it outputs data to a file with the PRINT attribute.
6.5. Sequential Files
This section shows examples of some typical sequential file I/O operations on sequential disk files and on sequential devices, including magnetic tapes.
6.5.1. Creating a Sequential File
Whenever a PL/I program opens a file with the SEQUENTIAL OUTPUT attributes, PL/I normally creates a new sequential file. By default, records are 510-byte, variable-length records. Each WRITE statement adds a new record to the file.
Appending Records to an Existing File
You can open a file with the APPEND option of the ENVIRONMENT attribute to add new records to the end of an existing sequential file. This overrides the default action of PL/I, which is to create a new version of an existing file when the existing file is opened for output. For example:
OPEN FILE(BIRD_FILE) OUTPUT SEQUENTIAL TITLE('BIRDS.DAT') ENV(APPEND); 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.
Superseding an Existing File
The ENVIRONMENT option SUPERSEDE lets you create a new version of a file each time you write it, simultaneously deleting an existing version. By default, each time a specific file is written, PL/I gives it a new version number and does not replace the existing version. For example:
OPEN FILE(CONTROL) OUTPUT RECORD TITLE('CONTROL.DAT;1') ENVIRONMENT(SUPERSEDE);
This OPEN statement opens the file CONTROL.DAT;1. If this file already exists, it is deleted.
6.5.2. Using Magnetic Tape Files
Before you execute a PL/I program that reads or writes a sequential file on a magnetic tape volume, you must use the following sequence of DCL commands:
Use the ALLOCATE command to allocate a device on which to mount the tape volume. For example:
$
ALLOCATE MT:_MTA0: ALLOCATEDThe ALLOCATE command responds with the name of the physical device. You can now place the physical tape reel on the device.
If the tape is a new tape that you are going to write or overwrite, use the INITIALIZE command to format the tape and write a label on it. For example:
$
INITIALIZE MTA0: MYTAPEThis command writes the label MYTAPE on the tape volume mounted on MTA0: (the system printer device).
Use the MOUNT command to ready the volume for use and, at your option, to define a logical name for the device and file. For example:
$
MOUNT MTA0: MYTAPE TAPEFILE
After this sequence of commands, a PL/I program that writes records using the logical name TAPEFILE will be writing to the MTA0: tape volume. For example:
DECLARE OUTFILE FILE RECORD OUTPUT; OPEN FILE(OUTFILE) TITLE('TAPEFILE:TAPE1.FIL');
When this OPEN statement is executed, the logical name TAPEFILE is translated to its equivalence MTA0:, and the file MTA0:TAPE1.FIL;0 is created. Magnetic tape files always have a version number of 0.
Format of Magnetic Tapes
RMS supports the magnetic tape structure defined by ANSI X3.27-1977, the Magnetic Tape Labels and File Structure for Information Interchange. The tapes are encoded in ASCII format and can be processed on 9-track tape drives only.
The INITIALIZE command writes a label on the tape in the required format. A tape created on an OpenVMS system can be read on another system that supports the same tape label format.
Tape Positioning
When an existing magnetic tape file is opened, it is by default rewound, if necessary, and positioned at its beginning. This positioning can be overridden in the following ways:
If the APPEND option of ENVIRONMENT is specified and if the file is opened with the OUTPUT attribute, the tape is wound and positioned at the end of the specified file. The next WRITE statement adds a new record at the end of the existing file.
The CURRENT_POSITION option of ENVIRONMENT causes the tape to remain at its current position when the next file is opened. Thus, if the file is in the middle of the tape, it is not rewound when the next OPEN statement is specified for the tape.
By default, when a file is closed, the tape remains positioned after the last record that was read or written. The ENVIRONMENT option REWIND_ON_CLOSE can override this action and position the tape at its beginning.
While the file is open, the program can call the REWIND built-in subroutine to rewind the tape to its beginning.
DECLARE TAPEFILE FILE; OPEN FILE (TAPEFILE) OUTPUT RECORD ENVIRONMENT(APPEND); WRITE FILE (TAPEFILE) FROM (NEWREC); ⋮ CLOSE FILE(TAPEFILE) ENVIRONMENT (REWIND_ON_CLOSE); OPEN FILE(TAPEFILE) INPUT RECORD;
In this example, the file TAPEFILE is opened for output with the APPEND option. WRITE statements add new records at the end of the tape file. Then, the CLOSE statement specifies that the tape is to be rewound, and the next OPEN statement opens the file for input. The first READ statement reads the first record in the file.
Blocking a Magnetic Tape File
On a magnetic tape, a block is a unit consisting of an integral number of records. Because of the control information needed to separate records on a tape, operations on a tape can be improved by blocking.
To create a blocked tape file, you must open it with the ENVIRONMENT option BLOCK_SIZE. This option specifies the size of the blocks. RMS automatically performs the blocking necessary. For example:
OPEN FILE(TAPEFILE) ENVIRONMENT( BLOCK_SIZE (2048), MAXIMUM_RECORD_SIZE (512), FIXED_LENGTH_RECORDS); WRITE FILE(TAPEFILE) FROM (BIG_RECORD);
Following this opening, each WRITE statement writes a single record; the file system buffers the records until it accumulates four records and then transfers them, blocked, to the tape volume.
The BLOCK_SIZE option, if specified, is ignored when the output file is not a magnetic tape device. Thus, if you create and test a program that writes to a magnetic tape, you can test the program's output on a disk device; PL/I ignores the block size.
When a blocked magnetic tape file is read, RMS determines the block size from the tape itself, and deblocks the file as individual records are read.
Performing Block Input/Output
If a tape file is opened with the ENVIRONMENT option BLOCK_IO, the file can be read or written in terms of blocks. The size of a block is determined by the value of the BLOCK_SIZE option. Each I/O operation transfers the specified number of bytes. For example:
OPEN FILE (TAPEFILE) ENV (BLOCK_IO,BLOCK_SIZE(2048)); READ FILE (TAPEFILE) INTO (READBUF);
These statements open the file TAPEFILE and read a 2048-byte block of data into the program variable READBUF. When a file is opened with the BLOCK_IO option, RMS does not block or deblock records. Your program must perform all necessary blocking and deblocking.
When a file is opened for block I/O, the program can advance the tape or move it backward a specified number of blocks by calling the SPACEBLOCK built-in subroutine. For example:
CALL SPACEBLOCK(TAPEFILE,3);
This call advances the file TAPEFILE forward three blocks. The SPACEBLOCK built-in subroutine is described in Chapter 9, File-Handling Built-In Subroutines.
Multivolume Tape Files
The ANSI standard X3.27-1977 for magnetic tapes allows any of the following combinations of tape files:
A single file on a single volume (that is, a reel)
A single file on more than one volume
Multiple files on a single volume
Multiple files on more than one volume
When more than one tape volume is required to contain a file or files, the tapes constitute a volume set. The OpenVMS system processes volume sets as follows:
When a file is being created on a tape volume and the tape reaches its end-of-volume, the magnetic tape control program (ACP) sends a message to a designated system operator requesting the operator to mount another tape volume. The program that is attempting to write to the tape must wait until the operator (or user who is performing operator functions) responds to the request. The response generally includes the initialization of another tape, with a volume number one greater than the volume number of the current volume.
When a file that spans two or more volumes is being read and the tape reaches end-of-tape, the magnetic tape ACP sends a message to the system operator requesting the operator to mount the next tape in the volume set.
Normally, RMS requests new volumes automatically. However, a PL/I program can request that the next volume in a volume set be mounted, for either an input or an output operation, by calling the NEXT_VOLUME built-in subroutine. The NEXT_VOLUME subroutine is described in Chapter 9, File-Handling Built-In Subroutines.
The physical process of volume switching, whether the switching is performed automatically by RMS or as a result of a call to the NEXT_VOLUME built-in subroutine, is transparent to the PL/I program. As a user, you may wish to function as an operator to receive the volume switching requests and to mount the volumes yourself. For a description of the procedure for handling volume switching, see the OpenVMS DCL Dictionary.
6.5.3. Allocated and Spooled Devices
The OpenVMS system spools low-speed I/O devices such as printers by accumulating data for the device in a file, and then queuing the file for processing when it is closed.
In a PL/I program, when you specify a device name such as LPA0: in a TITLE option, the specified device may be currently allocated for use by another user or be spooled. Depending on the status of the device, the following can occur:
If the device is spooled, all output to the device is written to a temporary file. When the file is closed, it is submitted to the queue for the spooled device.
If the device is allocated to another user, the UNDEFINEDFILE condition is signaled. If referenced in an ON-unit for this condition, the ONCODE built-in function returns the value associated with the SS$_DEVALLOC status code.
If the device is allocated to the current process, PL/I assigns a channel to the device, and each WRITE statement writes a physical line to the device.
If the device is not allocated and is not spooled, PL/I assigns a channel to the device. This assignment performs an explicit allocation of the device to the current process.
You can allocate a device before running a program by issuing the DCL command ALLOCATE. Within a PL/I program, you can invoke the system service SYS$ALLOC to allocate a device. For information on commands for device allocation and control, see the OpenVMS DCL Dictionary. For information on allocating devices using the SYS$ALLOC system service, see the OpenVMS System Services Reference Manual.
6.6. 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.
6.6.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 written and accessed.
Figure 6.1, “A 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.
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.
6.6.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, you should give consideration to using the following ENVIRONMENT options to maximize the efficiency of I/O operations on the file:
MAXIMUM_RECORD_NUMBER
MAXIMUM_RECORD_SIZE
BUCKET_SIZE
FILE_SIZE
EXTENSION_SIZE
Considerations for specifying values for each of these follow. For more detailed information on file design, see the OpenVMS Record Management Services Reference Manual.
Maximum Record Number
The MAXIMUM_RECORD_NUMBER option specifies the largest relative record number that will be used in the file, and thus specifies the maximum number of cells that the file can have. If you do not specify a maximum record number, PL/I sets the maximum record number to zero. This permits the file to expand to any size.
For example, if a relative file will contain inventory data on 600 parts, and the part number is to be used as the file key, the MAXIMUM_RECORD_NUMBER option can be specified as follows:
DECLARE PARTS FILE ENVIRONMENT ( MAXIMUM_RECORD_NUMBER (600));
You should realistically allow for future expansion of the file when you specify a maximum record number; the number is a permanent attribute of the file and cannot be changed. PL/I signals the KEY condition when a key value is too large.
Maximum Record Size
When you specify the length of the records in a file, RMS uses the value you specify in the MAXIMUM_RECORD_SIZE option to calculate a cell size. It uses the following formulas to calculate the size:
For fixed-length records:
cell-size = 1 + record-size
One byte is required for overhead; this byte contains a 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.
For variable-length records with fixed control:
cell-size = 3 + maximum-record-size + size-of-fixed-control-area
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, you should 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.
Bucket Size
A bucket is the storage unit for data in the file. Records are arranged in buckets, which consist of an integral number of physically contiguous 512-byte disk blocks. Within the bucket, records can cross block boundaries; however, records cannot cross bucket boundaries.
When RMS transfers data from a file, it transfers data a bucket at a time; thus, a large bucket size reduces the number of actual data transfers that are required. When you do not specify a bucket size, RMS uses the cell size rounded to a multiple of 512 bytes. When records are written to the file, RMS places as many records as will fit in each bucket. Excess space is wasted.
You can improve I/O performance by specifying a bucket size that is a multiple of the cell size, and by calculating whether space is being wasted. For example:
DECLARE EMP_FILE OUTPUT RECORD ENVIRONMENT ( FIXED_LENGTH_RECORDS, MAXIMUM_RECORD_SIZE (80) BUCKET_SIZE (4) );
The file EMP_FILE will be created with 81-byte cells and buckets that have 2048 bytes (that is, four 512-byte blocks). Each bucket can contain twenty-five 81-byte cells; 23 bytes in each bucket are unused.
When you specify a maximum record size and a bucket size for a relative file, you should consult the description of the BUCKET_SIZE option in Chapter 7, Options of the ENVIRONMENT Attribute. That description contains formulas for calculating the bucket size within the limits required by RMS.
File Size
To avoid the processing overhead that occurs when a file is extended beyond an initial default allocation, you can specify the FILE_SIZE option when you create a relative file. If you specify this option, you can preallocate all the space that will ever be required for the cells in the file.
To determine the space requirements of a file, you can use the following formulas. To compute the size of the file in 512-byte blocks: display file_size = (511 + ( number_of_buckets * bytes_per_bucket ) ) / 512
file_sizeIs the size of the file in blocks.
number_of_bucketsIs the number of buckets in the file.
bytes_per_bucketIs the number of bytes in each bucket.
To compute the number of buckets: display number_of_buckets = number_of_records / number_of_cells_per_bucket
number_of_bucketsIs the number of buckets in the file.
number_of_recordsIs the number of records in the file.
number_of_cells_per_bucketIs the number of records that can fit into a bucket.
You can also specify the CONTIGUOUS or CONTIGUOUS_BEST_TRY options in conjunction with the FILE_SIZE option to attempt to allocate all of the space for the file in contiguous disk blocks.
Extension Size
If you plan to add data to the file, you should specify a reasonable default extension size to reduce the number of times the file is extended. The FDL attribute FILE EXTENSION sets the extension size. The value to be assigned depends on the size of the records in the file and on the number of records that will be added to the file. The EDIT/FDL Utility calculates and assigns the correct extension size. For more information on the EDIT/FDL Utility, see Section 6.7.2, “Defining and Creating an Indexed Sequential File”.
You can also specify the extension size by using the DCL command SET FILE/EXTENSION=n. To determine the value to be assigned to the EXTENSION attribute, multiply the record size by the number of records to be added.
If you do not specify an extension size, RMS allocates the number of blocks needed for each extension.
6.6.3. Using Relative Files
You can create a relative file from any existing file that is suitable for relative file organization. Example 6.1, “Creating a Relative File” illustrates copying a sequential file with fixed-length records into a relative file.
The following notes are keyed to Example 6.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 a maximum record number of 600. It has 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.
COPY_TO_RELATIVE: PROCEDURE OPTIONS(MAIN); %INCLUDE PARTLIST; /* Declaration of PARTLIST */ /* ❶ */ DECLARE OLDFILE FILE INPUT RECORD SEQUENTIAL; DECLARE RECORD_NUMBER FIXED BINARY; DECLARE PARTS FILE OUTPUT KEYED RECORD ENVIRONMENT( MAXIMUM_RECORD_NUMBER(600), /* ❷ */ 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;
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.
Populating a Relative File
In Example 6.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 allowed for any record in the file is 600, but the largest record number specified for a record is 200, then RMS allocates only enough space for 200 records.
When you initially populate a file and you plan to fill the entire file through the maximum record number, you can cause RMS to use either of the following techniques to allocate space for the entire file:
Specify the FILE_SIZE option to allocate space for the file when it is created.
Write the record with the largest relative record number first. This will force RMS to allocate space for the entire file.
These techniques can optimize the throughput for the subsequent file additions, because RMS will not need to perform repeated extensions to the file as records are added.
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 PARTLIST */ 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.
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.
6.6.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 a relative record number exceeds the maximum record number specified for the file, or 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 or whether a record number specified exceeds the file's maximum record number.
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; /* Check for maximum record number exceeded */ ELSE IF ONCODE = RMS$_MRN THEN DO; PUT SKIP EDIT('Part number',PARTLIST.NUMBER, 'invalid. Reenter') (A,X,A,X,A); GET LIST(PARTLIST.NUMBER); /* Get new value */ GOTO GET_DATA; /* Go get other info */ END; END; ⋮ GET_DATA:
In this example, the ON-unit sets symbolic names for two specific status values returned by ONCODE, from PLI$STARLET:
The value RMS$_REX indicates that a record already exists.
The value RMS$_MRN indicates that a relative record number specified exceeds the maximum record number.
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 module $RMSDEF, from PLI$STARLET.TLB.
6.7. Indexed Sequential Files
This section describes the organization of indexed sequential files, suggests considerations for creating and using them, and shows examples of some typical operations.
6.7.1. Indexed File Organization
In an indexed sequential file, the file contains data records and pointers to the records. Data records and record pointers are arranged in buckets, which consist of an integral number of physically contiguous 512-byte disk blocks. Individual records within the file are located by the specification of the keys (indexes) associated with the records. Each file must have a primary key-that is, a field within the record that has a unique value to distinguish it from all other records in the file. An indexed sequential file can also have up to 254 alternate keys, which need not have unique values.
RMS writes records to an indexed file in collating sequence according to the primary key, putting them in buckets that are chained together. Thus, an individual file can be accessed sequentially with any key.
Figure 6.2, “An Indexed Sequential File” illustrates an indexed sequential file with a single key.
The records in the file illustrated in Figure 6.2, “An Indexed Sequential File” consist of address data that might have been defined in a PL/I structure as follows:
DECLARE 1 ADDRESS_FILE, 2 EMPLOYEE_NAME CHARACTER(30), 2 ADDRESS, 3 STREET CHARACTER(20), 3 ZIP_CODE CHARACTER(5);
In this file, the key is the employee name.
When RMS writes records to an indexed sequential file, it builds and maintains a tree-like structure of key values and location pointers. When records are accessed by key, RMS uses the tree to locate individual records. Thus, when a PL/I program accesses the record whose key value is JONES, RMS traverses the indexes to locate the record.
When new records are added to an indexed sequential file, a data bucket may not have enough room to accommodate a new record. In this case, RMS performs what is called bucket splitting: it inserts a new bucket in the chain of data buckets and moves enough records from the previous bucket to preserve the primary key sequence. Bucket splitting is transparent to the PL/I program; the program knows only that it has added a record to the file.
6.7.2. Defining and Creating an Indexed Sequential File
As shown in Figure 6.3, “Creating a Data File”, an indexed sequential file must first be defined, then created. You define the characteristics of your indexed sequential file using the Open File Definition Language (FDL), and then use that definition (the resulting FDL file) to create the indexed sequential file for use by your program.
You can use several methods to create an FDL definition file: an OpenVMS editor, a PL/I program, or the FDL Facility. The following steps are keyed to the callout numbers in Figure 6.3, “Creating a Data File”.
Use an editor to describe the data file.
An FDL language source file defines the data file.
You can use a program to create an FDL language source file and call FDL$CREATE to create a data file.
You can use FDL to create a data file.
The data file is created.
The following sections explain how to create a definition file by using the FDL Facility and how to create one through a PL/I program.
6.7.2.1. Using EDIT/FDL
You can use the command EDIT/FDL to define an indexed sequential file for PL/I. Note that while you can construct FDL file specifications using any of the OpenVMS text editors, the FDL Facility gives you the added convenience of systematic prompting for and automatic formatting of the specifications. The specifications are written in the File Definition Language (FDL) and the files are referred to as FDL files.
The FDL Facility is interactive: it prompts you to enter data and responds with error messages when you enter data incorrectly. It supplies many default values. The only data that you must specify is as follows:
The file name of the file you are creating
The word INDEXED, to indicate that the file is an indexed sequential file
The number of records that will initially be loaded into the file
The mean record size
The size of the key
You can obtain help by pressing RETURN in response to the first menu. Each item on the menu leads to another menu in the specification of the data file. You can return to the original menu by pressing Ctrl/z.
$EDIT/FDL Return_File:STATE50 ReturnParsing Definition File DBA0:[SMITH]STATE50.FDL; will be created. Press return to continue (^Z for Main Menu)ReturnOpenVMS FDL Editor Add to insert one or more lines into the FDL definition Delete to remove one or more lines from the FDL definition Exit to leave the FDL Editor after creating the FDL file Help to obtain information about the FDL Editor Invoke to initiate a script of related questions Modify to change existing line(s) in the FDL definition Quit to abort the FDL Editor with no FDL file creation Set to specify FDL Editor characteristics View to display the current FDL definition Main Editor Function (Keyword)[Help]:INVOKE Return
FDL displays further menus to guide you through the creation of the file.
After you define your data file, FDL displays a message like the following, giving the file specification of the definition file:
DBA0:[SMITH]STATE50.FDL;1 40 lines
FDL formats the information that you supply so that it can be used to create an indexed sequential file. However, you can also use EDT or any other OpenVMS text editor to design an FDL text.
FDL allows you to specify many characteristics for your file, but requires you to specify only a limited number. The following example, written in FDL, illustrates some of the possible file characteristics. The explanatory comments to the right would not appear in an FDL file; they have been added for your information.
SYSTEM
SOURCE VAX/VMS
FILE
NAME state50.dat /* file name */
ORGANIZATION indexed
RECORD
CARRIAGE_CONTROL carriage_return
FORMAT fixed /* record attributes */
SIZE 60
AREA 0
ALLOCATION 25 /* number of blocks */
BEST_TRY_CONTIGUOUS yes /* as close as possible */
BUCKET_SIZE 2 /* number of blocks in record */
EXTENSION 2 /* blocks for extension */
AREA 1
ALLOCATION 3
BEST_TRY_CONTIGUOUS yes
BUCKET_SIZE 2
EXTENSION 0
AREA 2
⋮
AREA 7
ALLOCATION 2
BEST_TRY_CONTIGUOUS yes
BUCKET_SIZE 1
EXTENSION 0
KEY 0 /* primary key */
CHANGES no /* cannot be changed */
DATA_AREA 0 /* data in AREA 0 */
DATA_FILL 100 /* %fill for data */
DUPLICATES no /* not allowed */
INDEX_AREA 1 /* index in AREA 1 */
INDEX_FILL 100 /* %fill for index */
LEVEL1_INDEX_AREA 1 /* level-1 index in AREA 1 */
PROLOGUE 2 /* type for index */
SEG0_LENGTH 20 /* key length */
SEG0_POSITION 0 /* key position */
TYPE string /* character string */
⋮
KEY 3 /* third alternate key */
CHANGES yes
DATA_AREA 6
DATA_FILL 100
DUPLICATES yes
INDEX_AREA 7
INDEX_FILL 100
LEVEL1_INDEX_AREA 7
SEG0_LENGTH 3
SEG1_LENGTH 3
SEG2_LENGTH 3
SEG0_POSITION 0
SEG1_POSITION 20
SEG2_POSITION 40
TYPE stringAfter creating an FDL file with EDIT/FDL or a text editor, you can then use the command CREATE/FDL, which uses the specifications in the FDL file to create a new, empty data file. Thus, you can use EDIT/FDL to define the data file, and then create the data file when it is required later. See the OpenVMS File Definition Language Facility Reference Manual for further details.
6.7.2.2. Using a PL/I Program
You can write a PL/I program to define an indexed sequential file or to create an indexed sequential file from an existing definition, or both. The following lines in the program enable the optional generation of a file definition and the use of that definition to create a file:
%INCLUDE FDL$CREATE; ⋮ CALL FDL$CREATE ('STATE50.FDL');
The following program, CREATE50, defines a file similar to the file defined with the FDL Facility in Section 6.7.2.1, “Using EDIT/FDL”.
CREATE50: PROCEDURE OPTIONS(MAIN); DECLARE ANSWER CHAR(1); /* answer variable */ DECLARE (I,J) FIXED BIN(31); /*index control variables */ %INCLUDE FDL$CREATE; /* default allocation, bucket size, and extent for each area */ DECLARE AREA_ATTRIBUTES(0:7,0:2) FIXED BIN(7) STATIC INITIAL ( 25, 2, 2, 2, 2, 0, 15, 2, 1, 3, 2, 0, 20, 2, 2, 3, 2, 0, 9, 1, 0, 7, 1, 0); /* default areas, fills, segment lengths, and positions for each key */ DECLARE KEY_ATTRIBUTES(0:3,0:10) FIXED BIN(7) STATIC INITIAL ( 0, 100, 1, 100, 1, 20, 0, 0, 0, 0, 0, 2, 100, 3, 100, 3, 20, 0, 0, 20, 0, 0, 4, 100, 5, 100, 5, 20, 0, 0, 40, 0, 0, 6, 100, 7, 100, 7, 3, 3, 3, 0, 20, 40); /* FDL file that will be created if necessary */ DECLARE FDL FILE PRINT OUTPUT; /* find out if FDL file already exists */ GET LIST (ANSWER) OPTIONS(PROMPT('Have you already created STATE50.FDL with EDIT/FDL or EDT? ')); /* file definition (.FDL) file creation */ IF ANSWER = 'N' | ANSWER = 'n' THEN DO; PUT SKIP LIST (' Creating STATE50.FDL.....'); OPEN FILE (FDL) TITLE ('state50.fdl') PAGESIZE(32767); /* define system */ PUT SKIP FILE (FDL) LIST ('SYSTEM'); PUT SKIP FILE (FDL) LIST (' SOURCE VAX/VMS'); /* define file */ PUT SKIP FILE (FDL) LIST ('FILE'); PUT SKIP FILE (FDL) LIST (' NAME state50.dat'); PUT SKIP FILE (FDL) LIST (' ORGANIZATION indexed'); /* define record */ PUT SKIP FILE (FDL) LIST ('RECORD'); PUT SKIP FILE (FDL) LIST (' CARRIAGE_CONTROL carriage_return'); PUT SKIP FILE (FDL) LIST (' FORMAT fixed'); PUT SKIP FILE (FDL) LIST (' SIZE 60'); /* define areas */ DO I = 0 TO HBOUND (AREA_ATTRIBUTES,1); PUT SKIP FILE (FDL) LIST ('AREA '||trim(character(i))); PUT SKIP FILE (FDL) LIST (' ALLOCATION '|| character(area_attributes(i,0))); PUT SKIP FILE (FDL) LIST (' BEST_TRY_CONTIGUOUS yes'); PUT SKIP FILE (FDL) LIST (' BUCKET_SIZE '|| character(area_attributes(i,1))); PUT SKIP FILE (FDL) LIST (' EXTENSION '|| character(area_attributes(i,2))); END; /* define keys */ DO I = 0 TO HBOUND (KEY_ATTRIBUTES,1); PUT SKIP FILE (FDL) LIST ('KEY '||TRIM(CHARACTER(I))); IF I = 0 THEN PUT SKIP FILE (FDL) LIST (' CHANGES no'); ELSE PUT SKIP FILE (FDL) LIST (' CHANGES yes'); PUT SKIP FILE (FDL) LIST (' DATA_AREA '|| CHARACTER(KEY_ATTRIBUTES(I,0))); PUT SKIP FILE (FDL) LIST (' DATA_FILL '|| CHARACTER(KEY_ATTRIBUTES(I,1))); IF I = 0 THEN PUT SKIP FILE (FDL) LIST (' DUPLICATES no'); ELSE PUT SKIP FILE (FDL) LIST (' DUPLICATES yes'); PUT SKIP FILE (FDL) LIST (' INDEX_AREA '|| CHARACTER(KEY_ATTRIBUTES(I,2))); PUT SKIP FILE (FDL) LIST (' INDEX_FILL '|| CHARACTER(KEY_ATTRIBUTES(I,3))); PUT SKIP FILE (FDL) LIST (' LEVEL1_INDEX_AREA '|| CHARACTER(KEY_ATTRIBUTES(I,4))); DO J = 0 TO 2; IF KEY_ATTRIBUTES(I,5+J*2) ^= 0 THEN DO; PUT SKIP FILE (FDL) LIST (' SEG'||TRIM(CHARACTER(J))||'_LENGTH '|| CHARACTER(KEY_ATTRIBUTES(I,5+J*2))); PUT SKIP FILE (FDL) LIST (' SEG'||trim(character(j))||'_POSITION '|| CHARACTER(KEY_ATTRIBUTES(I,5+J*2))); END; END; PUT SKIP FILE (FDL) LIST (' TYPE string'); END; CLOSE FILE (FDL); END; /* create STATE50.DAT using STATE50.FDL as the definition file */ PUT SKIP LIST (' Creating file using STATE50.FDL.....'); CALL FDL$CREATE ('STATE50.FDL'); END CREATE50;
This program uses the same FDL statements that would be used if you had issued the EDIT/FDL command. Notice that the program asks you whether you have already created the file and, if so, calls FDL$CREATE, which permits you to use a preexisting file definition.
Note that the following command:
$ CREATE/FDL=STATE50is equivalent to the following command and response:
$RUN CREATE50Have you already created STATE50.FDL with EDIT/FDL or EDT?Y
6.7.3. Defining Keys
An indexed sequential file must have at least one key. The first (primary) key is always numbered 0. An indexed sequential file can have up to 255 keys; however, for file-processing efficiency it is recommended that you define no more than 7 or 8 keys. (The time required to insert a new record or update an existing record is directly related to the number of keys defined; the retrieval time for an existing record, however, is unaffected by the number of keys.)
When you design an indexed sequential file, you must define each key in the following terms:
Position and size
Data type
Index number
Options selected
When you want to define more than one key, or to define keys of different data types, you must be careful when you specify the key fields. The next few subsections describe some considerations for specifying keys.
Specifying Key Position and Size
When you specify a key, you must specify both its position in the record and its length. The position is specified with respect to the beginning of the record; thus, a key that is positioned beginning in the first byte of the record has a starting position of 0, a key positioned beginning in the 21st byte has a key position of 20, and so on.
If the ENVIRONMENT option SCALARVARYING is in effect, the key size for a CHARACTER VARYING key should be 2 more than your declared maximum size; you specify a key position offset 2 from the variable base offset.
To determine the key positions for fields within a structure, you can examine the storage map in the program listing that defines the structure. The following structure definition illustrates the relationships between key field definitions and the storage map offsets:
1 FOO: PROCEDURE;
2 1 DECLARE 1 STATE BASED (STATE_PTR),
3 1 2 NAME CHARACTER (20), /*Primary key */ ❶
4 1 2 POPULATION FIXED BINARY(31), /*3rd alternate key */❹
5 1 2 CAPITAL,
6 1 3 NAME CHARACTER(20),
7 1 3 POPULATION FIXED BINARY(31),
8 1 2 LARGEST_CITIES(2),
9 1 3 NAME CHARACTER(30),
10 1 3 POPULATION FIXED BINARY(31),
11 1 2 SYMBOLS,
12 1 3 FLOWER CHARACTER(30), /*1st alternate key*/ ❷
13 1 3 BIRD CHARACTER(30); /*2nd alternate key*/ ❸
14 1
15 1 END FOO;The resultant storage map shows the following:
- ❶
The primary key is 20 bytes, offset 0 bytes from the base of the structure or record.
- ❷
The first alternate key is 30 bytes, offset 116 bytes from the base of the structure or record.
- ❸
The second alternate key is 30 bytes, offset 146 bytes from the base of the structure or record.
- ❹
The third alternate key is four bytes, offset 20 bytes from the base of the structure or record.
The storage map is:
+-------------+
| Storage Map |
+-------------+
External Entry Points and Variables Declared Outside Procedures
---------------------------------------------------------------
Identifier Name Storage Size Line Attributes
---------- ---- ------- ---- ---- ----------
FOO 1 ENTRY, EXTERNAL
Procedure FOO on line 1
-----------------------
Identifier Name Storage Size Line Attributes
---------- ---- ------- ---- ---- ----------
BIRD 30 BY 13 OFFSET FROM BASE IS 146 BY,
MEMBER OF STRUCTURE SYMBOLS,
CHARACTER(30) UNALIGNED, NONVARYING ❸
CAPITAL 24 BY 5 OFFSET FROM BASE IS 24 BY,
MEMBER OF STRUCTURE STATE, STRUCTURE
FLOWER 30 BY 12 OFFSET FROM BASE IS 116 BY,
MEMBER OF STRUCTURE SYMBOLS,
CHARACTER(30) UNALIGNED, NONVARYING ❷
LARGEST_CITIES 68 BY 8 OFFSET FROM BASE IS 48 BY,
MEMBER OF STRUCTURE STATE, STRUCTURE
DIMENSION
NAME 30 BY 9 OFFSET FROM BASE IS 48 BY, MEMBER OF
STRUCTURE LARGEST_CITIES
CHARACTER(30), UNALIGNED, NONVARYING
NAME 20 BY 6 OFFSET FROM BASE IS 24 BY, MEMBER OF
STRUCTURE CAPITAL, CHARACTER(20)
UNALIGNED, NONVARYING
NAME 20 BY 3 OFFSET FROM BASE IS 0 BY, MEMBER OF
STRUCTURE STATE, CHARACTER(20)
UNALIGNED, NONVARYING ❶
POPULATION 4 BY 10 OFFSET FROM BASE IS 78 BY, MEMBER OF
STRUCTURE LARGEST_CITIES
FIXED BIN(31,0), ALIGNED, PRECISION
POPULATION 4 BY 7 OFFSET FROM BASE IS 44 BY, MEMBER OF
STRUCTURE CAPITAL, FIXED BIN(31,0)
ALIGNED, PRECISION
POPULATION 4 BY 4 OFFSET FROM BASE IS 20 BY, MEMBER OF
STRUCTURE STATE, FIXED BIN(31,0)
ALIGNED, PRECISION
❹
STATE based 176 BY 2 STRUCTURE
SYMBOLS 60 BY 11 OFFSET FROM BASE IS 116 BY, MEMBER OF
STRUCTURE STATE, STRUCTURE
⋮After you enter the EDIT/FDL command, you can specify the keys to FDL as follows:
Enter Desired Primary (Keyword)[-] :KEY 0- Legal KEY 0 Secondary Attributes - CHANGES yes/no LEVEL1_INDEX_AREA number DATA_AREA number NAME string DATA_FILL number NULL_KEY yes/no DATA_KEY_COMPRESSION yes/no NULL_VALUE char/num DATA_RECORD_COMPRESSION yes/no POSITION number DUPLICATES yes/no PROLOGUE number INDEX_AREA number TYPE keyword INDEX_COMPRESSION yes/no SEGn_LENGTH number INDEX_FILL number SEGn_POSITION number LENGTH numberEnter KEY 0 Attribute (Keyword)[-]:POSITIONReturnKEY 0 SEG0_POSITIONEnter value for this Secondary (0-16299)[-]:0Return- Resulting Primary Section - KEY 0 SEG0_POSITION 0Continue with this Same Primary (Yes/No)[No]:YESReturn
At this point, the menu Secondary Attributes reappears on your screen:
Enter KEY 0 Attribute (Keyword)[-] :LENGTHReturnKEY 0 SEG0_LENGTH Enter value for this Secondary (0-255)[-] :20Return- Resulting Primary Section - KEY 0 SEG0_LENGTH 20 SEG0_POSITION 0 Continue with this Same Primary (Yes/No)[No] :Return
At this point, you return to the first menu. To establish the keys necessary for ADDRESS.DAT, you need to step through the entire process again. In response to the prompt after the first menu, type ADD; in response to the prompt after the second menu, type KEY 1. Give KEY 1 (for example) a position of 116 and a length of 30.
Establish the third and last key the same way. Give KEY 2 a position of 146 and a length of 30. Then go back to the first menu and type VIEW in response to the prompt. A summary appears as follows:
KEY 0
SEG0_LENGTH 20
SEG0_POSITION 0
KEY 1
SEG0_LENGTH 30
SEG0_POSITION 116
KEY 2
SEG0_LENGTH 30
SEG0_POSITION 146Use FDL to change information if necessary.
Key Data Types
Table 6.2, “Key Data Types” summarizes the valid data types for keys in RMS indexed sequential files, lists the corresponding PL/I data type declaration, and shows how to specify the key data type and length to the FDL Facility.
|
Data Type |
PL/I Declaration |
FDL Specification |
|---|---|---|
| String[a] |
CHAR(n), where 1 < n < 255 |
STR n |
|
15-bit signed integer | FIXED BINARY(15) |
INT 2 |
|
31-bit signed integer | FIXED BINARY(31) |
INT 4 |
|
63-bit signed integer | FIXED BINARY(63) |
INT 8 |
|
16-bit unsigned binary[b] | FIXED BINARY(15) |
BIN 2 |
|
32-bit unsigned binary[b] | FIXED BINARY(31) |
BIN 4 |
|
64-bit unsigned binary[b] | FIXED BINARY(64) |
BIN 8 |
| Packed decimal |
FIXED DECIMAL(n) where 1< n < 16 |
DECIMAL n |
Index Numbers
When you define the keys in an indexed sequential file, you must assign an index number to each alternate key. The index number of the primary key is always 0; subsequent alternate key indexes are numbered 1, 2, and so on. When you create a file with the FDL Facility, you must define the keys in index number order.
When you want to access a record in an indexed sequential file by an alternate key, you specify the index number. You can specify the index number either in the INDEX_NUMBER option of ENVIRONMENT or in the INDEX_NUMBER option of a record I/O statement.
For example, to access the record for a state whose flower is MAGNOLIA in the indexed file STATE_FILE, when the current index is not 1, you must specify the index number 1, as in the following example:
READ FILE(STATE_FILE) INTO(STATE) KEY('MAGNOLIA') OPTIONS(INDEX_NUMBER(1));
This READ statement reads the first record whose key in the first alternate index is MAGNOLIA and sets the current index number to 1.
Key Options
When you define alternate indexes for an indexed sequential file, you can specify the following information:
Whether duplicate keys are allowed. If you select the duplicate key option, multiple records in the file can have the same key value in the alternate index. If you do not allow duplicate keys, PL/I signals the KEY condition if you attempt to write a record with a duplicate key.
Whether the key of a record can be changed. If you select the change option, a rewrite request can modify one or more key fields in the record. By default, PL/I signals the KEY condition if you attempt to rewrite a record in which a key field has been modified.
Whether keys are to be initialized with null values. When a null value has been specified for a key and a record is inserted with the given key field equal to the null value, no index entry will be made in the alternate index.
These options are described in the OpenVMS File Definition Language Facility Reference Manual.
6.7.4. Using Indexed Sequential Files
After you have defined and created an indexed sequential file, you can write records to it by opening it with the UPDATE attribute and using PL/I WRITE statements. For example:
OPEN FILE(STATE_FILE) RECORD DIRECT UPDATE; ⋮ WRITE FILE(STATE_FILE) FROM(STATE) KEYFROM(STATE.NAME);
This WRITE statement writes the record whose key value is specified by the field STATE.NAME in the structure STATE.
When a WRITE statement adds a record to an indexed sequential file, the value of the KEYFROM option must always be the primary key. In fact, the WRITE statement causes the index number to be reset to zero if any other index number is in effect.
Note
When PL/I copies the KEYFROM value into the record, it overwrites anything already in those positions, while distributing segmented values as specified by the RMS key description. Therefore, it is important that the key value come from some variable other than the record variable itself.
Populating an Indexed Sequential File
There are two techniques for optimizing the initial population (loading) of an indexed sequential file:
Use the RMS CONVERT utility to load the file. This utility copies records from an existing file to load the indexed file. This utility also optimizes the index structure.
Use the INITIAL_FILL option of the ENVIRONMENT attribute. This option causes RMS to apply the fill number specified for index buckets in the file when the file was created. This option is effective only if a fill number was specified to the FDL Facility. It leaves unused space in the file for the key values that are inserted. Subsequent insertion of records with similar key values (without the INITIAL_FILL option) will tend to use the free space provided within the file, without causing further growth of the file.
For details on specifying fill numbers for the FDL Facility, see the OpenVMS File Definition Language Facility Reference Manual.
Reading an Indexed Sequential File Sequentially
To read records in an indexed sequential file in collating order by key value, open the file with the INPUT and SEQUENTIAL attributes. The following example illustrates reading the file STATE_FILE in sequential order using the primary key, that is, using the STATE.NAME field. This procedure uses the SET option of the READ statement; thus, no space is required in the records.
DECLARE STATE_PTR POINTER, STATE_FILE FILE, EOF BIT(1) INITIAL ('0'B); DECLARE 1 STATE BASED (STATE_PTR), 2 NAME CHARACTER(20), ⋮ ON ENDFILE(STATE_FILE) EOF = '1'B; OPEN FILE(STATE_FILE) INPUT SEQUENTIAL; READ FILE(STATE_FILE) SET(STATE_PTR); DO WHILE (^EOF); PUT SKIP(2) LIST('State:',STATE.NAME); PUT SKIP(2) EDIT('Population:',STATE.POPULATION) (A,P'ZZ,ZZZ,ZZZ'); ⋮ READ FILE(STATE_FILE) SET(STATE_PTR); END;
In this example, the procedure reads the records in the file STATE_FILE beginning with the first record in its primary index, the NAME field.
Accessing Records by Alternate Key
To use an alternate key to read a record in an indexed sequential file, specify the INDEX_NUMBER option on the READ statement.
For example, if a file containing data about states has as its primary key the state name, it might have alternate keys for state flowers, birds, and so on. Assuming that a field called FLOWER is the first alternate key, you could access the record for a state whose flower is MAGNOLIA by writing the following statements:
OPEN FILE(STATE_FILE) KEYED INPUT; READ FILE(STATE_FILE) SET(STATE_PTR) KEY('MAGNOLIA') OPTIONS(INDEX_NUMBER(1));
The INDEX_NUMBER option followed by 1 specifies the first alternate index, the FLOWER field. The INDEX_NUMBER option is also valid on the REWRITE and DELETE statements.
You can access a file starting with an alternate index by opening the file with the INDEX_NUMBER option of the ENVIRONMENT attribute as in the following example:
OPEN FILE(STATE_FILE) SEQUENTIAL INPUT ENV( INDEX_NUMBER(2)); READ FILE(STATE_FILE) SET(STATE_PTR); DO WHILE (^EOF); PUT SKIP EDIT(STATE.BIRD,'is the bird of',STATE.NAME) (A,X,A,X,A); READ FILE(STATE_FILE) SET(STATE_PTR); END;
These statements, executed until the end-of-file is reached, access the records in the file STATE_FILE on the basis of its second alternate index, the BIRD field.
Updating Records in an Indexed Sequential File
You can modify records in an indexed sequential file by opening the file with the UPDATE attribute, and using the REWRITE and DELETE statements to modify or delete records from the file.
The following example shows the correction of an invalid field in a record in the file STATE_FILE:
DECLARE (STATENAME,NEWFLOWER) CHARACTER(30) VARYING; ⋮ OPEN FILE(STATE_FILE) KEYED SEQUENTIAL UPDATE; GET SKIP LIST(STATENAME) OPTIONS (PROMPT('State: ')); READ FILE(STATE_FILE) SET(STATE_PTR) KEY(STATENAME); GET SKIP LIST(NEWFLOWER) OPTIONS( PROMPT('New state flower name: ')); STATE.FLOWER = NEWFLOWER; REWRITE FILE(STATE_FILE);
The REWRITE statement rewrites the current record in the file, that is, the record that was just read with the READ SET statement.
Specifying the Type of Key Match
RMS allows generic key matching; that is, it can locate a record in an indexed sequential file whose key is greater than a specified key value, or whose key is greater than or equal to a specified key value. In PL/I, you can access records by generic key using the MATCH_NEXT and MATCH_NEXT_EQUAL options on a record I/O statement.
For example, the file STATE_FILE's third alternate key is the state's population. To list all states whose populations are greater than a specified size, you could open and read the file as follows:
DECLARE EOF BIT(1) STATIC INIT('0'B), SIZE FIXED BIN(31); OPEN FILE(STATE_FILE) KEYED SEQUENTIAL ENV( INDEX_NUMBER(3)); GET LIST(SIZE) OPTIONS(PROMPT('Starting population: ')); READ FILE(STATE_FILE) SET (STATE_PTR) KEY(SIZE) OPTIONS(MATCH_NEXT_EQUAL); DO WHILE (^EOF); PUT SKIP EDIT(STATE.NAME,'Population is ', STATE.POPULATION) (A,A,P'ZZ,ZZZ,ZZZ'); READ FILE(STATE_FILE) SET (STATE_PTR); END;
In this example, the size is obtained by a GET statement. The procedure opens the file, specifying the third alternate index, the POPULATION field, in the INDEX_NUMBER option. After accessing a record by matching the first key in this index that equals or exceeds the size entered, the procedure reads the file sequentially to the end-of-file, using that index.
Segmented Keys
PL/I supports the RMS segmented key concept. Segmented keys are used when a single key cannot be made in contiguous fields of the record. The key's parts are split up over the record, or reordered within a contiguous part of the record. Segmenting the key has the advantage of using only one key to represent several fields that are always defined and sorted as a conceptual unit.
When there is a KEYFROM clause with a segmented key, PL/I takes its usual action of copying the key value into the record variable and then writing the variable directly to disk through RMS.
PL/I copies the one contiguous value to the position or positions specified by the RMS definition for the segmented
key. This means it must break up the value into subfields or segments. Those fields are determined by the RMS segment
sizes for the key. Thus, for a 4-segment key of 8 bytes and individual sizes of 1, 1, 4, and 2, respectively, it would
break up the one contiguous key value ABCDEFGH as follows:
Segment 0 = "A"
Segment 1 = "B"
Segment 2 = "CDEF"
Segment 3 = "GH"
Then it copies each segment into its correct position in the record-the positions as specified by RMS, not your record variable structure declaration.
When RMS orders the records by the key, it reconstructs the original key value as one piece and sorts it that way. Thus the relative ordering of the segments within the record has no bearing on how the original value is sorted.
These actions are the same for nonsegmented keys in PL/I. They are treated as keys of one segment only, of the appropriate RMS-determined data type.
If you need to use segmented keys, be aware of the following considerations and constraints:
PL/I supports segmented keys for character-string data only. Segmented keys are not designed for other data types.
RMS requires the key values to be embedded in the record, rather than separate from it.
A difference exists in the actions of the KEY option and the KEYFROM option that becomes important if you are using segmented keys, as follows:
- KEY(
v) Indicates that an existing record is to be accessed at the indicated key value (v). There is no movement of the key value. (The KEY option is valid on the READ and REWRITE statements.)
- KEYFROM(
v) Indicates that a new record is to be created at the position specified by the key value (v). Because RMS requires the key value to be embedded in the record, PL/I moves the value into the correct place in the record buffer that is specified by the FROM option of the WRITE statement.
Note that the key specified by the KEYFROM value is always the primary key, and that PL/I sets the key number to the primary index number (0). For segmented keys, PL/I copies the value into the correct key field positions.
Note also that the KEYFROM option is used on the WRITE statement only, not the REWRITE statement. When you use a REWRITE statement, you need to ensure that the key value is in the record by first using a READ statement.
- KEY(
RMS directly supports multikeyed files, but PL/I does not. Whereas in PL/I you would expect that only the INDEX_NUMBER key of reference would have its value filled in by the KEYFROM option, RMS actually writes all the keys in the record when it does a write or update operation. Thus, you must yourself fill in the unreferenced keys in the record before you use a WRITE or REWRITE statement.
Error Handling
PL/I signals the KEY condition when errors occur while keys for indexed sequential files are being processed. For example, if a key value specified on a READ statement specifies a key that does not exist, or if a WRITE statement attempts to write a record using a primary key value that already exists, the KEY condition is signaled.
You can write an ON-unit to detect and correct some of the more common key errors. For an example of an ON-unit that detects whether a record with a given key value was not found or whether an attempt was made to write a record whose key duplicates the value of an existing key, see Chapter 4, The File System.