CS352 - DATABASE SYSTEMS
Information on the Network DBMS Simulator: NETSIM (Pascal Version)
----------- -- --- ------- ---- --------- ------ ------ -------
NETSIM is a simulator that allows you to gain some experience with selected
features of the network DBMS model. A system built using NETSIM will consist of:
1. One or more data-definition language (DDL) files describing the database to
be used. The content of these file(s) will be in a special form described
below, which will be translated by a preprocessor into "pure Pascal" code
as part of each application program accessing this database.
2. One or more application programs that access the data base. Each such
program will contain a mixture of standard Pascal code and the following:
- A #DDL directive referencing the DDL file(s) needed by the program.
- Network data manipulation language (DML) statements, each beginning with
a pound sign (#) and standing on one or more separate lines of the file.
The NETSIM system itself consists of three parts:
- A preprocessor: NETSIM:NETPP.EXE. Each application program must be submitted
to the preprocessor, which will convert it and the DDL files it references to
a "pure Pascal" program which can then be compiled with the regular compiler.
- A Pascal environment file describing the DML procedures and other necessary
facilities: NETSIM:NETLIB.PEN. An inherit directive referencing this
environment is automatically included in the Pascal file by the preprocessor.
- A library of DML procedures to be linked with your compiled program:
NETSIM:NETLIB.OBJ
To use the Pascal version of NETSIM, it will be necessary to include the
following line in your LOGIN.COM file:
$ ASSIGN/JOB SYS$CSROOT:[CS352.352000.NETSIM_PASCAL] NETSIM
I. Creating and referencing DDL files
A. Each NETSIM application will use one or more DDL files describing the
database. A DDL file must not be changed once the system is up and
running and data is loaded into the files, since the interpretation of
the data in the files is dependent on the DDL description. If you must
change your DDL, you must also delete any existing .DAT files and rerun
your program. (Of course, any existing data in the files will be lost.)
B. The name of your DDL file should be something like PROJ1.DDL (the .DDL
filetype is the important part). It should contain only the DDL
constructs described below, plus appropriate Pascal comments if you wish.
It should NOT contain any Pascal code per se.
C. Each application program using the objects described by a given DDL file
will reference that file in its #DDL directive. Each application program
must contain exactly one such directive, in the portion of the program
where global constants, types, and variables are being declared, and
before the occurrence of any DML statements. A #DDL directive stands
by itself on one or more physical lines of the source file, and consists
of the directive #DDL followed by one or more ddl file names, separated
by commas. Examples:
#ddl 'MYDDL.DDL'
#ddl 'PROJ11.DDL', 'PROJ12.DDL', 'PROJ13.DDL'
II. Simulated DDL: the preprocessor requires that you use the following DDL
constructs to declare record types, set types, and keep lists you intend
to use.
A. General rules:
1. Each DDL declaration begins with a keyword of the form #RECORD, #SET,
or #KEEPLIST. The # must be the first non-blank character on the
line. The declaration can continue over any number of complete lines
in the source file.
2. All sets must be declared AFTER the records which are their members
and owners.
3. In the syntax that follows, uppercase is used for DDL reserved words,
lower-case for things you supply. (However, the simulator is not
case-sensitive at any point.) Square brackets enclose optional parts,
and braces enclose parts that may be repeated any number of times,
including zero times. Note that each DDL declaration ends with a
semicolon. Items "stacked" above one another within brackets or
braces indicate a choice among options.
B. Syntax:
1. Record declaration:
#RECORD recordname ON filename USING
{ DATE }
{ INTEGER }
{ fieldname : REAL ; }
{ STRING integer }
END ;
Example:
#record Student on 'STUDENTS.DAT' using
StudentId: string 7;
LastName: string 20;
FirstName: string 20;
YearAdmitted: integer;
GPA: real;
Birthday: date;
end;
2. Set declaration:
#SET setname OWNER recordname MEMBER recordname
[ AUTOMATIC ] [ FIXED ]
[ INSERTION MANUAL ] [ RETENTION MANDATORY ] ;
[ OPTIONAL ]
Examples:
#set StudentRoom owner Room member Student;
#set StudentRoom owner Room member Student retention optional;
3. Keeplist declaration:
#KEEPLIST keeplistname ;
Example:
#keeplist mykeep;
C. Within your program, you may access the following constructs declared
by the code generated by the preprocessor.
1. For each field of each record, you may access a template
recordname.fieldname.
Example: given the Student record declaration above, your program
may refer to:
Student.StudentID, Student.LastName, Student.FirstName,
Student.YearAdmitted, Student.GPA, Student.Birthday
2. You may use the recordname by itself, or the fieldnames by themselves,
as parameters in DML statements, but not for any other purpose. In
particular, you must NOT use a DDL record name in a Pascal WITH
statement.
3. You may use setnames and keeplistnames as parameters in DML
statement, but not for any other purpose.
D. VERY IMPORTANT: each recordname, fieldname, setname, and keeplistname
must be unique within your program - it must not be the same as any
other name used in the DDL (even for a different purpose), nor may
it be the same as any identifier you are using in your Pascal code.
III. Writing, compiling, and linking programs to access your database:
A. The name of your program file should be something like PROJ1.DML (the .DML
filetype is the important part). It should contain a standard program
heading, a #DDL directive embedded in the section containing declarations
for global constants, types, and variables, plus procedure declarations
and a main program as usual. The procedures and main program may
contain ordinary Pascal intermingled with network DML statements as
described below.
B. Each program must be submitted to the preprocessor NETSIM:NETPP.EXE
to be converted into pure Pascal code. The resultant Pascal code should
then be compiled by the regular Pascal compiler, and linked with the
library NETSIM:NETLIB.OBJ.
1. To translate your DML file, RUN the preprocessor NETSIM:NETPP. It will
ask for the name of your source file. The preprocessor will produce a
"pure Pascal" file whose name is the same as that of the DML source
but whose filetype will be .PAS. (Example: a DML file PROJ1.DML
will be converted into a Pascal file PROJ1.PAS).
2. Use a link command like the following after compiling the resultant
Pascal file as usual:
LINK programname,NETSIM:NETLIB
C. The following DML statements may be used from within your program,
interspersed with standard Pascal code. Each must stand by itself on one
or more physical lines of the source file - i.e. the # must be the first
non-blank character on the line, and normally nothing should appear on the
line after the end of the DML statement EXCEPT an optional semicolon (if
required at that point by Pascal syntax) or the remainder of the code
required to form a conditional statement.
1. In the syntax that follows, uppercase is used for DML reserved words,
lower-case for things you supply. (However, the simulator is not
case-sensitive at any point.) Square brackets enclose optional parts,
and braces enclose parts that may be repeated any number of times,
including zero times. Note that each DML statement ends with an
optional semicolon. Items "stacked" above one another within brackets
or braces indicate a choice among options.
2. Record-oriented FIND statement:
FIRST
#FIND NEXT recordname [ USING fieldname {, fieldname} ] [;]
ANY
DUPLICATE
(Note: FIRST and ANY are equivalent in meaning, and NEXT and
DUPLICATE are equivalent in meaning. Either may be used.)
a. Recordname must be a declared #RECORD.
b. If you include the word USING, you may specify one or more field
names. Each fieldname must be the name of one of the fields in the
specified record.
c. Examples:
#find first student;
#find next student using lastname;
#find first room using dorm, number;
d. There is no provision for a FOR UPDATE clause; update is allowed
after any successful find.
e. If no fieldnames are specified, these statements find, respectively,
the first record of the specified record type or the next record
after the current of record of the specified record type. If you
specify one or more fieldnames, then the statement begins searching
at the first/next record until it finds one that matches the record
template on ALL of the specified fields.
f. The record found (if any) becomes the current of run unit, current
of record for its record type, and current of set for all sets in
which it participates. (Note that if the record found is of the
member type of a certain set, but is not currently a member of any
occurrence of that set, then the current of set for that set is NOT
affected, though.) However, if no suitable record is found,
then no currency indicators are changed; instead DB$Status is set
to DB$_NotFound. Updating of currencies may be selectively
suppressed by using a RETAINING clause as described below.
3. Set-oriented FIND statement:
FIRST recordname
NEXT recordname
#FIND ANY recordname WITHIN setname [;]
DUPLICATE recordname
OWNER
(Note: FIRST and ANY are equivalent in meaning, and NEXT and
DUPLICATE are equivalent in meaning. Either may be used.)
a. Recordname must be a declared #RECORD and setname a #SET. (No
recordname is used with the OWNER format, though.)
b. Recordname must refer to the member record type of setname.
c. Examples:
#find first student within occupiedby;
#find owner within occupiedby;
d. There is no provision for a FOR UPDATE clause; update is allowed
after any successful find.
e. These statements find, respectively, the first member, next member,
or owner of the set occurrence associated with the current of set
for the specified set. If the set has no current record, then
DB$Status is set to DB$_NoCurrent instead.
f. The record found (if any) becomes the current of run unit, current
of record for its record type, and current of set for all sets in
which it participates. (Note that if the record found is of the
member type of a certain set, but is not currently a member of any
occurrence of that set, then the current of set for that set is NOT
affected, though.) However, if no suitable record is found,
then no currency indicators are changed; instead DB$Status is set
to DB$_NotFound. Updating of currencies may be selectively
suppressed by using a RETAINING clause as described below.
4. Currency-oriented FIND statement:
recordname
#FIND setname [;]
keeplistname
a. The parameter must be a declared #RECORD, #SET, or #KEEPLIST.
b. Examples:
#find student;
#find occupiedby;
#find tempkeep;
c. If the parameter is a recordname, then the current of record for
that type is selected.
d. If the parameter is a setname, then the current of set for
that type is selected.
e. If the parameter is a keeplistname, then the front entry is selected
and removed from the specified keeplist.
f. The record found (if any) becomes the current of run unit, current
of record for its record type, and current of set for all sets in
which it participates. (Note that if the record found is of the
member type of a certain set, but is not currently a member of any
occurrence of that set, then the current of set for that set is NOT
affected, though.) However, if no suitable record is found,
then no currency indicators are changed; instead DB$Status is set
to DB$_NotFound. Updating of currencies may be selectively
suppressed by using a RETAINING clause as described below.
5. STORE statement:
#STORE recordname [;]
a. A new record is added to the specified record type, and data is
copied from the in-memory template to it. Thus, the data to be
stored must be placed in the template before the statement is
executed.
b. Example:
#store student;
c. The newly-created record is also added to the current occurrence
of any INSERTION AUTOMATIC sets of which it is the member type.
Thus, if the record being created is a member type of such a set,
then the currency indicator(s) for each such set must be set to the
correct occurrence before #store is used.
d. The newly-created record becomes the current of run unit,
current of record for its record type, and current of set for
all sets of which it is the owner type or an AUTOMATIC member.
Updating of currencies may be selectively suppressed by using a
RETAINING clause as described below.
6. Record fetch/update statements:
#GET
#MODIFY [recordname] [;]
#ERASE [ALL]
a. Recordname is optional; but if it is specified, it must be a
declared #RECORD.
b. Each of these statements operates on the record that is the
current of run unit. The recordname parameter is only used for
documentation, to make the operation being performed clearer
to the reader. If it is specified it must match the record type
that is current of run unit when the statement is executed, or the
statement will fail.
c. Examples:
#get student;
#modify;
#erase all room;
d. #GET copies data from the current of run unit record on disk
to the template for its record type in memory.
e. #MODIFY copies data from a template in memory to the current of
run unit record on disk.
f. #ERASE (with or without ALL) deletes the current of run unit record.
Without ALL, it also erases any member records of set occurrences
the record being deleted owns if the retention for the set is fixed.
With ALL specified, ERASE erases ALL member records of set
occurrences the record being deleted owns regardless of the
retention specified for the set. Either way, the statement
recursively deletes member records of sets owned by set members
being deleted according to the same rules.
g. ERASE without ALL will fail if any record being deleted
owns a non-empty set occurrence of a mandatory retention set.
h. None of these statements directly affects any currency indicators.
However, since #ERASE deletes the current of run unit, the current
of run unit indicator and all currency indicators referring to that
record will now refer to a vacant slot in the database. Thus,
subsequent attempts to access that record directly will fail;
however, statements like #FIND NEXT ... will still work correctly,
finding the record after the one that was just erased.
7. Set modify statements:
#CONNECT TO
#DISCONNECT recordname FROM setname [;]
#RECONNECT WITHIN
a. Recordname must be declared a #RECORD and setname a #SET.
b. Recordname must refer to the member record type of setname.
c. Examples:
#connect student to occupiedby;
#disconnect student from occupiedby;
#reconnect student within occupiedby;
d. In each case, the record affected is the current of run unit,
which must be of the type specified by recordname, or the statement
will fail. In the case of #CONNECT or #RECONNECT, the set
occurrence to which this record is (re)connected is the one that is
current of set for the specified set type.
e. #CONNECT requires that the record NOT currently be a member of an
occurrence of the specified set, while #DISCONNECT requires
that it must be currently a member of an occurrence of the specified
set. #RECONNECT will work in either case. #DISCONNECT is not
legal for a set declared as RETENTION FIXED or MANDATORY and
#RECONNECT is not legal for a set declared as RETENTION FIXED.
f. Following a successful execution of #CONNECT or #RECONNECT, the
record (re)connected becomes current of set for the set type
specified, unless updating of set set currency is specified by a
RETAINING clause. Following a successful execution of #DISCONNECT
where the record was current of set for the set, current of set for
the set becomes undefined, but #FIND NEXT ... WITHIN and #FIND OWNER
will still work correctly. No other currency indicators are
affected.
8. Keep Statement:
[ recordname ]
#KEEP [ setname ] USING keeplistname [;]
[ keeplistname ]
a. Recordname, if specified, must be a declared #RECORD; setname a
declared #SET, and (each) keeplistname must be a declared #KEEPLIST.
b. Examples:
keep student using tempkeep;
keep using tempkeep;
c. With no name between the #KEEP and USING, the current of run unit
is added to the end of the specified keeplist. With a recordname
specified, the current of record for that record type is used,
with a setname specified, the current of set for that set type is
used, and with a keeplistname the front of that keeplist is used.
In any case, no currency indicators are affected.
9. Retaining clauses:
RECORD RECORD
... RETAINING [SET] setname {, [SET] setname } CURRENCY
(Note that the word SET preceeding a setname is optional.)
a. As indicated above, the various find statements, and the store
statement normally make the record accessed current of run unit,
current of record, and current of set for all sets in which it
participates, and the connect/reconnect statements make the
record involved current of set for the specified set. Sometimes,
it is necessary to suppress the updating of currency for the
record type and/or one or more set types when a find, store, or
(re)connect operation is performed. This is done by appending a
retaining clause to the statement (before the terminating semicolon,
if any.)
b. Examples:
#find student retaining set occupiedby currency;
#find owner within occupiedby retaining record currency;
#store student retaining record, occupiedby currency;
#connect student to occupied by retaining occupiedby currency;
c. Specifying RECORD suppresses the updating of currency for the
record type accessed by the statement. Specifying one or more sets
suppresses the updating of currency for the specified set type(s).
A successful find or store will still update the current of run
unit and any record/set currencies not specified in the retaining
clause.
d. No statements other than #FIND, #STORE, #CONNECT or #RECONNECT
may have a RETAINING clause. In the case of #CONNECT or #RECONNECT,
only the setname involved in the operation may be specified.
10. Set membership conditional statements:
#IF [NOT] MEMBER setname ...
#WHILE [NOT] MEMBER setname ...
a. Setname must be a declared #SET.
b. These translate into Pascal if or while statements, and anything
appearing on the line after the last keyword (normally a then, do,
or boolean connective such as and) will be passed on to the pure
Pascal code unaltered. The condition itself translates into
a Pascal boolean expression.
c. The MEMBER condition is true if the current of run unit is a
member of some occurrence of the specified set. The optional NOT,
of course, inverts the sense of this test.
d. DB$Status will be set to DB$_Success unless the condition cannot
be tested for some reason - e.g. there is no current of run unit,
or the current of run unit is not of the correct recordtype to be
a member of the specified set. A condition that cannot be
tested evaluates to false.
D. DB$Status (a variable) is set by all of the above.
1. It is set to DB$_Success if the operation succeeded and something else
if it failed. Thus, you could code something like:
#find any student using lastname;
if DB$Status = DB$_Success then
...
2. Appendix A lists all the values DB$Status can assume, and their
meanings. You may explicitly test for any particular one at any
time by coding something like:
if DB$Status = DB$_Success then
or while DB$Status = DB$_Success do
or case DB$Status of
DB$_Success: ...
DB$_NotFound: ...
DB$_NotMemberType:
...
end
3. Note that, in general, DB$_NotFound is the only "benign"
non-success status code; in most cases other status codes
indicate an error in logic in your program.
IV. Utility functions and procedures.
A. The following functions and procedures may be called by your program,
using standard Pascal syntax.
B. DB$Signal is a procedure that may be called after one of the above
DML statements returned a non-success status.
1. By default, DB$Signal is called by the library module when any
routine returns a status other than success or DB$_NotFound (the
only "benign" error status.)
a. This behavior may be overridden by altering the value of the
predefined global variable DB$AutomaticSignalStatuses, which is
declared as a set of DB$StatusType and initialized to contain all
status values except DB$_Success, DB$_NotFound. For example to
suppress automatic signalling on status DB$_NotCurrent, you
could code:
DB$AutomaticSignalStatuses :=
DB$AutomaticSignalStatuses - [ DB$_NotCurrrent ];
and to suppress all automatic signalling you could code:
DB$AutomaticSignalStatuses := [];
b. The predefined global variable DB$AbortOnAutomaticSignal determines
whether an automatic call to DB$Signal aborts program execution.
The default value (false) allows program execution to continue.
2. Your code may call DB$Signal also. It takes an optional boolean
parameter, which defaults to true if omitted.
3. When DB$Signal is called, it does the following:
a. Prints a message describing the failure.
b. If the boolean parameter is true or is not specified, it will
signal an error to the operating system, generating a stack dump.
c. If the boolean parameter is false, it always returns to the caller
after printing its message.
Examples:
DB$Signal; -- aborts after printing error message
DB$Signal(false); -- always returns to caller
4. If DB$Signal is called when the current status is success, it will
simply print a descriptive message, regardless of the parameter value.
C. The following routines facilitate working with DATE fields in records,
which are objects of type DB$Date:
1. Note that the type DB$Date is actually an integer, expressing a date
as days elapsed since the Smithsonian base year - November 17, 1858.
In particular, note that the elapsed days between two dates can be
obtained by simple subtraction.
2. function DB$StringToDate(DateIn: packed array[l1..h1:integer] of char
): DB$Date;
(* Converts a character string representation of a date to a numeric
value representing days elapsed since the Smithsonian base date,
November 17, 1858. An error in the date format will result in
impossible value -1 being returned instead. *)
3. type
DB$DateString = varying[11] of char;
function DB$DateToString(DateIn: DB$Date): DB$DateString;
(* Converts a numeric value of a date representing days elapsed since
the Smithsonian base date, November 17, 1858, to a character string
representation. The illegal value -1 will result in the string
"Bad Date" being returned instead. *)
4. function DB$Today: DB$Date;
(* Returns today's date in the same form as DB$StringToDate would *)
D. DB$Debug is a procedure which may be called at any time. It invokes
an interactive debugger that allows you to examine the currency
indicators and database contents, and then return to the execution
of your program. Instructions for using DB$Debug are found in
Appendix B.
E. None of the above routines affect any currency indicators or DB$Status.
V. An example
A. Attached are listings of a DDL file and an application program for
a simple system that keeps track of student dorm room assignments.
B. The following commands would be used to generate an executable program,
assuming that the DDL is contained in the file DEMO.DDL and the
application program is in the file DEMO.DML:
$ RUN NETSIM:NETPP
$_Source File: DEMO
$ PASCAL DEMO
$ LINK DEMO,NETSIM:NETLIB
$ RUN DEMO
At this point, the following files would be in existence:
DEMO.DDL -- DDL source
DEMO.DML -- application program source, containing DML
DEMO.PAS -- translated source, DDL, and DML (can be deleted)
DEMO.OBJ -- object file from compilation (can be deleted)
DEMO.EXE -- executable version of application program
STUDENTS.DAT, ROOMS.DAT -- database files created by the first run
of the program
(* This is file DEMO.DDL *)
(* Database definitions for student-room assignment demonstration database. *)
#record Room on 'ROOMS.DAT' using
Dorm: string 15;
Number: integer;
end;
#record Student on 'STUDENTS.DAT' using
LastName: string 20;
FirstName: string 20;
DateArrived: date;
GPA: real;
end;
#set OccupiedBy owner Room member Student insertion manual retention optional;
#keeplist TempKeep;
(* This is file DEMO.DML *)
program demo(input, output);
(* This program demonstrates some of the features of the network DML, by
maintaining a simple database of student room assignments. *)
#ddl 'demo'
procedure menu(var choice: integer);
(* Offer the user a choice of options, and return the one chosen *)
var
ok: boolean;
begin
repeat
write(chr(27),'[H',chr(27),'[J');
writeln(' 1: Add Student');
writeln(' 2: Add Room');
writeln(' 3: Put Student In Room');
writeln(' 4: Remove Student From Room');
writeln(' 5: Change Room');
writeln(' 6: Display Student Room');
writeln(' 7: Display Room Students');
writeln(' 8: Display All Students');
writeln(' 9: Display All Rooms');
writeln('10: Change student GPA');
writeln('11: Delete a Student');
writeln('12: Delete a Room');
writeln('13: Call the interactive debugger');
writeln;
write('Choice (0 to exit)? ');
if eof(input) then
begin
choice := 0;
ok := true
end
else
begin
read(choice, error := continue);
ok := (status(input) = 0) and (choice in [0..13]);
if not ok then writeln('Please enter an integer from 0 to 13');
readln;
end
until ok
end;
procedure AddStudent;
(* Add a new student to Students *)
begin
write('Last: '); readln(student.lastname);
write('First: '); readln(student.firstname);
student.DateArrived := DB$Today;
student.GPA := 0.0;
#store student;
if DB$Status <> DB$_Success then DB$Signal(false);
end;
procedure AddRoom;
(* Add a new room to Rooms *)
begin
write('Dorm: '); readln(room.dorm);
write('Number: '); readln(room.number);
#store room;
if DB$Status <> DB$_Success then DB$Signal(false)
end;
procedure PutStudentInRoom;
(* Assign a student to a room *)
begin
write('Last name: '); readln(student.lastname);
#find first student using lastname;
if DB$Status <> DB$_Success then
writeln('No such student')
else
begin
#if member occupiedby then
writeln('Student is already in a room')
else
begin
write('Dorm: '); readln(room.dorm);
write('Number: '); readln(room.number);
#find first room using dorm, number;
if DB$Status <> DB$_Success then
writeln('No such room')
else
begin
#find student retaining occupiedby;
#connect student to occupiedby;
if DB$Status <> DB$_Success then DB$Signal(false)
end
end
end
end;
procedure RemoveStudentFromRoom;
(* Removes a student from room assigned to *)
begin
write('Last name: '); readln(student.lastname);
#find first student using lastname;
if DB$Status <> DB$_Success then
writeln('No such student')
else
begin
#get student;
#if not member occupiedby then
writeln('Not in any room')
else
begin
#disconnect student from occupiedby;
if DB$Status <> DB$_Success then DB$Signal(false)
end
end
end;
procedure ChangeRoom;
(* Change a student from one room to another *)
begin
write('Last name: '); readln(student.lastname);
#find first student using lastname;
if DB$Status <> DB$_Success then
writeln('No such student')
else
begin
#if not member occupiedby then
writeln('Not in any room')
else
begin
#find owner within occupiedby;
#get room;
writeln('Currently in: ', room.dorm, room.number);
write('New dorm: '); readln(room.dorm);
write('New number: '); readln(room.number);
#find first room using dorm, number;
if DB$Status <> DB$_Success then
writeln('No such room')
else
begin
#find student retaining occupiedby;
#reconnect student within occupiedby;
if DB$Status <> DB$_Success then DB$Signal(false)
end
end
end
end;
procedure DisplayStudentRoom;
(* Display the room of a specified student *)
begin
write('Last name: '); readln(student.lastname);
#find first student using lastname;
if DB$Status <> DB$_Success then
writeln('No such student')
else
begin
#if member occupiedby then
begin
#find owner within occupiedby;
#get room;
writeln(Room.dorm, room.number)
end
else
writeln('Not in any room')
end
end;
procedure DisplayRoomStudents;
(* Display all students living in a given room *)
begin
write('Dorm: '); readln(room.dorm);
write('Number: '); readln(room.number);
#find first room using number;
if DB$Status <> DB$_Success then
writeln('No such room')
else
begin
#find first student within occupiedby;
if DB$Status <> DB$_Success then
writeln('No students in this room');
while DB$Status = DB$_Success do
begin
#get student;
writeln(student.lastname, student.firstname);
#find next student within occupiedby;
end
end
end;
procedure DisplayAllStudents;
(* Displays all students with their dorm assignment, if any *)
begin
#find first student;
while DB$Status = DB$_Success do
begin
#keep using TempKeep;
#get student;
write(student.lastname, student.firstname);
write(DB$DateToString(student.DateArrived), ' ');
write(student.GPA:3:1, ' ');
#if member occupiedby then
begin
#find owner within occupiedby;
#get room;
writeln(room.dorm, room.number)
end
else
writeln('Not assigned');
#find TempKeep;
if DB$Status <> DB$_Success then DB$Signal(false);
#find next student;
end
end;
procedure DisplayAllRooms;
(* Displays all rooms, with count of occupants *)
var
count: integer;
begin
#find first room;
while DB$Status = DB$_Success do
begin
#get room;
write(Room.Dorm, Room.Number);
count := 0;
#find first student within occupiedby;
while DB$Status = DB$_Success do
begin
count := count + 1;
#find next student within occupiedby;
end;
if count = 0 then
writeln('No students assigned': 25)
else
writeln(Count:7, ' students assigned');
#find next room;
end
end;
procedure ChangeGPA;
(* Prompts for a student name, then alters his GPA *)
begin
write('Last name: '); readln(student.lastname);
#find first student using lastname;
if DB$Status <> DB$_Success then
writeln('No such student')
else
begin
write('Old GPA is ', student.GPA:3:1, '. Enter new GPA: ');
readln(student.GPA);
#modify student;
if DB$Status <> DB$_Success then
DB$Signal(false)
end
end;
procedure DeleteAStudent;
(* Prompts for a student name, then deletes him! *)
begin
write('Last name: '); readln(student.lastname);
#find first student using lastname;
if DB$Status <> DB$_Success then
writeln('No such student')
else
begin
#erase student;
if DB$Status <> DB$_Success then
DB$Signal(false)
end
end;
procedure DeleteARoom;
(* Prompts for a room, then deletes it! *)
begin
write('Dorm: '); readln(room.dorm);
write('Number: '); readln(room.number);
#find first room using dorm, number;
if DB$Status <> DB$_Success then
writeln('No such room')
else
begin
#erase room;
if DB$Status <> DB$_Success then
DB$Signal(false)
end
end;
(* Main program *)
var
choice: integer;
begin
DB$AutomaticSignalStatuses := []; (* We'll signal errors manually *)
repeat
menu(choice);
writeln;
case choice of
1: AddStudent;
2: AddRoom;
3: PutStudentInRoom;
4: RemoveStudentFromRoom;
5: ChangeRoom;
6: DisplayStudentRoom;
7: DisplayRoomStudents;
8: DisplayAllStudents;
9: DisplayAllRooms;
10: ChangeGPA;
11: DeleteAStudent;
12: DeleteARoom;
13: DB$Debug
end;
if choice <> 0 then
begin
write(chr(27),'[23;0H', chr(10), 'Press Return to continue ');
readln
end
until choice = 0;
writeln('Finished')
end.
APPENDIX A - INTERPRETATION OF VALUES OF DB$STATUS
(Note: all of these values are defined as symbolic constants in the NETLIB
environment and can be explicitly tested for in your program.)
Value Interpretation
----- --------------
DB$_Success Last operation succeeded.
DB$_AlreadyConnected Attempt to CONNECT a record to a set to which it is
already connected.
DB$_EmptyKeepList Attempt to FIND front of an empty keep list.
DB$_NoCurrent Current of run unit, record or set is undefined.
DB$_NotConnected Attempt to disconnect a record that was not connected.
DB$_NotCRU Attempt to manipulate a record that is not current of
run unit.
DB$_NotFound FIND operation failed to find a matching record.
DB$_NotMemberType Record specified is not member type of specified set.
DB$_NotRecord Recordname parameter is not the name of a recordtype.
DB$_RecordsizeMismatch Record size specified in file does not match size
of declaration.
DB$_RetentionViolation Operation would result in set retention rule
violation.
APPENDIX B - USING DB$Debug
A. The debugger is entered by calling DB$Debug from within your program. It
can also be called, if you wish, from the VMS debugger by typing the command
CALL DB$DEBUG at the DBG> prompt, provided your program was both compiled
and linked with the /DEBUG qualifier.
B. The debugger will prompt for a command as follows:
NETDBG>
C. At this prompt, you may enter any of the following commands, always typing
the command on a single physical line and terminating it with RETURN:
[ recordname [ ALL ] ]
LIST [ setname [ OWNER ] ]
SHOW [ ALL ] ]
DISPLAY [ keeplistname [ ALL ] ]
[ ALL ]
FIND recordname number
FIRST
FIND NEXT recordname [ USING fieldname = value ]
ANY
DUPLICATE
RESTORE
SIGNAL
HELP
EXIT
D. The meanings of these commands are as follows:
1. The LIST command gives a one-line description of each selected record -
its record number and recordtype. The meaning of the various options
is as follows:
LIST by itself gives CRU and status
LIST recordname gives current of record
LIST recordname ALL gives all records of specified type
LIST setname gives current of set
LIST setname OWNER gives owner of current occurrence of set
LIST setname ALL gives owner and all of members of current
occurrence of set
LIST keeplistname gives front of keeplist
LIST keeplistname ALL gives all items on keeplist
LIST ALL gives CRU, current of record for all records, current of set
for all sets, front of all keeplists and status.
2. The SHOW command gives a two-line description of each selected record -
its record number and recordtype on one line, followed by its contents
(as much as will fit on one screen line) on the second line. The
options are the same as for the LIST command described above.
3. The DISPLAY command gives a multi-line description of each selected
record - its record number and recordtype on one line, followed by the
names and values of each of its fields and sets it participates in, each
on one line. The options are the same as for the LIST command described
above.
4. The FIND command temporarily repositions all currency indicators to
the record specified. The first variant of FIND can be used to find
a specific record number within a recordtype. The remaining variants
work like the corresponding DML statements.
All currencies altered in this way are restored to their values when
the debugger was entered upon exiting the debugger, or when the RESTORE
command is given.
5. The RESTORE command resets all currency indicators to the state they
were in when the debugger was entered. A RESTORE is also done
automatically when EXITing the debugger.
6. The SIGNAL command invokes DB$Signal to display the full text
associated with the current value of DB$Status.
7. The HELP command puts you into the VMS help facility accessing a help
text similar to this appendix.
8. The EXIT command returns control to your program after first restoring
all currency indicators to the state they were in when the debugger
was entered.
You may also exit by simply typing Control-Z at the NETDBG> prompt.