Compaq BASIC for OpenVMS
Alpha and VAX Systems
User Manual


Previous Contents Index

14.6.6 Deleting Records

The DELETE statement removes a record from a file that was opened with ACCESS MODIFY. After you have deleted a record you cannot retrieve it. DELETE works with relative and indexed files only.

A successful FIND or GET operation must precede the DELETE operation. These operations make the target record available for deletion. In the following example, the FIND statement locates record 67 in a relative file and the DELETE statement removes this record from the file. Because the cell itself is not deleted, you can use the PUT statement to write a record into that cell after deleting its contents.


FIND #1%, RECORD 67% 
DELETE #1% 

Note

There is no current record after a deletion. The next record pointer is unchanged.

14.6.7 Updating Records

The UPDATE statement writes a new record at the location indicated by the current record pointer. UPDATE is valid on RMS sequential, relative, and indexed files.

UPDATE operates on the current record, provided that you have write access to that record. In order to successfully update a variable-length record, you must know the exact size of the record you want to update. BASIC has access to this information after a successful GET operation. If you have not performed a successful GET operation on the variable-length record, then you must specify a COUNT clause in the UPDATE statement that contains the record size information.

If you are updating a variable length record, and the record that you want to write out is of different size than the record you retrieved, you must use a COUNT clause.

An UPDATE will fail with the exception "No current record" (ERR=131) if you have not previously established a current record with a successful GET or FIND. Therefore, when updating records you should include error trapping in your program to make sure all GET operations execute successfully.

An UPDATE operation on a sequential file is valid only when:

The following program searches to find a record in which the L_name field matches the specified Search_name$. Once this record is found and retrieved, the Rm_num field of that record is updated; the program then prompts for another Search_name$. If a match is not found, BASIC prints the message "No such record" and prompts the user for another Search_name$. The program ends when the user enters a null string for the Search_name$ value.


20      MAP (AAA) STRING L_name = 60%, F_name = 20%, Rm_num = 8% 
30      OPEN "STU.DAT"FOR INPUT AS FILE #9%,           & 
                ORGANIZATION SEQUENTIAL FIXED, MAP AAA 
50      INPUT "Last name";Search_name$ 
55      Search_name$ = EDIT$(Search_name$, -1%) 
60      IF Search_name$ = "" 
                THEN GOTO 32010 
        END IF 
65      RESTORE #9% 
70      WHEN ERROR IN 
75         GET #9% WHILE Search_name$ <> L_name 
        USE 
           IF ERR=11 
               THEN 
                   PRINT "No such record" 
                   CONTINUE 50 
               ELSE 
                   EXIT HANDLER 
           END IF 
        END WHEN 
80      INPUT "Room number";Rm_num 
90      UPDATE #9% 
100     GOTO 50 
32010   CLOSE #9% 
32030   PRINT "Update complete" 
32767   END 

Note

An UPDATE operation invalidates the value of the current record pointer. The next record pointer is unchanged.

When you update a record in a relative variable file, the new record can be larger or smaller than the record it replaces, provided that it is smaller than the maximum record size set for the file. When you update a record in an indexed variable file, the new record can also be larger or smaller than the record it replaces. The updated record:

The following program updates a specified record on an indexed file:


MAP (UPD) STRING Enrdat = 8%, LONG Part_num, Sh_num, REAL Cost 
OPEN "REC.ING"FOR INPUT AS FILE #8%,                    & 
      INDEXED, MAP UPD, PRIMARY KEY Part_num 
INPUT "Part number to update";A% 
Loop1: 
WHILE -1% 
   GET #8%, KEY #0%, EQ A% 
   INPUT "Revised Cost is";Cost 
   UPDATE #8% 
   INPUT "Next Record";A% 
   IF A% = 0% 
   THEN 
        EXIT Loop1 
   END IF 
NEXT 
CLOSE #8% 
END 

If the new record either omits one of the old record's alternate key fields or changes one of them, the OPEN statement must specify a CHANGES clause for that key field when the file is created. Otherwise, BASIC signals the error "Key not changeable" (ERR=130).

14.6.8 Controlling Record Access

When you open a file, BASIC allows you to specify how you will access the file and what types of access you will allow other running programs while you have the file open.

If you open a file for read access only (ACCESS READ), BASIC by default allows other programs to have unrestricted access to the file. You can restrict access with an ALLOW clause only if the file's security constraints allow you write access to the file.

BASIC by default prevents access by other programs to any file you open with ACCESS WRITE, ACCESS MODIFY, or ACCESS SCRATCH (sequential files only). This default action is equivalent to specifying the OPEN statement ALLOW NONE clause. To allow less restrictive access to the open file, specify ALLOW READ or ALLOW MODIFY.

When a file is open for read access only and you have not restricted access to other programs with ALLOW NONE, BASIC allows other programs to read any record in the file including records that your program is concurrently accessing. However, when you retrieve a record with the GET statement from a file you have opened with the intent to modify, BASIC normally restricts other programs from accessing that record. This restriction is called locking.

To allow other programs to access a record you have locked, you must release the lock on the record in one of the following ways:

In addition to the capability of restricting access through the OPEN statement ALLOW clause, BASIC allows programs to explicitly control record locking on each record that is retrieved. To use explicit record locking on a file, the OPEN statement must include an UNLOCK EXPLICIT clause. You may then optionally specify an ALLOW clause on the GET and FIND statements. The ALLOW clause on a GET or FIND statement specifies the type of access allowed by other programs to the record while you are accessing it. The following statement specifies that other programs may read but not modify the records you have locked:


GET #1, ALLOW READ 

If you specify UNLOCK EXPLICIT when opening a file, all records that you retrieve remain locked until you explicitly unlock them with a FREE, UNLOCK, or CLOSE statement.

14.6.9 Gaining Access to Locked Records

If you are trying to access a record that is currently locked, one possible solution is to use the REGARDLESS clause on the GET or FIND statement. The REGARDLESS clause is useful when you are interested in having only read access to the specified record. Be aware, however, that using the REGARDLESS clause to read a locked record can lead to unexpected results because the record you read can be in the process of being changed by another program.

Another solution is to include a WAIT clause on the GET or FIND statement. Note that you cannot specify a WAIT clause and a REGARDLESS clause on the same statement line. By specifying the WAIT clause, you can tell RMS to wait for a locked record to become available. You can optionally specify an integer expression from 0 to 255 with the WAIT clause. This integer expression indicates the number of seconds RMS should wait for a locked record to become available. If the record does not become available within the specified number of seconds, RMS signals the error "Keyboard wait exhausted" (ERR=15).

If you do not specify an integer expression with the WAIT clause, RMS waits indefinitely for the record to become available. Once the record becomes available, RMS delivers the record to the program.

Note that a deadlock condition can occur when you cause RMS to wait indefinitely for a locked record. A deadlock condition occurs when two users simultaneously try to access locked records in each other's possession. When a deadlock occurs, RMS signals the error, "RMS$_DEADLOCK". In turn, BASIC signals the error, "Detected deadlock error while waiting for GET or FIND" (ERR=193). To handle this error, you can either stop trying to access the particular record, or, if you must access the record, free all locked records (regardless of the channel) and then attempt the GET or FIND again. You need to unlock all records because you cannot know which record the other process wants.

Note

If the timeout value specified in the WAIT clause is less than the SYSGEN parameter DEADLOCK_WAIT, then a "Keyboard wait exhausted" (ERR=15) message can indicate that either the record did not become available during the specified time, or there is an actual deadlock situation. However, if the timeout value is greater than the SYSGEN parameter DEADLOCK_WAIT, the system correctly specifies that a deadlock situation has occurred.

The following example uses the WAIT clause to overcome a record locked condition and traps the resulting error condition:


MAP (worker) STRING first_name = 10,   & 
                    last_name = 20,    & 
                    badge_number = 6,  & 
             LONG   dept_number 
 
MAP (departments) STRING dept_name = 10,  & 
                  LONG   dept_code 
OPEN "Employee_data.dat" FOR INPUT AS FILE #1%,  & 
      INDEXED FIXED, MAP worker, ACCESS MODIFY,  & 
      PRIMARY badge_number 
 
OPEN "departments.dat" FOR INPUT AS FILE #2,         & 
     INDEXED FIXED, MAP departments, ACCESS MODIFY,  & 
     PRIMARY dept_code 
 
WHEN ERROR IN 
   WHILE -1% 
     GET #1, WAIT 
     WHEN ERROR USE time_expired_handler 
       GET #2%, KEY #0 EQ dept_number,  & 
           WAIT 10% 
     END WHEN 
     PRINT badge_number, dept_name 
   NEXT 
USE 
  SELECT ERR 
    CASE = 11% 
       PRINT "End of file reached" 
       CLOSE 1%, 2% 
    CASE = 193% 
       PRINT "Deadlock detected" 
       UNLOCK #2% 
       RETRY 
    CASE ELSE 
       EXIT HANDLER 
  END SELECT 
END WHEN 
 
 
 
HANDLER time_expired_handler 
  IF ERR = 15% OR ERR = 193% 
    THEN 
       PRINT "Department info not available for:" 
       PRINT "Employee ";badge_number 
       PRINT "Going on to next record." 
       CONTINUE 
    ELSE 
       EXIT HANDLER 
  END IF 
END HANDLER 
END PROGRAM 

The first WHEN ERROR block traps any deadlock conditions. The WHEN ERROR handler unlocks the current record on channel #2 in case another program is trying to access it and then retries the operation. The detached handler for the second WHEN ERROR block traps timeout errors and deadlock errors. If the desired information does not become available in the specified amount of time, or a deadlock condition occurs, the employee's badge number is printed out with an appropriate message, and the GET statement tries to retrieve the next record in the sequence.

14.6.10 Accessing Records by Record File Address

A Record File Address (RFA) uniquely specifies a record in a file. Accessing records by RFA is therefore more efficient and faster than other forms of random record access.1

Because an RFA requires six bytes of storage, BASIC has a special data type, RFA, that denotes variables that contain RFA information. Variables of data type RFA can be used only with the I/O statements and functions that use RFA information, and in comparison and assignment statements. You cannot print these variables or use them in any arithmetic operation. However, you can compare RFA variables using the equal to (=) and not equal to (<>) relational operators.

You cannot create named constants of the RFA data type. However, you can assign values from one RFA variable to another, and you can use RFA variables as parameters.

Accessing a record by RFA requires the following steps:

  1. Explicitly declare the variable or array of data type RFA to hold the address.
  2. Assign the address to the variable or array element. You can do this either with the GETRFA function, or by reading a file of RFAs generated by previous GETRFA functions or by the VMS Sort Utility.
  3. Specify the variable in the RFA clause of a GET or FIND statement.

The GETRFA function returns the RFA of the last record accessed on a channel. Therefore, you must access a record in the file with a GET, FIND, or PUT statement before using the GETRFA function. Otherwise, GETRFA returns a zero, which is an invalid RFA.

The following example declares an array of type RFA containing 100 elements. After each PUT operation, the RFA of the record is assigned to an element of the array. Once the RFA information is assigned to a program variable or array element, you can use the RFA clause on a GET or FIND statement to retrieve the record.


DECLARE RFA R_array(1 TO 100) 
DECLARE LONG I 
MAP (XYZ) STRING A = 80 
OPEN "TEST.DAT" FOR OUTPUT AS FILE #1,     & 
      SEQUENTIAL, MAP XYZ 
FOR I = 1% TO 100% 
   .
   .
   .
    PUT #1 
    R_array(I) = GETRFA(1%) 
NEXT I 

You can use the RFA clause on GET or FIND statements for any file organization; the only restriction is that the file must reside on a disk that is accessible to the node that is executing the program. An RFA value is only valid for the life of a specific version of a file. If a new version of a file is created, the RFA values might change. If you attempt to access a record with an invalid RFA value, BASIC signals a run-time error.

The following example continues the previous one. It randomly retrieves the records in a sequential file by using RFAs stored in the array.


DECLARE RFA R_array(1% TO 100%) 
DECLARE LONG I 
MAP (XYZ) STRING A = 80 
OPEN "TEST.DAT" FOR OUTPUT AS FILE #1,     & 
      SEQUENTIAL, MAP XYZ 
FOR I = 1% TO 100% 
   .
   .
   .
    PUT #1 
    R_array(I) = GETRFA(1%) 
NEXT I 
WHILE -1% 
    PRINT "Which record would you like to see"; 
    INPUT "(type a carriage return to exit)";Rec_num% 
    EXIT PROGRAM IF Rec_num% = 0% 
    GET #1, RFA R_array(Rec_num%) 
    PRINT A 
NEXT 

14.6.11 Transferring Data to Terminal-Format Files

The PRINT # statement transfers program data to a terminal-format file. In the following example, the INPUT statements prompt the user for three values: S_name$, Area$, and Quantity%. Once these values are entered, the PRINT # statement writes these values to a terminal-format file that is open on channel #4.


FOR I% = 1% TO 10% 
    INPUT "Name of salesperson":S_name$ 
    INPUT "Sales district";Area$ 
    INPUT "Quantity sold";Quantity% 
    PRINT #4%, S_name$, Area$, Quantity% 
NEXT I% 

If you do not specify an output list in the PRINT # statement, a blank line is written to the terminal-format file. A PRINT statement without a channel number transfers program data to a terminal. See Chapter 6 for more information.

14.6.12 Resetting the File Position

The RESTORE # statement resets the current record pointer to the beginning of the file; it does not change the file. RESET # is a synonym for RESTORE. For example:


RESTORE #3%, KEY #2% 
RESET #3% 

The RESTORE # statement restores the file in terms of the second alternate key. The RESET # statement restores the file in terms of the primary key.

The RESTORE # statement can be used by all RMS file organizations. RESTORE without a channel number resets the data pointer for READ and DATA statements but does not affect any files.

14.6.13 Truncating Files

The SCRATCH statement is valid only on sequential files. Although you cannot delete individual records from a sequential file, you can delete all records starting with the current record through to the end of the file. In order to do this, you must first specify ACCESS SCRATCH when you open the file.

To truncate the file, locate the first record to be deleted. Once the current record pointer points to this record, execute the SCRATCH statement. The following program locates the thirty-third record and truncates the file beginning with that record.


OPEN "MMM.DAT" AS FILE #2%,            & 
      SEQUENTIAL FIXED, ACCESS SCRATCH 
 
first_bad_record = 33% 
 
FIND #2%, RECORD first_bad_record 
SCRATCH #2% 
CLOSE #2% 
END 

SCRATCH does not change the physical size of the file; it reduces the amount of information contained in the file. (You can use the DCL command SET FILE/TRUNCATE to truncate the excess file space.) Therefore, you can write records with the PUT statement immediately after a SCRATCH operation.

14.6.14 Renaming Files

If the security constraints permit, you can change the name or directory of a file with the NAME...AS statement. For example:


NAME "MONEY.DAT" AS "ACCOUNTS.DAT" 

This statement changes the name of the file MONEY.DAT to ACCOUNTS.DAT.

Note

The NAME...AS statement can change only the name and directory of a file; it cannot be used to change the device name.

You must always include an output file type because there is no default. If you use the NAME...AS statement on an open file, the new name does not take effect until you close the file.

14.6.15 Closing Files and Ending I/O

All programs should close files before the program terminates. However, files are automatically closed in the following situations:

Files are not closed after executing a STOP, END SUB, END FUNCTION, or END PICTURE statement.

The CLOSE statement closes files and disassociates these files and their buffers from the channel numbers. If the file is a magnetic tape device and the data is written to a tape, CLOSE writes trailer labels at the end of the file. The following is an example of the CLOSE statement:


CLOSE #1% 
B% = 4% 
CLOSE #2%, B%, 7% 
CLOSE I% FOR I% = 1% TO 20% 

14.6.16 Deleting Files

If the security constraints permit, you can delete a file with the KILL statement. For example:


KILL "TEST.DAT" 

This statement deletes the file named TEST.DAT. Note that this statement deletes only the most current version of the file. Do not omit the file type, because there is no default. You can delete only one file at a time; to delete all versions of a file matching a file specification, use the Run-Time Library routine LIB$DELETE_FILE.

You can delete a file that is currently being accessed by other users; however, the file is not deleted until all users have closed it. You cannot open or access a file once you have deleted it.

Note

1 Record File Addresses do not exist for terminal-format files.

14.7 File-Related Functions

The following built-in functions are provided for finding:

These functions are discussed in the following sections.

14.7.1 FSP$ Function

If you do not know the organization of a file, you can find out by opening the file for input with the ORGANIZATION UNDEFINED and RECORDTYPE ANY clauses. Your program can then use the FSP$ function to determine the characteristics of that file. Your program must execute FSP$ immediately after the OPEN FOR INPUT statement. For example:


RECORD FSP_data 
       VARIANT 
       CASE 
          BYTE Org 
          BYTE Rat 
          WORD Max_record_size 
          LONG File_size 
          WORD Bucketsize_blocksize 
          WORD Num_keys 
          LONG Max_record_number 
       CASE 
          STRING Ret_string = 16 
       END VARIANT 
END RECORD 
 
DECLARE FSP_data File_chars 
 
OPEN "FIL.DAT" FOR INPUT AS FILE #1%,              & 
    ORGANIZATION UNDEFINED,                        & 
    RECORDTYPE ANY, ACCESS READ 
File_chars::Ret_string = FSP$(1%) 

The following list explains the above example:

Note that FSP$ returns zeros in bytes 9 to 12. For more information, see the OpenVMS Record Management Services Reference Manual.


Previous Next Contents Index