OpenVMS Source Code Demos

RMS_INDEXED_DEMO_FMS.BAS

1000	%title "BASIC_RMS_INDEXED_DEMO_FMS_xxx.BAS"
	%sbttl "RMS (Record Management Services) Demo - FMS Version"
	%ident "version 102.3"
	declare string constant k_program = "BASIC_RMS_Indexed_Demo_FMS"
	!==============================================================================================================
	! title  : BASIC-RMS-indexed-demo-fms_xxx.bas
	! author : Neil Rieck (https://neilrieck.net/links/cool_openvms.html)
	! purpose: demos the use of RMS-based indexed file access for novice OpenVMS programmers
	! scope  : this educational program comes free of charge with no strings attached
	! notes  : 1. OpenVMS-BASIC has 'built in' support for RMS (Record Management Services)
	!        : 2. a. in a RDBS, the primary key must be unique, isn't indexed by default, can't be changed
	!        :    b. what OpenVMS BASIC calls a primary key doesn't need to be unique, is indexed, can't be changed
	!        :    c. in RDBS terms, the primary key is really the RFA (record file address) which can be thought
	!                of as an internal RMS sequence counter
	!        : 3. edit environment: VT-220, 132 column, 8 column tab stops at 1,9,17,25,....
	!        : 4. all remarks begin in column 81
	! build  :	1. compile basic source
	!        :	    $bas BASIC_RMS_INDEXED_DEMO_FMS_102
	!        :	2. compile fms form
	!        :	    $fms/obj BASIC_RMS_INDEXED_DEMO_FMS.frm /out=BASIC_RMS_INDEXED_DEMO_FMS_frm.obj
	!        :	3. link pieces together
	!        :	    $link BASIC_RMS_INDEXED_DEMO_FMS_102, BASIC_RMS_INDEXED_DEMO_FMS_frm.obj
	! history:
	! ver who when   what
	! --- --- ------ ----------------------------------------------------------------------------------------------
	! 100 NSR 020829 1. original program
	! 101 NSR 050123 1. cleanup for public view
	!     NSR 050124 2. added more documentation
	! 102 NSR 110401 1. tweaks for 2011
	!     NSR 110615 2. added code to replace plain i/o with FMS (forms management system) i/o
	!     NSR 110617 3. a few more tweaks
	!==============================================================================================================
	option type=explicit							! cuz tricks are for kids
	set no prompt								! no ? with INPUT
	!
	%include "CSMIS$ROOT3:[DVLP.INC]FMS_FDVDEF.INC"				! FMS functions and constants
	%include "CSMIS$ROOT3:[DVLP.INC]device_controls.inc"			! VT control codes
	!
	!	<<< declare constants >>>
	!
	declare string constant k_idx_fs$ = "OpenVMS-BASIC-RMS-Indexed-Demo.dat"
	!
	!	<<< mapped variables to 'lay out' a disk record >>>
	!
	!	note: when the same map names is used, the second map overlays the first
	!
	map (idx_map)	string												&
			d21_first_name	= 20		,			! 20					&
			d21_last_name	= 20		,			! 40					&
			d21_telephone	= 10		,			! 50					&
			d21_address	= 20		,			! 70					&
			d21_city	= 20		,			! 90					&
			d21_postal_code	= 10		,			!100					&
			fill$		= 50		,			!150	room to grow			&
			d21_align 	= 0					!	to enforce map alignment
	map (idx_map)	string												&
			d21_whole_chunk	= 150		,			!150					&
			d21_align	= 0					!	to enforce map alignment
	!
	map (my_form)	string												&
			f21_first_name	= 20		,			! 20					&
			f21_last_name	= 20		,			! 40					&
			f21_telephone	= 10		,			! 50					&
			f21_address	= 20		,			! 70					&
			f21_city	= 20		,			! 90					&
			f21_postal_code	= 10		,			!100					&
			f21_align	= 0					!	to enforce map alignment
	map (my_form) string												&
			f21_whole_chunk	= 100		,			!150					&
			f21_align	= 0					!	to enforce map alignment
	!
	!	<<< declare variables >>>
	!
	declare long	handler_error%					,	&
			rec_count%					,	&
		string  junk$						,	&
			prev$						,	&
		rfa	rfa21							! record file address (a 48-bit variable)
	!
	!	<<< stuff for FMS >>>
	!
	map(wksp4)	string	WorkSpace4	= 12				! work space 4
	map(tca)	string	TCA		= 12				! terminal control area
	declare long	fms_init%					,	&
			form_up%					,	&
			term%						,	&
		string	choice$
	declare string	constant k_fname4$ = "BASIC_RMS_INDEXED_DEMO_FMS"	! title defined in the FORM
	declare long	constant k_fsize4% = 2000				!
	!
	!========================================================================================================================
	!	<<< main >>>
	!========================================================================================================================
2000	print k_program								! display program name
	print string$( len(k_program), ascii("=") )				! now underline it
	on error goto trap							! legacy error handler support
	margin #0, 132								! this will not change the screen size
	!
	!====================================================================================================
	!
	!	<<< delete all OpenVMS versions of our test file >>>
	!
	input "OK to delete 'demo data files'? (y/N) ";junk$			!
	junk$ = left$( edit$(junk$,32+2), 1)					! upcase, no white space
	goto sortie if junk$ <> "Y"						!
	when error in								!
	    while 1								! make sure we get all versions
		kill k_idx_fs$							!
	    next								!
	use									!
	end when								!
	!
	!	<<< open the file >>>
	!
	!	"BASIC Open" notes:
	!	1. open k_idx_fs$  for input  as file #21	- the file must already exist
	!	2. open k_idx_fs$  for output as file #21	- a new file version is always created
	!	3. open k_idx_fs$             as file #21	- the file is created if it doesn't exit
	!
	input "OK to create/open 'demo data file'? (y/N) ";junk$		!
	junk$ = left$( edit$(junk$,32+2), 1)					! upcase, no white space
	goto sortie if junk$ <> "Y"						!
	!
3000	when error in								!
	    print "-i- opening file: "; k_idx_fs$				!
	    open k_idx_fs$  as file #21						! create the file if it doesn't exist		&
		,access       modify						! we want to read + write			&
		,allow        modify						! allow others to read + write while we do it   &
		,map          idx_map						!						&
		,organization indexed						!						&
		,primary      (d21_last_name, d21_first_name, d21_city)		! key #0					&
		,alternate    d21_last_name   duplicates changes		! key #1					&
		,alternate    d21_telephone   duplicates changes		! key #2					&
		,alternate    d21_telephone   duplicates changes descending	! key #3
	    !
	    ! note: the connected channel is opened last but must be closed first
	    !
	    print "-i- opening file: "; k_idx_fs$; " (connect)"			!
	    open k_idx_fs$    as file #22					!						&
		,access       modify						! we want to read + write			&
		,allow        modify						! allow others to read + write while we do it	&
		,map          idx_map						!						&
		,organization indexed						!						&
		,connect 21							!
	    handler_error% = 0							! cool
	use									!
	    handler_error% = err						! oops
	    print "-e- error: "+str$( handler_error% )+" in phase #1"		!
	    print "-i- text : "+ert$( handler_error% )				!
	end when								!
	goto sortie if handler_error% <> 0					! exit on ant errors
	!
	!	<<< write some records >>>
	!
4000	rec_count% = 0								!
	input "OK to write 4 demo data records? (y/N) ";junk$			!
	junk$ = left$( edit$(junk$,32+2), 1)					! upcase, no white space
	goto sortie if junk$ <> "Y"						!
	when error in								!
	    print "-i- writing file: "; k_idx_fs$				!
	    d21_whole_chunk	= ""						! start with a clean buffer
	    !
	    d21_first_name	= "Neil"					!
	    d21_last_name	= "Rieck"					!
	    d21_telephone	= "5195551212"					!
	    d21_address		= "20 Water Street N"				!
	    d21_city		= "Kitchener"					!
	    d21_postal_code	= "N2H5A5"					!
	    print "-i- writing record: "; str$(rec_count% + 1)			!
	    put #21								! write to file
	    rec_count% = rec_count% + 1						!
	    !
	    d21_first_name	= "Ken"						!
	    d21_last_name	= "Olsen"					!
	    d21_telephone	= "4165553333"					!
	    d21_address		= "129 Parker Street"				!
	    d21_city		= "Toronto"					! this gets corrected to "Maynard" below
	    d21_postal_code	= "01754"					!
	    print "-i- writing record: "; str$(rec_count% + 1)			!
	    put #21								! write to file
	    rec_count% = rec_count% + 1						!
	    !
	    d21_first_name	= "Dave"					!
	    d21_last_name	= "Cutler"					!
	    d21_telephone	= "4165552222"					!
	    d21_address		= "220 Simcoe Street"				!
	    d21_city		= "Toronto"					!
	    d21_postal_code	= "M5T1T4"					!
	    print "-i- writing record: "; str$(rec_count% + 1)			!
	    put #21								! write to file
	    rec_count% = rec_count% + 1						!
	    !
	    d21_first_name	= "Gordon"					!
	    d21_last_name	= "Bell"					!
	    d21_telephone	= "4165551111"					!
	    d21_address		= "483 Bay Street"				!
	    d21_city		= "Toronto"					!
	    d21_postal_code	= "M5G2C9"					!
	    print "-i- writing record: "; str$(rec_count% + 1)			!
	    put #21								! write to file
	    rec_count% = rec_count% + 1						!
	    !
	    print "-i- now will rewrite previous record to force a 'duplicate key' error"
	    print "-i- writing record: "; str$(rec_count% + 1)			!
	    put #21								! write to file (will fail)
	    rec_count% = rec_count% + 1						!
	    !
	    handler_error% = 0							! cool
	use									!
	    handler_error% = err						! oops
	    print "-e- error: "+ str$( handler_error% )+" in phase #2"		!
	    print "-i- text : "+ ert$( handler_error% )				!
	    print "-i- recs : "+ str$( rec_count% )				!
	end when								!
	gosub read_sequentially							! display all records
	!
	!	<<< read the file sequentially by index-key #1 >>>
	!
5000	input "OK to display data records in reverse telephone order? (y/N) ";junk$
	junk$ = left$( edit$(junk$,32+2), 1)					! upcase, no white space
	goto sortie if junk$ <> "Y"						!
	rec_count% = 0								! init
	when error in								!
	    print "-i- reading file: "; k_idx_fs$; " by index-key-3"		!
	    reset #21, key#3							!
	!   find #21, key#3 gt " ", regardless					x same as previous line
	    while 1								! loop forever (until we trap out)
		get #21, regardless						! read without applying a record lock
		rec_count% = rec_count% + 1					!
		select rec_count%						!
		    case 1							! method #1 (plain terminal i/o)
			print   "first name  : "; d21_first_name
			print   "last_name   : "; d21_last_name
			print   "telephone   : "; d21_telephone
			print   "address     : "; d21_address
			print   "city        : "; d21_city
			print   "postal code : "; d21_postal_code
			print   "=============================="
			sleep 1							!
		    case 2							! method #2 (fms mode: use fdv$put)
			print							!
			print "switching to FMS mode"				!
			sleep 1							!
			gosub fms_setup if fms_init% = 0			!
			gosub display_fms_form4 if form_up% <> 4		!
			!
			call fdv$putl("will populate fields one-by-one", 23%)	!
			sleep 1							!
			!
			f21_whole_chunk = d21_whole_chunk			! copy data from disk buffer to form buffer
			call fdv$put(f21_first_name	,"F$FIRST_NAME"	)	! put data by field name
			call fdv$put(f21_last_name	,"F$LAST_NAME"	)	!
			call fdv$put(f21_telephone	,"F$TELEPHONE"	)	!
			call fdv$put(f21_address	,"F$ADDRESS"	)	!
			call fdv$put(f21_city		,"F$CITY"	)	!
			call fdv$put(f21_postal_code	,"F$POSTAL_CODE")	!
			!
			call fdv$getdl( choice$, term%, 23%, "Record "+str$(rec_count%)+". Hit <enter>" )
		    case else							! method #3 (fms mode: use fdv$putal)
			gosub fms_setup if fms_init% = 0			!
			gosub display_fms_form4 if form_up% <> 4		!
			!
			call fdv$putl("will populate whole form using one call", 23%)	!
			sleep 1							!
			!
			f21_whole_chunk = d21_whole_chunk			! copy disk buffer to form buffer
			call fdv$putal(f21_whole_chunk)				! populate the form
			prev$ = f21_whole_chunk					! remember old data$
			!
			call fdv$putl("help: <TAB> = next, <ctrl-H> = previous, <enter> = leave form", 22%)
			call fdv$putl("Record "+str$(rec_count%), 23%) 		!
			!
			call fdv$getal(f21_whole_chunk, term%)			! read the whole form
			!
			call fdv$putl("", 22%)					!
			if prev$ = f21_whole_chunk then				!
			    call fdv$getdl(junk$, term%, 23%,"nothing changed. Hit <enter> ...")
			else							!
			    call fdv$getdl(junk$, term%, 23%,"something changed. Hit <enter> ...")
			end if							!
			!
		end select							!
	    next								!
	use									!
	    handler_error% = err						!
	    if form_up% > 0 then						! if form #4 is up
		form_up% = 0							! then show it down
		call fdv$clear							! clear the fms workspace (and screen)
		print vt$normal;						! get rid of reverse video
!		print fdv$clear;vt$home;					x more stuff
	    end if								!
	    print "-e- error: "+str$( handler_error% )+" in phase #4"		!
	    print "-i- text : "+ert$( handler_error% )				!
	end when								!
	!
	!	<<< find/delete record "Cutler" >>>
	!
6000	input "OK to delete record for 'Dave Cutler'? (y/N) ";junk$		!
	junk$ = left$( edit$(junk$,32+2), 1)					! upcase, no white space
	goto sortie if junk$ <> "Y"						!
	when error in								!
	    find #21, key#1 nxeq "C "						! find (with lock)
	    while 1								! loop forever (until we trap out)
		get #21								! read (with lock)
		if  d21_last_name = "Cutler" and				&
		    d21_first_name = "Dave"
		then								! if Dave Cutler
		    delete #21							!
		    print "-i- record deleted, looking for more people named 'Dave Cutler'"
		else								!
		    cause error 11 if left$( d21_last_name,1) <> "C"		! exit if we've gone too far
		    iterate							!
		end if								!
	    next								!
	use									!
	    handler_error% = err						!
	    print "-e- error: "+str$( handler_error% )+" in phase #5"		!
	    print "-i- text : "+ert$( handler_error% )				!
	end when								!
	!
	gosub read_sequentially							! display all records (again)
	!
	!	<<< delete record #2 >>>
	!
7000	input "OK to delete 'Gordon Bell' using the RFA method? (y/N) ";junk$
	junk$ = left$( edit$(junk$,32+2), 1)					! upcase, no white space
	goto sortie if junk$ <> "Y"
	when error in
	    find #21, key#1 ge "Bell", regardless				! Find (without lock) (ge = nxeq)
	    while 1								!
		get #21, regardless						! read (without lock)
		cause error 11 if pos(d21_last_name,"Bell",1)=0			! exit if we've gone too far
		if  d21_last_name = "Bell" and					&
		    d21_first_name = "Gordon"
		then								! if Gordon Bell
		    rfa21 = getrfa(21)						! get the record file address
		    get #22, rfa rfa21						! position connected channel with LOCK
		    delete #22							! now delete
		    print "-i- record deleted, looking for more people named 'Gordon Bell'"
		else								!
		    iterate							! do another GET on orginal channel
		end if								!
	    next								!
	use									!
	    handler_error% = err						!
	    print "-e- error: "+str$( handler_error% )+" in phase #6"		!
	    print "-i- text : "+ert$( handler_error% )				!
	end when								!
	gosub read_sequentially							! display all records
	!
	!	<<< find/update record "Olsen" >>>
	!
8000	input "OK to change Ken Olsen's City? (y/N) ";junk$			!
	junk$ = left$( edit$(junk$,32+2), 1)					! upcase, no white space
	goto sortie if junk$ <> "Y"						!
	find_update_reentry_point:						!
	when error in								!
	    find #21, key#1 ge "Olsen", regardless				! set key
	    while 1								!
		get #21, regardless						! read without lock
		cause error 11 if pos(d21_last_name,"Olsen",1)=0		! exit if we've gone too far
		if  d21_first_name = "Ken"	and	&
		    d21_last_name  = "Olsen"	and	&
		    d21_city       = "Toronto"					!
		then								!
		    rfa21 = getrfa(21)						!
		    get #22, rfa rfa21						!
		    d21_city        = "Maynard"					!
		    d21_postal_code = ""					!
		    update #22							!
		    print "-i- record update, looking for more people named 'Ken Olsen'"
		end if								!
	    next								!
	use									!
	    handler_error% = err						!
	    print "-e- error: "+str$( handler_error% )+" in phase #7a"		!
	    print "-i- text : "+ert$( handler_error% )				!
	end when								!
	!
	select handler_error%							!
	    case    130								! key not changeable (for primary keys only)
		when error in							!
		    print "-i- attempting FIND-RFA"				!
		    find #22, rfa rfa21						! position with LOCK (but don't change data)
		    print "-i- attempting DELETE"				!
		    delete #22							! delete
		    print "-i- attempting PUT"					!
		    put #22							! write buffered data
		    handler_error% = 0						! cool
		use								!
		    handler_error% = err					! oops
		    print "-e- error: "+str$( handler_error% )+" in phase #7b"	!
		    print "-i- text : "+ert$( handler_error% )			!
		end when							!
		goto find_update_reentry_point if handler_error% = 0		! look for more if successful
	    case    11								! end-of-file
	    case    155								! record-not-found
	    case    131								! no current key (no lock)
	end select								!
	!
	gosub read_sequentially							! display all records
	!
	print string$( 60, ascii("-") )						! draw a line
	print "That's all for now"						!
	sleep 1									!
	goto sortie								!
	!====================================================================================================
	!	Subroutines
	!====================================================================================================
19000	fms_setup:								!
	call fdv$aterm( TCA by desc, 12%, 5% )					! attach terminal
	call fdv$awksp( WorkSpace4 by desc, k_fsize4% )				! attach WorkSpace 4
	fms_init% = 1								!
	return									!
	!
19010	display_fms_form4:							!
	call fdv$load ( k_fname4$ )						!
	call fdv$dispw								!
	form_up% = 4								! show form #4 loaded and up
	return									!
	!
	!	<<< read the file sequentially >>>
	!
20000	read_sequentially:							!
	input "OK to display data records sequentially? (y/N) ";junk$		!
	junk$ = left$( edit$(junk$,32+2), 1)					! upcase, no white space
	goto read_sequentially_exit	if junk$ <> "Y"				!
	when error in								!
	    print "-i- reading file: "; k_idx_fs$; " sequentially"		!
	    handler_error% = 0							!
	    reset #21								! rewind to BOF
	!   reset #21, key#0							x same as "reset #21"
	    while 1								!
		get #21, regardless						! read without applying a record lock
		print   "first name  : "; d21_first_name			!
		print   "last_name   : "; d21_last_name				!
		print   "telephone   : "; d21_telephone				!
		print   "address     : "; d21_address				!
		print   "city        : "; d21_city				!
		print   "postal code : "; d21_postal_code			!
		print   "=============================="			!
		sleep 1								!
	    next								!
	use									!
	    handler_error% = err						!
	    print "-e- error: "+str$( handler_error% )+" in phase #3"		!
	    print "-i- text : "+ert$( handler_error% )				!
	end when								!
	read_sequentially_exit:							!
	return									!
	!========================================================================================================================
	!	<<< Final Error Trap >>>
	!
	!	If we've done a good job coding, we should never execute this code >>>
	!========================================================================================================================
31000	trap:
	print
	print "Error in final trap"						!
	print "Line: "; str$(erl)						!
	print "Err : "; str$(err)						!
	print "Msg : "; ert$(err)						!
	resume sortie								!
	!========================================================================================================================
	!
	!	<<< that's all folks >>>
	!
32000	sortie:									!
	close 22								! always close the connected channel first
	close 21								!
	print "Adios..."							!
	end									!

Back to Home
Neil Rieck
Waterloo, Ontario, Canada.