Confusion is Relative by deb! Christensen (C)opyright 1986 all rights reserved The Eighth Wonder of the World should be Commodore Relative files. The wonder is that anyone can make sense from the documentation which CBM supplies for using these files in 2.0 BASIC! A highly powerful random access file type, Relative files are worth every ounce of perspiration you put into them. Allowing the programmer to define fields within each record and retrieve information from any given record number this file type is easily accessible from BASIC and can substantially speed up editing tasks to disk records. If you'll follow closely here, I'll try to remove the guesswork as well as the mystery from Commodore Relative Records. A warning first: Do not try to work with a Relative file until you are thoroughly familiar with SEQuential files, BASIC disk programming and using the drive command channel! Relative files depend heavily upon programming and planning. All good Data Base programs use this file type because any piece of information can be easily accessed in the file without having to read all the information which precedes it. That information can also also be changed and written back into the file without even touching another file entry! Anyone who has ever had to sift through a very long SEQuential file to find one piece of information, update the data and wait for the file to write back to the disk should very quickly see the advantages that this kind of random access will give you. There is a catch to this, though. The DOS must know where to go to find the information you want to retrieve. With careful planning and programming, your software can keep track of this for you. Very literally, every position within the file is available for instant access. Poor planning will result in a tangled web of confusion and possibly a whole stack of disk errors! To avoid chaos, be sure to define the task on paper before even touching your computer. I know this probably sounds like I'm about to dish out a Flowcharting 101 course, but there is no substitute for crystalizing a program idea in an outline before undertaking the actual BASIC coding needed. First, define the purpose of the program and any special conditions which need to be incorporated. My example here is going to be a file which holds questions and the correct answers. The program will need to be able to read the question from the file, print it to the screen, ask the user for the correct answer and then compare it to the correct answer stored in the file. The greatest advantage with a Relative file is that the access time for the first piece of information should be about the same as the last entry in the file! This is very important when the free memory of your computer is nearly full and a large amount of information must be on hand for easy access. There are several steps involved in using a Relative file. The file must be created first. That sounds very obvious, but because of the unique way a relative file is used, you can OPEN an existing file on the disk just once, and either Read from it or Write to it! Consequently, there is a specific way to create that Relative file on the disk for the first time. After the initial creation of the file, anytime you OPEN a relative file it is available for Reading or Writing when the correct commands have been issued. You can easily replace just one piece of information in the file without disturbing or having to re-write the rest of the file through careful positioning of the read-write head. Handy sounding, isn't it? Every Relative file is divided into what are known as Records. To understand what a Record is, imagine a file box full of 3 x 5 index cards. This is your address file, and each card contains only one name and address. The entire box is referred to as the File and each individual card is one Record within the file. Each card, or Record is the same size, 3" x 5", or it wouldn't fit in the file, would it? Written on each Record, or card, is a name on the top line, followed by the address, city, etc. These separate lines are called Fields and are the individual entries you make in each Record. So, each individual Record can contain several Fields. A typical address Record looks like this: John Jones 123 Main St. Anywhere, CA 90000 (209) 555-1234 The four Fields from the Record above would be: Name Address City, State, ZIP Phone When a Relative file is created, the size of each record is defined and cannot be changed. The size of the record is called the record length and is the combined total of maximum number of characters from each field for that record entry. This is a critical part of planning. If a record is not large enough, part of the data being written will be dropped, and if there is too much room, you will just be wasting precious disk storage space. In the example for the question and answer file, I am going to allow 89 characters for the question and 24 characters for the answer. Each record within a Relative file can contain up to 254 characters. There will only be the two fields in each record of this file, so just add up the maximum characters from each anticipated field to find the total length for each record. That gives us a record length of 113 characters, well under the 254 maximum. It is a very good idea to allow an extra character in each field to separate the information. I use carriage returns to mark the end of each field, which store as a 13 or CHR$(13) in the file. So, add two more characters to the record length, and that is all the information needed to create the file. The file can be created in the direct mode or it can be created with a program. I'm going to use program lines here. You'll find a small program in the appendix which can be used to create any Relative file. When the DOS is asked to create a new Relative file a Side Sector is created for that new file. In the Side Sector the DOS stores all the pointers to the physical location of each record number. You'll probably never even know where that Side Sector is, but it will be working hard to make sure that each of the designated records are available to the program by merely calling the record number. The Side Sector is an operation of the DOS. Neither the program or the programmer has to keep track of it! Now, lets name the file and put it on the disk. The number of characters per record will follow the file name as a CHR$ value. You cannot create a relative file without it! This file will be named "TEST". 10 REM CREATE RELATIVE FILE 20 OPEN3,8,3,"TEST,L,"+CHR$(115) Make sure that you type it in exactly as specified above. The most common errors include leaving out the comma right after the secondary address and leaving out that second comma within the quotes, following the "L". And yes, the plus sign is part of the syntax also. Here is the breakdown of the OPEN statement: OPEN file#,device#,channel#,"name,L,"+CHR$(length of record) The file should be CLOSEd next. 30 CLOSE3 RUN the small program and then take a look at the directory. (You may want to SAVE the program to a disk first, though.) You can see the REL for the file type on the directory entry for TEST. It is done, and the Relative file named TEST now resides on the disk. I am sure it will not surprise you, but Relative files do have some of their own rules. The SAVE with Replace option does not work with a Relative file. If you need to get rid of a Relative file, you can use the SCRATCH command, and start all over again with a new file, or you could erase the whole disk with a NEW command. Some backup programs will not copy a Relative file. Don't panic, I know that "1541 Copy" will do it for sure. This is a public domain program and is available from most user groups. Remember those Side Sectors? The DOS uses them to keep track of the file. These Side Sectors are automatically updated by the DOS and you do not see them. Although they are allocated in the BAM the Side Sector does not even have a separate entry in the Directory. It is considered part of the overhead and housekeeping space taken up by the Relative file itself. Many copy and backup programs do not look for them, and that is why Relative files may not copy. Because of these Side Sectors, the total storage on the diskette is not quite as large when a Relative file format is being used. The relative file you just created is now ready for either Reading or Writing anytime is is OPEN. Storing and retrieving from this file will require several pieces of information. When you created the Relative file you told the DOS how many characters would be in each record. That relative file will now be sectioned off into 115 character records. Each record will be exactly the same size as every other record in the file, just like you had used the same cookie cutter on a large piece of dough! Once you tell the DOS what size of "cookie cutter" to use, each record which is added to this file will always be 115 characters long and will be ready to accept the data which you need stored in it. The DOS and the Side Sectors will keep track of the physiscal location of each record. To access the information in any record, only the record number is needed. A relative file has no pre-determined size and will expand dynamically as you write to the new record numbers. This will take a bit of time if the DOS must find space on the diskette, allocate the sector in the BAM and update the Side Sector, as well as write to the file itself. This whole process can be speeded up considerably if the maximum number of records can be anticipated. When the last record number is known, the Relative file can be forced to write to that last record, thereby also writing all the records from the beginning of the file to that last record number and allocating all that space on the disk. Although this will take a little time at the beginning, it is a good habit. When you need to write to any record on a relative file, whether it is a new record or an old one, you need to send a position command to the drive. This will give the DOS the neccesary information to compute the exact position for the read/write head and allow you to store the information where you can easily retrieve it later with the same positioning command. To send this positioning command to the drive you will need to know the record number. So, pulling a number out of the hat, the maximum record number in this file will be 335. By positioning the drive to record number 335, the file can be forced to write that last record. All positioning commands must be sent to the drive in the form of a CHR$. The maximum allowable number which will fit into a CHR$ is 255. This represents the 8-bit limit of one byte, with all bits on (on means each bit is set to 1, totalling 255 in decimal). It is very common to need more than just 255 records for one file, so the record number is sent to the drive in the standard low byte-high byte format of 6502 machine code. This sounds overwhelming at first, but is really quite simple to calculate. The high byte is calculated first by dividing the record number by 256. The low byte is then derived from the remainder. The low byte for 335 is 79 and the high byte is 1. The BASIC syntax for this formula looks like this: N=335:HI=INT(N/256):LO=N-(HI*256) You can double check the formula results by multiplying the high byte times 256 and adding the low byte. Here are some more examples for you. Please notice that the low byte will always preceed the high byte. low byte high byte decimal number 0 1 = 256 1 2 = 513 36 0 = 36 232 4 = 1000 Here is what the syntax looks like for a position command: PRINT#15,"P"CHR$(CHANNEL#)CHR$(LO)CHR$(HI) Obviously the command channel must be open as well as the relative file. To accomplish this use the following line of BASIC: 40 OPEN15,8,15:OPEN3,8,3,"TEST" When accessing a relative file it is always a good idea to check the error status! Use a subroutine to do this. 42 GOSUB500 500 INPUT#15,E,ER$,T,S:IFE<>0THEN?E;ER$;T;S:STOP 510 RETURN Before accessing the file itself, the position command must be issued first. This command will always be sent to the command channel, #15. 50 PRINT#15,"P"CHR$(3)CHR$(79)CHR$(1) The first CHR$ value is the channel number of the Relative file, which was specified as the secondary address in the OPEN command. The next value is the low byte of the record number, followed by the high byte of the record number. Again, check the disk status after using this command. 55 GOSUB500 A word of caution! The record number you are writing does not exist yet, and it will return error number 50, indicating that the record does not exist. Of course this is no surprise, is it? That is the whole purpose of doing this, to create the record. When the program stops after this error message, just type in : CONT The program will continue to execute from the place it was STOPped after this command. This number 50 error message does not matter under these circumstances, but it must be read and cleared from the disk status. At the end of this chapter is a LISTing of the final version of this program which includes one technique for handling a RECORD NOT PRESENT error without STOPping the program. The disk drive is now in position for you to write to the new record number 335 in your relative file! Accomplish this with the following line of programming: 60 PRINT#3,"This is the Last Record!" 65 GOSUB500 The read/write head was already positioned through the command channel in line 50, and the actual information being written into the file is written to the File Number of the Relative file, just like you would send it to a SEQuential file! CLOSE the files and make everything nice and tidy. Many times the DOS buffers still hold some of the information which you sent to the file. If the file is not CLOSED you will loose important DATA and pointers. The warning to always CLOSE a file still applies to a Relative file. 70 CLOSE3:CLOSE15 Just as in other file operations, the command channel must be the first one OPENed and the last one CLOSEd. Closing the command channel will automatically close all other files. Be very careful! Although you can have as many Relative files on one disk as will fit, only one Relative file can be OPENed at one time. A Relative file takes 2 of the DOS RAM buffers, one for the file and the extra one for the Side Sectors. Trying to OPEN a second Relative file will return a NO CHANNEL error message from the drive. I know you have read that 2 Relative files can be OPEN simultaneously, but don't believe it! Commodore forgot to update the manuals and although prior IEEE drives have more channels to work with, a 1541 has a total of only 3 channels you can access with SEQuential or Relative files. Since the Relative file uses 2 of these channels, there is still one channel free, and a SEQuential file can be OPENed along with the Relative one. Be sure to SAVE this program. After doing that, look at the directory on your disk. You should see a remarkable difference in the number of blocks which the Relative file called TEST uses! By writing to that last record, you not only created all the intermediate records, but you also forced the DOS to allocate the sectors on the disk, saving the space from being used for something else. This is really the best technique to use. Can you imagine being in the middle of a program and finding out that there is no more room on the disk to finish writing your data? That is a situation to be avoided at all costs! Even though the only record which contains any information is number 335, record numbers 1 through 334 exist on your disk. The first data byte in each empty record will contain a chr$(255) to indicate it is empty, and the rest of the 115 bytes in the record contain nulls, or binary zeros. When you partially fill a record, the unused bytes in the record will remain nulls. It is important to realize that any subsequent entries to that same record which are shorter than the first entry will not result in the rest of the record being filled with nulls again. All bytes after the new data is written will remain untouched. This is why it is so very important to use a field separator in a relative file. Each record can have extra space in it, which may be filled with either nulls or old data. As long as the program knows what the field separator is, this old data will can be ignored. This is what the Relative file looks like now: Records 1 to 334: Byte 1 Bytes 2-115 ASCII 255 nulls-CHR$(0) Record # 335: Bytes 1-24 contain the ASCII for these characters: ----------------------------------------------- T h i s i s t h e L a s t R e c o r d ! ----------------------------------------------- Byte 25 Bytes 26-115 CHR$(13) nulls- CHR$(0) Program Listings CREATE THE FILE! 10 rem create relative file chapter 10 20 open3,8,3,"test,l,"+chr$(115) 30 close3 40 open15,8,15:open3,8,3,"test" 45 input"highest record number";n 46 hi=int(n/256):lo=n-(hi*256) 50 print#15,"p"chr$(3)chr$(lo)chr$(hi)chr$(1) 55 gosub500:ife<>0thenprinte;er$:ife<>50thengosub510 60 print#3,"The last record is number";n 65 gosub500:ife<>0thenprinte;er$:ife<>50thengosub510 70 print"a successful file write!":close3:close15 100 print" Now, lets see if it is where it is *supposed* to be!" 110 open15,8,15:open2,8,2,"test" 115 gosub500:ife<>0thengosub510 120 print#15,"p"chr$(2)chr$(lo)chr$(hi)chr$(1):gosub500:ife<>0thengosub510 125 input#2,a$:printa$:gosub500:ife<>0thengosub510 130 close2:close15:end 499 end 500 input#15,e,er$,t,s:return 510 printe,er$,t,s:stop 515 return ready. WRITE THE RECORD 10 rem test file 115 characters 20 rem per record 30 : 39 rem *** open files *** 40 open15,8,15:open3,8,3,"test":gosub500:ife<>0thengosub510 50 rem position 1 has question (field 1) 60 rem position 90 has answer (field 2) 90 rem *** write file *** 100 fori=1to11:rem i=record number 102 : 105 rem channel 3, recordi, position 1 110 print#15,"p"chr$(3)chr$(i)chr$(0)chr$(1):gosub500:ife<>0thengosub510 112 : 115 rem put question in file 3 first position 120 reada$:print#3,a$:gosub500:ife<>0thengosub510 122 : 125 rem position 90 in record i 130 print#15,"p"chr$(3)chr$(i)chr$(0)chr$(90):gosub500:ife<>0thengosub510 132 : 135 rem store answer at position 90 140 reada$:print#3,a$:gosub500:ife<>0thengosub510 150 nexti 160 close3:close15:end 290 end 299 rem *** contents for file *** 300 data "How many tracks on a 1541 formatted diskette?","35" 310 data "On which track does the Directory sit?","18" 320 data "What is the maximum number of SEQuential Files OPEN at the same time?" 325 data "3" 330 data "What is the maximum number of Relative files OPEN at once?","1" 340 data "Can 1 Relative file and 1 SEQuential file be used simultaneously?" 345 data "yes" 350 data "How do you get rid of a file marked with an asterisk?","validate" 360 data "What is the channel number for the command channel?","15" 370 data "Can channel 1 or 0 be used for secondary address in OPEN statement?" 375 data "no" 380 data "What is the correct abbreviation for the BASIC command,PRINT#?" 385 data "pR" 390 data "Can you read or write to the same OPEN Relative file?","yes" 395 data "Can an existing Relative file get larger without making a new one?" 396 data "yes" 499 rem *** read error *** 500 input#15,e,er$,t,s:return 510 printe,er$,t,s:stop 515 return RANDOMLY READ FILE 6 open15,8,15:open3,8,3,"test":gosub500:ife<>0thengosub510 10 goto100:rem skip subs 20 x$="":a$="" 21 get#3,x$:ifx$=chr$(13)thenreturn 22 printx$;:a$=a$+x$:goto21 30 x$="":a$="" 31 get#3,x$:ifx$=chr$(13)thenreturn 32 a$=a$+x$:goto31 40 x$="":g$="" 41 getx$:ifx$=""then41 42 ifx$=chr$(13)thenprint:goto50 44 ifx$=chr$(20)thenprint" ";:g$=left$(g$,len(g$)-1):goto41 45 printx$;:g$=g$+x$:goto41 50 ifg$=a$thenprint"correct answer!":return 55 print"correct answer is ";a$:return 100 dimfl(11):forx=1to11:rem i=record number 105 i=int(rnd(1)*11)+1 106 iffl(i)then105 107 fl(i)=1 108 print"Question number";i 110 print#15,"p"chr$(3)chr$(i)chr$(0)chr$(1) 120 gosub20:print 130 print#15,"p"chr$(3)chr$(i)chr$(0)chr$(90) 140 gosub30 145 print:print"q Your answer? ";:gosub40 150 print:nextx 160 close3:close15:end 290 end 500 input#15,e,er$,t,s:return 510 printe,er$,t,s:stop 515 return ready. As You can see, it is only half done! Use what you've learned to improve and continue from here!