Skip to content

VSAM Processing in APL

Vector 5:1, page 104

data and files system interfaces

Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 104

This article is not intended to be an exhaustive account of how to process every type of VSAM file and every requirement that a user may have when using those files. Rather, it is a case-study, designed to show you, the gentle reader, how easy VSAM file processing can be, and why you should not be frightened of it.

For various reasons that need not concern us here, the functions and programs used in the application covered by the case-study are deliberately somewhat restrictive, in that key-lengths are hard-coded into some of the cover functions rather than being held in global variables, and several operations that are normally permitted in general are not permitted in this application (such as file-updates via the alternate index). It is not intended that you should impose the same restrictions on your application, but rather this case-study is intended to guide you to how to use VSAM in the best way for your needs.

Background

In this article I shall be referring to other languages in addition to APL, these are FORTRAN (because I am going to include some FORTRAN code to be called via Associated Processor 11 in the application) and PL/1 (because my VSAM file has to be read by PL/1 in addition to APL). Please bear with me if the parts dealing with these languages are unintelligible to you, because they may be useful to others.

For those of you who remember the article by David Piper in Vector 3.2, covering VSAM file processing in a more general way, I should state that (a) this system was written long before David’s article appeared and (b) this system is specific to the VSAM file being used, whereas David’s article covered more general concepts. However, I am currently investigating the possibilities of an extremely general set of VSAM processing functions which will combine all of the best points of IBM’s supplied workspaces and David Piper’s concepts, and it will have a syntax exceedingly similar to another filing system well-known to the APL world. This new system will be described in a future issue of Vector, with the editor’s permission.

VSAM concepts

This article is only concerned with VSAM key sequenced data sets (KSDSs) which contain variable-length records that are accessed by means of one or more keys embedded in the record. The length of an existing record can be changed when it is rewritten. Alternate indexes can be provided for key-sequenced files: their provision makes processing much easier in some circumstances, as explained later.

The VSAM file discussed here contains the following: a ‘base cluster’ containing a data component and an index component; an ‘alternate index cluster’ also containing data and index components; an ‘alternate index path’ which connects the alternate index to the base cluster. The keys in the base cluster must be unique whereas the alternate index keys do not have to be unique (in this application it is essential that they are not).

Application Requirements

There is a requirement to do the following things in the application:

Close a file (base cluster or alternate index)
Open a file for input
Open a file for updates (base cluster only, although you can also do this for alternate index in the general case)
Read a record from the file by specifying the whole of the primary key
Read a record from the file by specifying only the first few bytes of the primary key
Read a record from the file by means of the alternate index
Read the record immediately following the one you just read
Read the record immediately preceding the one you just read
Write a completely new record to the file
Read a record from the file, amend it and replace it on the file

All of these things can be done in PL/1, although activities can become complicated if you want to do them all, because each alternate-index path needs a separate filename, and you can only read a file in one direction (forwards or backwards), not both. Things are easier in FORTRAN, although you might have some problems with varying-length records and different datatypes in the same record. For FORTRAN a maximum of 8 alternate indexes can be used at any one time; although each alternate index needs a separate file all of them are accessed by the same unit number as the base cluster, and you can read in both directions on the same file. APL will not let you read the file backwards, which is a nuisance, although it is possible to simulate a backwards sequential read by means of FORTRAN subroutines executed through Associated Processor 11. I can see no real reason for not allowing backwards reading in APL: maybe IBM will make this feature available in future releases of the program product.

Setting up the VSAM file

I used the CLIST shown below to set up my VSAM KSDS. You could, of course translate this into JCL, if you really wanted to. This is the basic CLIST; add your own bells and whistles for error-handling.

PROC 0
CONTROL MAIN LIST MSG

DEFINE +
   CLUSTER  (NAME('PROJECT.VSAMFILE') OWNER(USERID) +
            VOLUME(VOLSER) SHR(4) KEYS(32 0) +
            RECSZ(8192 32600) IMBED REPL FSPC(0 0) SPND) +
   INDEX (NAME('PROJECT.VSAMFILE.INDEX') CYLINDERS(25)) +
   DATA  (NAME('PROJECT.VSAMFILE.DATA') CYLINDERS(250)) +
   CATALOG ('USERCAT')
IF &LASTCC ¬= 0 THEN EXIT

REPRO +
   INDATASET(TEMP.VSAMFILE.DATA) +
   OUTDATASET('PROJECT.VSAMFILE')
IF &LASTCC ¬= 0 THEN EXIT

DEFINE +
   AIX      (NAME('PROJECT.VSAMFILE.AIX') OWNER(USERID) +
             RELATE('PROJECT.VSAMFILE') +
             VOLUME(VOLSER) SHR(4) KEYS(8 9) +
             RECSZ(512 32600) REPL IMBED FSPC(0 0)) +
   INDEX    (NAME('PROJECT.VSAMFILE.AIX.INDEX') CYLINDERS(3)) +
   DATA     (NAME('PROJECT.VSAMFILE.AIX.DATA') CYLINDERS(22)) +
   CATALOG ('USERCAT')
IF &LASTCC ¬= 0 THEN EXIT

/* BUILD ALTERNATE INDEX FOR NEW VSAMFILE
BLDINDEX +
   INDATASET('PROJECT.VSAMFILE') +
   OUTDATASET('PROJECT.VSAMFILE.AIX')
IF &LASTCC ¬= 0 THEN EXIT

/* DEFINE ALTERNATE INDEX PATH
DEFINE +
   PATH (NAME('PROJECT.VSAMFILE.PATH') OWNER(USERID) +
   PATHENTRY('PROJECT.VSAMFILE.AIX'))

After setting up the VSAM base cluster but before constructing the alternate index(es) you have to put some data into the file. This process of putting data into the empty VSAM file is known as loading, and you stay in the loading state until you close the file. You can do the loading in a number of ways, either by yourself under some kind of program control (using any language that will handle VSAM), or by using the IBM utility REPRO, which is what I did. Whichever method you use, your data MUST be presented in ascending sequence of primary key.

If you decide to load the file yourself, the following points should be noted:

  • You MUST open the file for writing (Service request OW)
  • You cannot read or erase a record (Service request R or E) while you are loading.
  • All the records to be dealt with must be in ascending order of primary key.
  • Once you have loaded data into the file you cannot (and do not need to) repeat the process. Therefore you could load a single record consisting completely of blanks (large enough to hold the key and all of the relevant alternate indexes, of course) and then delete it at the first available opportunity. This is what happens implicitly in FORTRAN if you open a new VSAM KSDS with an action of READWRITE.

If you decide to use REPRO, your input data should be in a varying-length sequential dataset, sorted into ascending order of the keys. REPRO is one of IBM’s Access Method Services commands so it is very quick.

You must repeat the DEFINE AIX, BLDINDEX and DEFINE PATH commands for each alternate index that you want to set up. You will see that I have set up just one alternate index with an eight-byte key starting at offset 9 (position 10) in the record. You can define your alternate index keys to start anywhere as long as each record is actually long enough to hold the whole of the alternate key.

Accessing the VSAM file

Before doing anything you have to allocate the file and any alternate indexes so that you have a means of referring to them in your programs. This can be done outside APL in a CLIST, or it can be done in the workspace latent expression or a function called by the latent expression. With TSO’s ALLOC command you use the cluster name for the base cluster, and the pathname for the alternate index, in the dataset-name parameter.

When you use Auxiliary Processor 123 you will have to share one pair of variables for each index that you wish to access. (You could do everything through a single pair of variables, but it would give rise to a lot of wasted time, opening and closing files, when you want to read different alternate indexes in ‘random’ order.) APL imposes a maximum of 14 pairs of variables to be shared (and hence files to be open) at any one time. All the pairs of shared variables should be global in the workspace.

There are some rules of thumb that should be followed when writing the cover functions:

  • Make your file names logical, so that the programming becomes simpler. For instance, if you have 9 alternate indexes, call their filenames AI1, AI2, …, AI8, AI9.
  • Make your shared-variable names logical for the same reason.

In the example, the base cluster is allocated to file VSAMBC, the alternate index to VSAMAI: if I had more than 1 alternate index I would have called them VSAMAI1, VSAMAI2, etc; and if all files were unrestricted I would have named them differently, but using the same ideas, for example, VSAM0 for the base cluster, VSAM1, VSAM2, etc., for the alternate indexes.

Similarly, the shared variables are CTLb and DATb for the base cluster and CTLa and DATa for the alternate index; other schemes are possible as shown above with filenames.

The functions used in the application are

aiCLOSE
Closes the alternate index
aiGET
Reads a record from the alternate index, given the key. In theory, you can read the alternate index using a key of any length up to the keylength that you used when the alternate index was defined. In my case I have insisted that the user must specify the full 8 bytes for the alternate index key.
aiREAD
Sequentially reads a record from the alternate index in the forwards direction.
aiROPEN
Opens the alternate index for read only
bcBKWD
Sample function to read the base cluster backwards. Because this is a sample function it is not very fast.
bcCLOSE
Closes the base cluster
bcDELETE
Delete a record from the file via the base cluster. The whole of the primary key must be supplied. File must be open for update.
bcGET
Reads a record from the base cluster given the key. The key can be any length between 1 and the keylength defined when the file was set up
bcKF
Returns the key of the previous successful operation
bcPUT
Writes a record to the file through the base cluster. File must be open for update. This function combines the effects of bcUGET and bcUPUT.
bcREAD
Sequentially reads a record from the base cluster in the forwards direction
bcREPLACE
Replaces a record in the file, which must be open for update
bcROPEN
Open the base cluster for read only
bcUGET
As for bcGET, but uses service RU instead of R. File must be open for update
bcUOPEN
Open the base cluster for update
bcUPUT
Write a record to the file through the base cluster. File must be open for update and previous operation must have been a read-for-update (bcUGET).
dbALLOC
Allocate and open the VSAM base cluster and alternate index. The relevant filenames are held as elements in nested vector of vectors <dbname>.
SETUP_VSAMerc
Sets up the VSAM error-code matrix. Run once then forget it.
VSAMchk
Checks the return code from a VSAM request.

Reading the file backwards

As mentioned above, APL will not allow you to read a VSAM file sequentially backwards. Sometimes, you need this facility and its absence from the APL system can be rather frustrating. It is possible to read the file backwards, however, by using FORTRAN, although the method is cumbersome, and unless you are going to write specific code for each eventuality for the data in your file it is messy.

What I have done is written a suite of 4 subroutines to open the file, close it, read it via the primary key, and read it sequentially backwards, each read operation reading ONLY the 32-byte primary key. When enough of these keys have been saved in the workspace you can then use APL to read the relevant records by means of these keys. (I did say it was messy, didn’t I?) You can, of course, write your subroutines to do the same with an alternate index if you so desire.

The procedure is as follows:

  • Close the base cluster and any alternate indexes in APL, which has the effect of writing all amendments from the buffers to the file.
  • Open the base cluster (only) in FORTRAN. In FORTRAN read the record from which you want the backwards reading to start, by supplying the subroutine with the key. This is done in order to position the file pointer.
  • Now backspace the file twice (once to position at the record you’ve just read and once to move in front of it). Read the file sequentially (i.e. without specifying a key), returning the key of this record to the workspace.
  • Repeat the last step as often as required until your ending condition is satisfied
  • Close the file in FORTRAN; reopen the base cluster and alternate indexes in APL
  • Continue …

The FORTRAN subroutines are listed in Appendix 2, and the relevant cover function is amongst the functions in Appendix 1 as bcBKWD.

Points to note

When using the alternate indexes you should note that you could get into trouble if you delete records using the alternate indexes and the base cluster is still open. You should close the base cluster, do your deletions, close and if necessary reopen the alternate index (this makes sure all the deletions are done); then and only then should you reopen everything. Of course, deleting via the base cluster doesn’t give you this trouble. In my application we decided that there was no real reason for wanting to delete records via the alternate index, so we didn’t include the facility in the cover functions.

You can use any of the pairs of shared variables to add new records to the file, since you have to supply the whole record in any case. If a duplicate key in any of the indexes would result where it is not allowed, you get the usual error message. This is particularly useful if you want to read through all the records with the same nonunique key in an alternate index and then update some of the records (remember with the file open for update, a write request after a RU request will replace the record just read with the new one. This applies no matter which index you used to obtain the record. In my application we decided that there was no real reason for wanting to update via the alternate index, so we didn’t include the facility in the cover functions.

Sometimes your VSAM file will run out of space, and further space will have to be allocated. The method is simple. First of all you use REPRO to make a backup sequential copy of your file, making sure that this has completed correctly before continuing. Then you delete the base cluster (this actually deletes all of the files connected with the base cluster) and run the setup CLIST after increasing the space allocations and making sure that the REPRO step is copying your backup dataset back into the VSAM file. I have done this several times and have had no bother, but be patient since BLDINDEX can take a fair length of time when you have a big file. System failures may cause grief if they occur during this process so do not delete your backup file until everything is O.K. (you can usually delete any half-complete alternate index and pick up again from where the CLIST crashed, which should save some time).

You can also use REPRO to take extracts of the file to give to other people. You can use the PRINT command to obtain a dump of selected parts of the file for documentation and/or debugging purposes. See the Access Methods Services documentation for more details.

Why do you need an alternate index? Consider a simple VSAM file containing the names of your company’s sales force. Fields contain payroll number, department, territory covered, sales target, salary, commission, name, address, telephone number(s) – you know the sort of thing that people delight in using for examples. The obvious candidate for the unique primary key is the payroll number. Having set this up, get me a list of all salesmen in the Greater London area without searching the whole file. Or all of those with a salary of £25,000 or more. Of course, this file is small, so reading it in total should not provide too much of a problem with unnecessary processing. But consider a file containing a million records (different contents, of course, unless your company employs 2% of the total population of the U.K.!) and an extract request that results in only a dozen records being selected. That’s why you need (multiple) alternate indexes.

Why do you need a VSAM file? I don’t know, that’s up to you to decide. VSAM is easier than some other filing systems, and easier to handle than most people think, but like all filing systems it has its good points and its bad ones. I started experimenting with VSAM when APL2 arrived with its concept of libraries stored in VSAM files, and when I discovered that there was nothing to be scared of it was natural to add VSAM to the list of problem-solving techniques.

Appendix 1: Function Listings

[⍝]∇ aiCLOSE.3  ⍴: 9  1986-07-23 13.23.19
[0]    aiCLOSE;RC
[1]   ⍝ Close the currently open alternate index
[2]    CTLa←⍳0                   ⍝ Try empty vector - test for syntax error
[3]    →(0=⍴RC←CTLa)/0           ⍝ Exit if no shared variables
[4]    →(∧/12 100012≠100000⊥RC)/∆1
[5]    CTLa←'C'                  ⍝ Otherwise try setting a 'C' for CLOSE
[6]    RC←CTLa
[7]   ⍝ Ignore VSAM return code for file not open
[8]   ∆1:100022 VSAMchk RC

[⍝]∇ aiGET.3  ⍴: 9  1986-07-23 13.24.00
[0]    Z←aiGET KEY
[1]   ⍝ Reads a record from the database alternate index, given the key.
[2]   ⍝ The file must be open and the key must be correct.
[3]    ⎕ES(8≠⍴KEY)/'Key for alternate index must be 8 bytes long'
[4]    CTLa←'R:',KEY             ⍝ Issue GET request
[5]   ⍝ Exit with null vector if not found
[6]    ⍎(∨/(8,[1.1]4 16)∧.=2↑Z←CTLa)/'→⍴Z←'''''
[7]    VSAMchk Z                 ⍝ Check return code
[8]    Z←DATa                    ⍝ Get data variable if return code OK

[⍝]∇ aiREAD.3  ⍴: 5  1986-07-23 13.24.16
[0]    Z←aiREAD
[1]   ⍝ Sequentially reads a record from the alternate index
[2]    CTLa←'R'
[3]    VSAMchk CTLa
[4]    Z←DATa

[⍝]∇ aiROPEN.3  ⍴: 15  1986-07-23 13.25.08
[ 0]   aiROPEN;A;N
[ 1]  ⍝ Open alternate index for READ only (Cannot open for update)
[ 2]   aiCLOSE                   ⍝ Close any already-open file
[ 3]  ⍝ If variables already shared then don't do them again
[ 4]   →(2∧.=⎕SVO¨N←'CTLa' 'DATa')/∆1
[ 5]   A←⎕SVR¨N                  ⍝ Might not be shared properly so retract
[ 6]   ⎕ES(2∨.≠123 ⎕SVO¨N)/'Unable to share with AP123'
[ 7]   A←0 0 1 1 ⎕SVC 1⊃N        ⍝ Set access control
[ 8]  ∆1:CTLa←'OR VSAMAI'        ⍝ Open VSAM file for read only
[ 9]   100013 VSAMchk A←CTLa
[10]   CTLa←'R'                  ⍝ Sequential read of first item
[11]   VSAMchk A←CTLa
[12]   CTLa←'KF'                 ⍝ Key of first item
[13]   VSAMchk A←CTLa
[14]   aiKEYLEN←⍴aiFIRSTKEY←DATa ⍝ Store key length, first key

[⍝]∇ bcBKWD.3  ⍴: 25  1988-01-25 15.28.16
[ 0]   Z←bcBKWD B;A;P;Q;T;X
[ 1]  ⍝ Read the database base cluster backwards
[ 2]  ⍝ Care: this routine loses positioning on BC and AI
[ 3]  ⍝ Return the null vector if cannot do it, else return the record
[ 4]   DECLARE CHARACTER VECTOR SIMPLE B
[ 5]   ⎕ES(32≠⍴B)/5 3            ⍝ Length error if B not 32 bytes
[ 6]   K←'APL DDI FT03K01'       ⍝ File already allocated?
[ 7]   →(8≠↑K)/∆2,Z←⍳0           ⍝ Yes, so bypass ALLOC statement
[ 8]   K←'ALLOC DD(FT03K01) DS(''',(1⊃dbname),''') SHR'
[ 9]   →(0≠K)/0                  ⍝ Exit if unable to allocate
[10]  ⍝ Database allocated for FORTRAN. So far so good.
[11]  ∆2:bcCLOSE                 ⍝ Close database for APL
[12]   aiCLOSE                   ⍝ ... and alternate index
[13]   →(0≠FVOPEN 0 3)/0         ⍝ Open for FORTRAN, exit if error
[14]  ⍝ Try to read record for initial positioning purposes
[15]  ⍝ If this fails there is an error somewhere
[16]   →(0≠↑A←FVGET 0 3 B(32↑' '))/∆CLOSE
[17]   P←2⊃A                     ⍝ Save it for comparison purposes
[18]   A←FVRDBK 0 3(32↑' ')      ⍝ Backwards read
[19]  ∆CLOSE:T←FVCLOS 0 3        ⍝ Close in FORTRAN
[20]   bcROPEN                   ⍝ Reopen in APL
[21]   aiROPEN                   ⍝ ... and alternate index
[22]   →(0≠↑A)/0                 ⍝ If error then quick quit
[23]   →(P≡Q←2⊃A)/0              ⍝ If equal then cannot read backwards
[24]   Z←bcGET Q                 ⍝ Return record as promised

[⍝]∇ bcCLOSE.3  ⍴: 9  1986-07-23 12.43.28
[0]    bcCLOSE;RC
[1]   ⍝ Close the currently open base cluster
[2]    CTLb←⍳0                   ⍝ Try empty vector - test for syntax error
[3]    →(0=⍴RC←CTLb)/0           ⍝ Exit if no shared variables
[4]    →(∧/12 100012≠100000⊥RC)/∆1
[5]    CTLb←'C'                  ⍝ Otherwise try setting a 'C' for CLOSE
[6]    RC←CTLb
[7]   ⍝ Ignore VSAM return code for file not open
[8]   ∆1:100022 VSAMchk RC

[⍝]∇ bcDELETE.3  ⍴: 12  1986-08-13 15.03.14
[ 0]   Z←bcDELETE KEY;R
[ 1]  ⍝ Deletes a record on the file
[ 2]  ⍝ The file must be open and the key must be correct.
[ 3]  ⍝ Returns 1 for record deleted, 0 otherwise
[ 4]   CTLb←'R:',KEY             ⍝ Issue GET request
[ 5]   →(∨/(8,[1.1]4 16)∧.=2↑R←CTLb)/~Z←1 ⍝ Not found
[ 6]   VSAMchk R                 ⍝ Check return code
[ 7]   →(KEY∧.=(⍴KEY)↑Z←DATb)/∆1
[ 8]   →Z←0                      ⍝ Error reading file so return
[ 9]  ∆1:CTLb←'E:',KEY           ⍝ Erase record
[10]   VSAMchk R←CTLb            ⍝ Check return code
[11]   Z←1                       ⍝ All is OK

[⍝]∇ bcGET.3  ⍴: 8  1986-08-28 15.04.07
[0]    Z←bcGET KEY
[1]   ⍝ Reads a record from a file, given the key.
[2]   ⍝ The file must be open and the key must be correct.
[3]    CTLb←'R:',KEY             ⍝ Issue GET request
[4]   ⍝ Exit with null vector if not found
[5]    ⍎(∨/(8,[1.1]4 16)∧.=2↑Z←CTLb)/'→⍴Z←'''''
[6]    VSAMchk Z                 ⍝ Check return code
[7]    Z←DATb                    ⍝ Get data variable if return code OK

[⍝]∇ bcKF.3  ⍴: 7  1987-12-11 10.27.21
[0]    Z←bcKF;I
[1]    I←1
[2]   ∆1:CTLb←'KF'
[3]    ⍎(0∧.=CTLb)/'→0,⍴Z←DATb'
[4]    ⍎(2<I←I+1)/'→⍴Z←'''''
[5]    ⍎(0=⍴bcGET 1↑⎕AV)/'Z←bcREAD'
[6]    →∆1

[⍝]∇ bcPUT.3  ⍴: 11  1986-07-23 13.26.13
[ 0]   bcPUT X;Q;Z
[ 1]  ⍝ Puts a record on the file
[ 2]  ⍝ The file must be open and the key must be correct.
[ 3]   CTLb←'RU:',bcKEYLEN↑X     ⍝ Issue GET request
[ 4]   →(∨/(8,[1.1]4 16)∧.=2↑Z←CTLb)/∆1 ⍝ Branch if not found
[ 5]   VSAMchk Z                 ⍝ Check return code
[ 6]   →(X≡Q←DATb)/0             ⍝ Don't write if no change
[ 7]  ∆1:X[¯32+⍳4]←4 10 0 100 100⊥3↑⎕TS ⍝ Date of last update
[ 8]   DATb←X                    ⍝ Put data in data variable
[ 9]   CTLb←'W'                  ⍝ Issue PUT request
[10]   VSAMchk CTLb              ⍝ Check return code

[⍝]∇ bcREAD.3  ⍴: 7  1986-07-23 13.26.33
[0]    Z←bcREAD
[1]   ⍝ Sequentially reads a record from the base cluster
[2]   ⍝ Returns the null vector if EOF
[3]    CTLb←'R'
[4]    ⍎(8 4∧.=Z←CTLb)/'→0,⍴Z←'''''
[5]    VSAMchk Z
[6]    Z←DATb

[⍝]∇ bcREPLACE.3  ⍴: 7  1986-07-23 13.26.43
[0]    Z←bcREPLACE X
[1]   ⍝ Replace a record in the file, which must be open for update
[2]    CTLb←'RU:',32↑X           ⍝ Does record already exist?
[3]    800016 VSAMchk Z←CTLb     ⍝ Ignore error if record not found
[4]    DATb←X
[5]    CTLb←'W'
[6]    800028 VSAMchk Z←CTLb     ⍝ Return error code to calling function

[⍝]∇ bcROPEN.3  ⍴: 15  1986-07-23 12.44.58
[ 0]   bcROPEN;A;N
[ 1]  ⍝ Open base cluster for READ only
[ 2]   bcCLOSE                   ⍝ Close any already-open file
[ 3]  ⍝ If variables already shared then don't do them again
[ 4]   →(2∧.=⎕SVO¨N←'CTLb' 'DATb')/∆1
[ 5]   A←⎕SVR¨N                  ⍝ Might not be shared properly so retract
[ 6]   ⎕ES(2∨.≠123 ⎕SVO¨N)/'Unable to share with AP123'
[ 7]   A←0 0 1 1 ⎕SVC 1⊃N        ⍝ Set access control
[ 8]  ∆1:CTLb←'OR VSAMBC'        ⍝ Open VSAM file for read only
[ 9]   100013 VSAMchk A←CTLb
[10]   CTLb←'R'                  ⍝ Sequential read of first item
[11]   VSAMchk A←CTLb
[12]   CTLb←'KF'                 ⍝ Key of first item
[13]   VSAMchk A←CTLb
[14]   bcKEYLEN←⍴bcFIRSTKEY←DATb ⍝ Store key length, first key

[⍝]∇ bcUGET.3  ⍴: 16  1986-08-11 12.24.53
[ 0]   Z←bcUGET KEY;I
[ 1]  ⍝ Reads a record for update
[ 2]   I←1                       ⍝ Try twice only
[ 3]  ⍝ The file must be open and the key must be correct.
[ 4]  ∆0:CTLb←'RU:',KEY          ⍝ Issue GET request
[ 5]  ⍝ Exit with null vector if not found
[ 6]   →(∨/(8,[1.1]4 16)∧.=2↑Z←CTLb)/∆1
[ 7]   VSAMchk Z                 ⍝ Check return code
[ 8]   →(KEY≡(⍴KEY)↑Z←DATb)/0    ⍝ Get data variable if return code OK
[ 9]   →(2<I←I+1)/∆1             ⍝ Only try twice
[10]   bcUOPEN
[11]   →∆0
[12]  ⍝ Logical error on GET request causes VSAM to lose positioning
[13]  ⍝ (See VSAM programmer's guide p.94)
[14]  ∆1:bcUOPEN                 ⍝ Reestablish positioning
[15]   Z←''                      ⍝ Exit with null vector

[⍝]∇ bcUOPEN.3  ⍴: 15  1986-07-23 12.43.12
[ 0]   bcUOPEN;A;N
[ 1]  ⍝ Open base cluster for UPDATE only
[ 2]   bcCLOSE                   ⍝ Close any already-open file
[ 3]  ⍝ If variables already shared then don't do them again
[ 4]   →(2∧.=⎕SVO¨N←'CTLb' 'DATb')/∆1
[ 5]   A←⎕SVR¨N                  ⍝ Might not be shared properly so retract
[ 6]   ⎕ES(2∨.≠123 ⎕SVO¨N)/'Unable to share with AP123'
[ 7]   A←0 0 1 1 ⎕SVC 1⊃N        ⍝ Set access control
[ 8]  ∆1:CTLb←'OU VSAMBC'        ⍝ Open VSAM file for read only
[ 9]   100013 VSAMchk CTLb
[10]   CTLb←'R'                  ⍝ Sequential read of first item
[11]   VSAMchk CTLb
[12]   CTLb←'KF'                 ⍝ Key of first item
[13]   VSAMchk CTLb
[14]   bcKEYLEN←⍴bcFIRSTKEY←DATb ⍝ Store key length, first key

[⍝]∇ dbALLOC.3  ⍴: 9  1988-01-21 14.25.07
[0]    dbALLOC
[1]    bcCLOSE                   ⍝ Close base cluster
[2]    aiCLOSE                   ⍝ Close alternate index
[3]    K←'ALLOC DD(VSAMBC) DS(''',(1⊃dbname),''') SHR REUSE'
[4]    ⎕ES(E←0≠K)/'Unable to allocate VSAM base cluster'
[5]    K←'ALLOC DD(VSAMAI) DS(''',(2⊃dbname),''') SHR REUSE'
[6]    ⎕ES(0≠K)/'Unable to allocate VSAM alternate index'
[7]    bcROPEN                   ⍝ Open Base cluster for read
[8]    aiROPEN                   ⍝ Open Alternate index for read

[⍝]∇ bcUPUT.3  ⍴: 7  1986-07-23 12.44.10
[0]    bcUPUT X
[1]   ⍝ Rewrites a record to the file
[2]   ⍝ The file must be open for UPDATE, and the previous action MUST
[3]   ⍝ have been a READ for UPDATE
[4]    DATb←X                    ⍝ Put data in data variable
[5]    CTLb←'W'                  ⍝ Issue PUT request
[6]    VSAMchk CTLb              ⍝ Check return code

[⍝]∇ SETUP∆VSAMerc.3  ⍴: 81  1988-01-18 17.30.50
[ 0]   SETUP∆VSAMerc;A
[ 1]  ⍝ Set up error-code matrix for VSAM files
[ 2]   A←0 2⍴''
[ 3]   A←A,[1]4 'More space requested or another volume mounted'
[ 4]   A←A,[1]8 'More than 1 record with specified key'
[ 5]   A←A,[1]16 'Record written into another control area'
[ 6]   A←A,[1]100012 'Command syntax error'
[ 7]   A←A,[1]100013 'VSAM file already open'
[ 8]
                     • • •
[74]
[75]   A←A,[1]800188 'Invalid dataset type'
[76]   A←A,[1]800192 'Invalid relative record number'
[77]   A←A,[1]800196 'Invalid request for RRDS'
[78]   A←A,[1]800200 'Invalid access through a path'
[79]   A←A,[1]800204 'Invalid request in Backward mode'
[80]   VSAMerc←A

[⍝]∇ VSAMchk.3  ⍴: 11  1986-07-23 12.52.09
[ 0]   X VSAMchk RC;A
[ 1]  ⍝ Check return code from a VSAM request
[ 2]   ⎕ES(2≠⎕SVO 'CTLb')/'No variables shared with VSAM'
[ 3]   →(0=RC←100000⊥RC)/0       ⍝ Exit if return code OK
[ 4]  ⍝ If RC is amongst those items specified in X then accept it
[ 5]   ⍎(2=⎕NC 'X')/'→(RC∊X)/0'
[ 6]  ⍝ If in VSAMerc then use that message
[ 7]   ⍎(RC∊VSAMerc[;1])/'→∆E,⍴A←⊃VSAMerc[VSAMerc[;1]⍳RC;2]'
[ 8]  ⍝ Error codes not accounted for by any of the above
[ 9]   A←'An error has occurred, codes ',⍕0 100000⊤RC
[10]  ∆E:⎕ES A

Appendix 2: FORTRAN subroutines

Note that these use VS FORTRAN release 4.1 (IBM’s version of FORTRAN-77) so they may not resemble the kind of FORTRAN you’ve come to know and hate!

      SUBROUTINE FVOPEN(IERR,IUNIT)
      OPEN (UNIT=IUNIT,IOSTAT=IERR,ACCESS='KEYED',ACTION='READ')
      END
****
      SUBROUTINE FVCLOS(IERR,IUNIT)
      CLOSE (UNIT=IUNIT,IOSTAT=IERR)
      END
****
      SUBROUTINE FVGET(IERR,IUNIT,KEY,KEYR)
      CHARACTER*32 KEY,KEYR
      LOGICAL OPEN
*
* Test if file is open. If not then set return code=1, reason code=22
* (as for APL2 AP123: request to process a file not currently open)
*
      INQUIRE (UNIT=IUNIT,IOSTAT=IERR,OPENED=OPEN)
      IF ( .NOT. OPEN) THEN
         IERR = 65558
         RETURN
      END IF
*
* Read the VSAM file using the specified key.
* If the record we read has a different key then we have lost
* positioning somehow, and we should return with an error code.
* I am using return code 1, with reason code 0, to show that there has
* been an error, but the file has been read. I am also returning the
* key of the record that was read.
*
      READ (UNIT=IUNIT,IOSTAT=IERR,KEY=KEY) KEYR
      IF (KEY .NE. KEYR) IERR = 65536
      END
****
      SUBROUTINE FVRDBK(IERR,IUNIT,KEYR)
      CHARACTER*32 KEYR
      LOGICAL OPEN
      INQUIRE (UNIT=IUNIT,IOSTAT=IERR,OPENED=OPEN)
      IF ( .NOT. OPEN) THEN
         IERR = 65558
         RETURN
      END IF
*
* Backspace once to retrieve the record we've just read.
* Backspace once again to retrieve the previous record.
*
      BACKSPACE (UNIT=IUNIT,IOSTAT=IERR)
      IF (IERR .NE. 0) RETURN
      BACKSPACE (UNIT=IUNIT,IOSTAT=IERR)
      IF (IERR .NE. 0) RETURN
*
* We only want the key to the record
*
      READ (UNIT=IUNIT,IOSTAT=IERR) KEYR
      END

We will also want the following Assembler module, which should be link-edited before the FORTRAN package (i.e. as the primary entry point):

FVSAM    CSECT
         XR    15,15              Identify this as EP table
         BCTR  15,14              Return with -1
         DC    A(FIRST)           Start of Entry Point table
         DC    A(16)              Width of Entry Point table
         DC    A(LAST)            Last entry in the table
FIRST    DC    CL8'FVOPEN  ',V(FVOPEN),A(0)
         DC    CL8'FVCLOS  ',V(FVCLOS),A(0)
         DC    CL8'FVGET   ',V(FVGET),A(0)
LAST     DC    CL8'FVRDBK  ',V(FVRDBK),A(0)
         END   FVSAM

References:

IBM Manuals:

GC26-4015 MVS/XA VSAM Administration Guide
GC26-4016 MVS/XA VSAM Administration: Macro Instruction Reference
GC26-4019 MVS/XA Access Method Services Reference
SC26-4118 VS FORTRAN Programming Guide, Release 4.1
SC26-4119 VS FORTRAN Language and Library Reference, Release 4.1
SH20-9218 APL2 Programming System Services Reference, Release 2

Other:

Using Quad-FX to Facilitate the Use of Auxiliary Processors, by David Piper
(Vector, Vol 3 No. 2, function listings in Vol 3 No. 3)