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.