Trigger API Example


SUBROUTINE CUSTOMERS-CHECK(filevar , event , prerc , flags , recordkey , record , userrc )
INCLUDE JBC.h
COMMON /CUSTOMER_CHECK/ openflag , odfile , arfile , defile
*
* This is an example of using database triggers. To run this example, you need
* to have the following files :
*
* CUSTOMERS: The file to which the database triggers have been applied and that
* contains the actual customer records. We assume attribute 4 is the
* balance of the account, and we set and reset attribute 5, the
* overdrawn flag.
* OVERDRAWN: We use this to keep a list of customers who have gone overdrawn.
* ARCHIVE: We use this as a waste-bin to archive deleted customer records.
* DELETED: We keep a track of all deleted records in here
*
* The following commands should have been run against the CUSTOMERS file :
*
* create-trigger -at CUSTOMERS PREWRITE CUSTOMERS-CHECK
* create-trigger -t CUSTOMERS PREDELETE CUSTOMERS-CHECK
* create-trigger -t CUSTOMERS PRECLEAR CUSTOMERS-CHECK
*
* The parameters passed are :
* filevar: The file variable associated with the update. For example, you
* can do this 'WRITE var ON filevar,"newkey"' , however you must then
* be very careful of calling this subroutine recursively !
* event: One of the TRIGGER_TYPE_xxx values to show which of the 6 events is
* currently about to take place. Defined in source JBC.h .
* prerc: The current return code (i.e. status) of the action. For all the
* TRIGGER_TYPE_PRExx events, it will be 0. For all the TRIGGER_TYPE_POSTxx
* events, it will show the current status of the action, with 0 being the
* action was performed successfully and any other value showing the update
* failed. For example, if a WRITE fails because the lock table is full, the
* value in prerc is 0.
* flags: Various flags to show things like if a WRITE or WRITEV was performed. Not
* used yet.
* recordkey: The record key (or item-id) of the WRITE or DELETE being performed. For
* CLEARFILE, this is set to ""
* record: For the WRITE actions, this is the record currently being updated. For the
* DELETE or
* CLEARFILE actions, this is set to "". You can modify this variable if you
* wish. However the changes will be thrown away unless the 'create-trigger'
* command was run with the -a option.
* userrc: You can set this to a non-zero value for the TRIGGER_TYPE_PRExxx actions
* so that it will abort the action. However, unless the -t option was used
* with the 'create-trigger' command, it will be meaningless. There are two
* options to setting this value :
* (a) Any negative value will cause the action to be terminated. However,
* nothing will be flagged to the application, and it will appear to all
* intents and purposes that the action performed.
* (b) Any positive value is taken to be the return code for the action. For
* example, when a WRITE completes it will normally give a return code of 0.
* If this variable is then set to say 13 (which is the Unix error number for
* 'Permission denied') then the application will fall into the jBASE
* debugger with error code 13.
*

* In this subroutine we use a number of alternative flags.
* In order to prevent us continually opening and closing the
* files as we enter and exit the subroutine, we will do it
* just the once in this application by using named common variables.
*

    IF NOT(openflag) THEN

        OPEN 'OVERDRAWN' TO odfile ELSE
            PRINTERR 201:CHAR(254):'OVERDRAWN'
            userrc = 2 ;* Set up an appropriate return code
            RETURN
            END
        OPEN 'ARCHIVE' TO arfile ELSE
            PRINTERR 201:CHAR(254):'ARCHIVE'
            userrc = 2 ;* Set up an appropriate return code
            RETURN
        END
        OPEN 'DELETED' TO defile ELSE
            PRINTERR 201:CHAR(254):'DELETED'
            userrc = 2 ;* Set up an appropriate return code
            RETURN
        END
        openflag = 1

    END

* Now to handle each of the actions possible, 3 of which we actually
* do something, 3 of which we have no code for.

    BEGIN CASE

    CASE event EQ TRIGGER_TYPE_PREWRITE ;* If this is a call BEFORE a WRITE is performed

* For the purposes of this example, we will assume that
* attribute 4 of the record is a balance field. It it has gone
* negative, we write a marked in the 'OVERDRAWN' field and set their
* negative flag in attribute 5.

* For this to work, the 'create-trigger' must have been run
* with the '-a' option so we can amend the record, and the -t option
* so we can terminate the update if we want (by setting 'userrc').

        balance = record<4> ;* Extract the current balance
        IF NUM(balance) THEN ;* Make sure we are looking at a numeric value
        IF balance GE 0 THEN
            record<5> = "" ;* Clear the 'negative balance' field.
        END ELSE
            record<5> = "1" ;* Set the 'negative balance' field.

* The customer has gone overdrawn. Add an entry in the
* file 'OVERDRAWN' in the item 'NEWIDS'.
* Read in the control record and update it.

            READU control FROM odfile,"NEWIDS" ELSE control = ""
                control<-1> = recordkey ;* Add the record key to the list of overdrawn.
                WRITE control ON odfile,"NEWIDS"
            END

        END

    CASE event EQ TRIGGER_TYPE_POSTWRITE ;* If call AFTER a WRITE has been performed

* Note: We ignore this action

CASE event EQ TRIGGER_TYPE_PRE_DELETE ;* If call BEFORE a DELETE is performed

*

* The user is going to delete a customer record.
* Stop this happening unless the balance is zero !
* Note that the value for 'record' is currently "" , so we
* have to read in the current value ourselves !

        READ newrecord FROM filevar,recordkey ELSE

* Trying to delete something that doesn't exist ! So, ignore this

            RETURN
        END

        balance = newrecord<4>
        IF NUM(balance) AND balance NE 0 THEN
            CRT "ERROR! You can only delete customer ":recordkey:" if they"
            CRT " have a zero balance -- please retry the operation!"
            userrc = 2 ;* Sets the error code
            RETURN
        END

* We want to keep a list of customer records deleted in a separate
* file. This includes copying the customer record for archive
* purposes and keeping a log of deleted records.

        arkey = "CUSTOMERS*":TIME():"*":DATE():recordkey ;* The key to write to archive.
        WRITE newrecord ON arfile , arkey ;* Make archive of record before deleted

* Make up a record describing this record delete.

        hist = ""
        hist<1> = TIMEDATE() ;* The time and date in external format
        hist<2> = TIME() ;* The time of deletion in internal format
        hist<3> = DATE() ;* The date of deletion in internal format
        hist<4> = SYSTEM(18) ;* The port number it was deleted from
        hist<5> = SYSTEM(19) ;* The user name who deleted it.
        hist<6> = arkey ;* The name of the record key it was save in the 'ARCHIVE'.
        WRITE hist ON defile,arkey ;* Write back these details

    CASE event EQ TRIGGER_TYPE_POST_DELETE ;* If call AFTER a DELETE performed

* Note: We ignore this action

    CASE event EQ TRIGGER_TYPE_PRECLEAR ;* If call BEFORE a CLEARFILE performed

* We are about to do a CLEAR-FILE on the CUSTOMERS file.
* We only allow this to go ahead if the users name is 'GREG', and even then
* we prompt to confirm this.

        IF SYSTEM(19) NE "GREG" THEN
            CRT "ERROR! A clear-file on the CUSTOMERS file can only be"
            CRT" performed by user GREG. Please see him to confirm this"
            userrc = 13 ;* Causes a 'Permission denied' message
            RETURN
        END

* Prompt user GREG to confirm he wants to clear the CUSTOMERS file.
* However, if we have stacked data, then we probably don't
* have a keyboard attached, and so we must abort this action.

        IF SYSTEM(10) THEN
            CRT "ERROR! A clear-file on the CUSTOMERS file cannot be performed"
            CRT " from an application with stacked data"
            userrc = 13 ;* Causes a 'Permission denied' message.
            RETURN
        END

* Similarly, don't allow this from a background process.

        IF SYSTEM(25) THEN
            CRT "ERROR! A clear-file on the CUSTOMERS file cannot be performed"
            CRT " from an application running in the background"
            userrc = 25 ;* Causes a 'Permission denied' message.
            RETURN
        END

* Now we can at last prompt user GREG.

        promptsave = SYSTEM(26) ;* Save the current value of the PROMPT string
        PROMPT "" ;* Amend the PROMPT to a NULL string
        CRT "GREG, are you SURE you want to clear the CUSTOMERS file ? ":
        INPUT ans
        IF OCONV(ans[1,1],"MCU") EQ "Y" THEN
            CRT "Okay, be it on your head !"
        END ELSE

* GREG does NOT want to carry on with the clear-file.
* Set the 'userrc' to -1, which means the clear-file will abort,
* but there will be no error message -- the application will
* believe it has completed okay.

            CRT "Good to know you've changed your mind"
            userrc = -1
        END

    CASE event EQ TRIGGER_TYPE_POSTCLEAR ;* Ifcall AFTER a CLEARFILE performed

* Note: We ignore this action

    END CASE

    RETURN


TRIGGERS