This section is devoted to the remaining file statements. These statements allow you to create new records, remove old records, and change existing records in place. Each statement is presented individually, showing how each one works by itself. Then, at the end of this section, a transaction processing program for the demo system is presented.
The first statement is the UPDATE statement. With this statement, you can change the contents of any record in an RMSfile. Before you can change the record, you must first read it. This can be done with the FIND statement, or either of the file reading statements: FOR or READ. After the record is read, you can make changes to the fields in the file. Once all of the changes are made, execute UPDATE to store the changes into the RMSfile. This is very important. If UPDATE is not executed, the changes to the fields are not stored into the RMSfile. The following statements find the magazine with the code "sd" and change the title to "Science Digest".
FIND IN mag
WHERE magazine EQ "sd"
title := "Science Digest"
UPDATE mag;
These statements make several assumptions, not all of which are necessarily true. The first is that the FIND actually finds what we are looking for. As long as the record exists in the RMSfile, this should be the case. If we need to be sure, we can check to see if any errors existed for the file mag. This would tell us if we found the record. The other assumption made is that the update process actually worked. Again, we have to check for errors to be sure of that.
File operation errors are returned to a report program through the ERROR function. This function is similar to the end of file function from the previous section. A call to ERROR looks like.
ERROR ( fname )
where fname is an internal file name declared at the beginning of the report program. This function returns zero (0) if no errors occurred during the file operation. If an error did occur, the ERROR function returns the error number from RMS.
If we were to incorporate error checking into our example above, we might have something like
FIND IN mag
WHERE magazine EQ "sd"
IF ERROR (mag) NE 0 THEN
PRINT "No magazine to update", NL;
ELSE BEGIN
title := "Science Digest"
UPDATE mag;
IF ERROR (mag) NE 0 THEN
PRINT "Update failed", NL;
END
As you can see, error checking tends to make the program much more complex. One thing can be done to make things a little simpler. If you re-check Chapter 10, Report Writer Programming Reference, it states that a condition is false if the result is zero and assumed to be true for any other values. It just so happens the ERROR returns a zero (false) if no errors have occurred and something non-zero (true) if there were errors (actually, it was planned that way). This means that we can simplify our conditions to just checking the value returned by ERROR. This yields the following statements:
FIND IN mag
WHERE magazine EQ "sd"
IF ERROR (mag) THEN
PRINT "No magazine found", NL;
ELSE BEGIN
title := "Science Digest"
UPDATE mag;
IF ERROR (mag) THEN
PRINT "Update failed", NL;
END
The next statement on our list is DELETE. With this statement, you can remove records from an RMSfile. As with UPDATE, you must first read the record before you can remove it. Again, any of the file reading statements can be used to locate the desired record. The following statements remove the magazine whose code is "sd":
FIND IN mag
WHERE magazine EQ "sd"
DELETE mag;
This example makes the same assumptions that the first example on UPDATE made. The error-checking version of this looks like
FIND IN mag
WHERE magazine EQ "sd"
IF ERROR (mag) THEN
PRINT "Magazine not found", NL;
ELSE BEGIN
DELETE mag
IF ERROR (mag) THEN
PRINT "Delete failed", NL;
END
The final file statement is INSERT. This statement is used to add new records to an RMSfile. Before this statement can be executed, you must assign each field its new value. When INSERT executes, it stores the contents of the fields into the RMSfile in a new record. (Note: The Report Writer does not enforce uniqueness of key values. If this is required (as in a master file) it is up to the report program to insure this. The following statements add a new magazine with the code "sd", and title of "Science Digest":
magazine := "sd"
title := "Science Digest"
INSERT mag;
Error conditions were ignored in this example. Checking for errors results in the following statements:
magazine := "sd"
title := "Science Digest"
INSERT mag;
IF ERROR (mag) THEN
PRINT "Insert failed.", NL;
Some RMSfiles are set up with a fixed number of records that can be stored in the file. If you try to add a record to one of these RMSfiles and the file already contains its allocated number of records, you will get an error which means "file is already full". The function FILEFULL has been provided to eliminate the need for a report to test for a particular error number. As with the other file functions, this function also requires an internal file name as its argument. The value returned by FILEFULL is true if the RMSfile was full before the insert and false otherwise. The following statements illustrate the use of this function:
magazine := "sd"
title := "Science Digest"
INSERT mag;
IF FILEFULL (mag) THEN
PRINT "Magazine file full.", NL;
ELSE IF ERROR (mag) THEN
PRINT "Insert failed.", NL;
Now that you have seen how each of these statements is used individually, a complete example is presented that uses all of the input-output statements together. To do this, we must go outside of the demo system a little. On top of the demo system, we will add a transaction processing system. In this system, the user creates a transaction file which gets applied to the master file. These transaction files have the same format as the master files except that we have added a field that indicates what kind of transaction the record represents. This transaction code has three values:
"I" - Insert the record into file
"U" - Update the record contents
"D" - Delete the record from the file
The report uses two files: the master file, and the transaction file. The declarations for these files are:
FILE master IS "sub"
FILE tr IS "sub_tr"
FIELDS IN master ARE
subscriber name address,
city, state, zip;
FIELDS IN tr ARE
tr_code, subscriber, name,
address city, state
zip;
The main process of the report reads through the transaction file and processes each transaction. Our first try at the main procedure looks like:
MAIN
BEGIN
FOR EACH tr
IF tr_code EQ "I" THEN
DO tr_insert;
ELSE IF tr_code EQ "U" THEN
DO tr_ update;
ELSE IF tr_code EQ "D" THEN
DO tr_delete;
ELSE
PRINT "Invalid transaction code:",
tr_code, NL;
END
The sequence of statements IF ...ELSE IF is a way of making a multiway decision. In this case, we are going through a series of tests looking for a valid transaction code. When we find a valid code, we call the appropriate procedure. If we get to the bottom of the ELSE IF chain, the transaction code is invalid and we print out the error.
Now imagine this program running. You have a file of 5000 transactions being applied to the master file. Somewhere in this file, the operator has made a mistake in entering the transaction code and entered a "F" instead of a "D" (they are close on the keyboard). The error that is displayed is:
Invalid transaction code :F
It is now up to the operator to search through the 5000 transactions to find the one that has the code "F". It would be more helpful if we somehow tell the operator where the error is. One simple way is to give a transaction number. Such an error message might read
Invalid transaction code :F, transaction # 2409.
Now the operator knows exactly which transaction to change. Keeping track of the transaction number requires that we create a variable which acts as a transaction counter. Thus our new main procedure is
MAIN
BEGIN
tr_num := 1;
FOR EACH tr BEGIN
IF tr_code EQ "I" THEN
DO tr_insert;
ELSE IF tr_code EQ "U' THEN
DO tr_update;
ELSE IF tr_code EQ "D" THEN
DO tr_delete;
ELSE BEGIN
PRINT "Invalid transaction code:",
tr_code;
DO print_tr;
END
tr_num := tr_num + 1;
END
END
We are now ready to move on to the remaining procedures. In each of these procedures, we assume that the transaction record contains the new image of what is to be stored in the data file. Since both the update and insertion procedures need to move this data into the master file fields, we have created another procedure, called move_to_master, to move the fields. This procedure looks like
PROCEDURE move_to_master /* move fields to master file */
BEGIN
master.subscriber := tr.subscriber;
master.name := tr.name;
master.address [1] := tr.address [1];
master.address [2] := tr.address [2];
master.city := tr.city;
master.state := tr.state;
master.zip := tr.zip;
END
The code for insertions must do the following:
Check to make sure the subscriber code is unique.
Move the fields to the master file.
Add the record to the data file.
Check for insertion errors.
To check for uniqueness, we can try to find the subscriber code we want to add. If we do manage to find the record in the master file, then the subscriber code is non-unique. This check might look something like:
FIND IN master
WHERE subscriber EQ tr.subscriber;
IF ERROR (master) EQ 0 THEN BEGIN
PRINT "Subscriber '", master.subscriber,
"' already exists"
DO print_tr;
END
Moving the fields to the master file is a procedure call to move_to_master. Executing the INSERT statement adds the record to the master file. When checking for errors, we should make two checks. The first check is for "file full". This gives a little more meaningful error as to why an insert transaction failed. The second check can be for other errors in general. In summary, our procedure for insertions is:
PROCEDURE tr_insert /* add new subscriber */
BEGIN
FIND IN master
WHERE subscriber EQ tr.subscriber;
IF ERROR (master) EQ 0 THEN BEGIN
PRINT "Subscriber '", master. subscriber,
"' already exists"
DO print_tr;
END
ELSE BEGIN
DO move_to_master;
INSERT INTO master;
IF FILEFULL (master) THEN BEGIN
PRINT "File full on subscriber`",
master.subscriber;
DO print_tr;
END
ELSE IF ERROR (master) THEN BEGIN
PRINT "Insert failed"
DO print_tr;
END
END
END
The procedures for updates and deletions are similar to insertions. However, in these procedures it is an error if the transaction subscriber cannot be found in the master file. (Cannot change or remove a record if it is not there.) The full program follows:
/* process subscriber transaction file */
FILE master IS "sub"
FILE tr IS "sub_tr"
FIELDS IN master ARE
subscriber, name, address,
city, state, zip;
FIELDS IN tr ARE
tr_code, subscriber, name,
address, state, city,
zip;
VARIABLE IS
tr_num; /* transaction counter */
MAIN
BEGIN
FOR EACH tr BEGIN
IF tr_code EQ "I" THEN
DO tr_insert;
ELSE IF tr_code EQ "U" THEN
DO tr_update;
ELSE IF tr_code EQ "D" THEN
DO tr_delete;
ELSE BEGIN
PRINT "Invalid transaction code:",
tr_code;
DO print_tr;
END
tr_num := tr_num + 1;
END
END
PROCEDURE tr_insert /* add new subscriber */
BEGIN
FIND IN master
WHERE subscriber EQ tr.subscriber;
IF ERROR (master) EQ 0 THEN BEGIN
PRINT "Subscriber `", master.subscriber,
"' already exists"
DO print_tr;
END
ELSE BEGIN
DO move_to_master;
INSERT INTO master;
IF ERROR (master) THEN BEGIN
PRINT "Insert failed"
DO print_tr;
END
END
END
PROCEDURE tr_update /* change old subscriber */
BEGIN
FIND IN master
WHERE subscriber EQ tr.subscriber;
IF ERROR (master) THEN BEGIN
PRINT "Subscriber `", master.subscriber,
"' not found"
DO print_tr;
END
ELSE BEGIN
DO move_to_master;
UPDATE IN master;
IF ERROR (master) THEN BEGIN
PRINT "Update failed"
DO print_tr;
END
END
END
PROCEDURE tr_delete /* remove old subscriber */
BEGIN
FIND IN master
WHERE subscriber EQ tr.subscriber;
IF ERROR (master) THEN BEGIN
PRINT "Subscriber `", master.subscriber,
"' not found"
DO print_tr;
END
ELSE BEGIN
DELETE FROM master;
IF ERROR (master) THEN BEGIN
PRINT "Delete failed"
DO print_tr;
END
END
END
PROCEDURE move_to_master /* move fields to master file */
BEGIN
master.subscriber := tr.subscriber;
master.name := tr.name;
master.address [1] := tr.address [1];
master.address [2] := tr.address [2];
master.city := tr.city;
master.state := tr.state;
master.zip := tr.zip;
END
PROCEDURE print_tr /* print transaction number */
BEGIN
PRINT ", transaction # ", tr_num, NL;
END