Version 2.11 Feb 16, 1999


Sybercron


Introduction

The next evolution of Sybernet will allow you to deliver your stored procedures via E-mail, either directly by specifying an E-mail address in your form or URL request, or scheduled automatically on a recurring basis. The documentation that follows attempts to explain how all this works.

Mary Ann logs into Sybase every Monday morning. She does this by first selecting the URL to the Sybernet sign-on screen. She then enters her username and [Enigma] password. Then she selects the desired form screen from the Sybernet main menu. Now she enters her org number on this screen and clicks on the SUBMIT button. In a matter of only few seconds, her report appears on her screen.

There's a better solution: just send Mary Ann this report to her E-Mail address. Not only does this eliminate several redundant steps on her end, but also the job that does this can be scheduled when Sybase is not busy doing other work.

Unless you override the default Content-type, the results of your stored procedure are mailed with a Content-type of text/html. This means someone receiving this mail will want to have an E-mail program that recognizes this type. NetScape Navigator 3.0 and above is such a program.

Email

EMAIL is a reserved word in Sybernet that may be read or written. If specified in a stored procedure, it should either represent a valid E-mail address or a GROUPNAME in table GROUPS.

The following example illustrates how EMAIL may be specified in a URL request:

    select '<A HREF="Sybase.cgi$http..sp_html_who?@EMAIL=%22nospam@nospams.com%22>Click</A>'
	
Similarly, you may specify EMAIL in a POST request:

    select '<INPUT TYPE="TEXT" NAME="@EMAIL" VALUE="nospam@nospams.com">'

In addition to specifying EMAIL, you may optionally specify both the Subject and Sender of this mail. Procedures which have been registered with the Sybercron register facility will get the Subject automatically from TITLE in the CRON table. The Sender can be specified manually using the keyword FROM or automatically from the CRON table from the SENDER field.

The MacOS version of Sybernet does not allow EMAIL to be assigned in this fashion and will ignore any attempts to do so.

Table Cron

The following declarations represent the tables and indicies that are required to execute stored procedures on a recurring basis. You should not insert entries into table CRON directly. Instead, you should call the procedure sp_cron_insert to insert or update an entry in this table. Entries into the table GROUPS may be done safely without intervention, however.

    create table CRON
    (
        ROW_ID      numeric(9,0)    identity /* This allows us to update our cursor         */
    ,   PROCNAME    varchar(92)     not null /* Procedure Name (fully qualified)            */
    ,	TITLE       varchar(255)        null /* Procedure title (for subscriptions)         */
    ,	DESCRIPTION varchar(255)        null /* Description (for subscriptions)             */
    ,   GROUPTYPE   char(1)             null /* "Y" if recurse on EMAIL in GROUPS.          */

    ,   EMAIL       varchar(255)        null /* E-Mail Address (or GROUPNAME)               */
    ,   SENDER      varchar(255)        null /* E-Mail Address of sender                    */
    ,   SCRIPTNAME  varchar(255)        null /* Name of disk file (filename)                */
    ,   FORMAT      varchar(30)         null /* NONE,SUPPRESSED,RTF,EXCEL,TABULAR           */
    ,   FILTER      varchar(30)         null /* CGI,ECHO                                    */
    ,   CONTENTTYPE varchar(30)         null /* Mime Content-type (ie, text/html)           */
    ,	HOSTNAME    varchar(30)         null /* Hostname (server name)                      */
    ,   USERCODE    varchar(30)         null /* Username                                    */
    ,   PASSWORD    varchar(30)         null /* Password                                    */
    ,   PLATFORM    varchar(30)         null /* Platform name where Sybercron is running at */

    ,   TIMESTAMP   datetime            null /* The NEXT time I should run this             */
    ,   STARTTIME   datetime            null /* When I can start running this procedure     */
    ,   STOPTIME    datetime            null /* When I can stop running procedure           */

    ,   DATENAME    varchar(10)         null /* Month/Weekday/HOUR/WEEKDAY/DAY/MONTH/PERIOD */
    ,   DAY         int                 null /* 1-31 (or 1-28)                              */
    ,   HOUR        int                 null /* 0-23                                        */
    ,   MINUTE      int                 null /* 0-59                                        */
    )

    create unique index CRON_INDEX on CRON (ROW_ID)

Table CRON is obviously the more complicated of these structures. So complicated in fact that you should not attempt to insert entries into this table directly. Failure to do so may cause your stored procedure to fail when executed.

The following columns are required to identify your stored procedure:

Some columns are important for documentation:

Some of these columns are parameters passed to Sybernet:

Other columns determine when this procedure can be executed:

And the remaining columns determine how often your stored procedure should be executed:

Seems complicated, I know, but sp_cron_insert (described below) will help you create entries in this table in a very easy manner. As I said, inserting into this table directly is discouraged because your procedure may not run if the information in this table is invalid.

Cron Parameters

The table CRON_PARAMETERS are the parameters (if any) that are passed to your procedure when executed by Sybercron. Here's what this table looks like:

    create table CRON_PARAMETERS
    (
        ROW_ID      numeric(9,0)    not null /* Identifies which procedure this goes with   */
    ,   NAME        varchar(30)     not null /* Parameter name                              */
    ,   VALUE       varchar(255)    not null /* Parameter value                             */
    )
Although you may not use sp_cron_insert to insert values into this table, sp_html_cron_register will do this automatically for you.

You can safely add or delete entries in this table, but if you do, you'll want to make sure that ROW_ID, NAME, and VALUE are valid. An invalid ROW_ID will be ignored. An invalid NAME could cause the execution of your procedure to fail. All parameters, of course, must be either char or varchar.

        declare @ROW_ID int
        exec @ROW_ID = sp_cron_insert
		
            @PROCNAME = "http.dbo.sp_getstatus"
        ,   @EMAIL    = "PUBLIC"
        ,   @DATENAME = "MONTH"
		
        if @ROW_ID != -1
        insert CRON_PARAMETERS
        (
            ROW_ID
        ,   NAME
        ,   VALUE
        )
        values
        (
            @ROW_ID                  -- ROW_ID in table CRON
        ,   "@ORGCODE"               -- Parameter name
        ,   "024"                    -- Parameter value
        )

This example first inserts an entry in the CRON table. If the return result is not -1, the appropriate parameter name and value is added to CRON_PARAMETERS. When this procedure executes, @ORGCODE="024" is appended to its run statement.

Cron Groups

Table CRON_GROUPS is used for E-Mail aliases. It's very easy to use, but there are rules for adding and deleting entries. First, let's take a look at its declaration.

    create table CRON_GROUPS
    (
        GROUPNAME   varchar(30)     not null /* Name of group                                   */
    ,   EMAIL       varchar(255)    not null /* An E-Mail address (may not be a groupname)      */
    ,   TIMESTAMP   datetime            null /* The datetime this entry added to /etc/aliases   */
    )

The entries in this table must correspond to aliases in the UNIX file /etc/aliases. As such, there must be some way to determine when /etc/aliases should be rewritten. The stored procedure
sp_html_cron examines this table for NULL values of TIMESTAMP. NULL values mean this table has changed; therefore, you never want to insert non-null values for this field.

You can safely insert new entries into this table manually, but it is important that if you do you make sure that TIMESTAMP is NULL.

        insert GROUPS
        (
            GROUPNAME
        ,   EMAIL
        ,   TIMESTAMP
        )
        values
        (
            "group1"                     -- Name of group
        ,   "nospam@nospams.com"         -- E-Mail address
        ,   null                         -- Must be null
        )
		
You can also update entries in this table, but (again) you want to set TIMESTAMP to NULL so that the /etc/aliases file can be rewritten.

        update GROUPS
        set    GROUPNAME = "group2"
        ,      TIMESTAMP = NULL
        where  EMAIL     = "nospam@nospams.com"
		
And rows should never be deleted directly. Instead, simply set the EMAIL column to NULL (to indicate that this row is to be deleted) and TIMESTAMP to NULL (to indicate that the table has changed).

        update GROUPS
        set    TIMESTAMP = NULL
        ,      EMAIL     = NULL
        where  EMAIL     = "nospam@nospams.com"
		
When sp_html_cron runs and /etc/aliases is rewritten, this procedure will delete rows that have null EMAIL and TIMESTAMP columns.

It is also safe to set all TIMESTAMPs to NULL to force a rewrite of /etc/aliases.

sp_cron_insert

sp_cron_insert is a stored procedure that allows you to create valid entires in the CRON table. Its use is encouraged over inserting entries in the CRON table directly.

sp_cron_insert allows you to specify only those columns that you are interested in. With some intelligence, the remaining columns are calculated automatically! You can even update entries in the CRON table with this procedure when ROW_ID is not null.

sp_cron_insert returns -1 if the insert failed or the ROW_ID of this entry if the insert succeeded.

Let's take a look at a few examples.

The first example illustrates how you would insert an entry in table CRON that is executed every Christmas at 8 o'clock in the morning:

    exec sp_cron_insert
	
        @PROCNAME   = "http.dbo.sp_html_xmas"
    ,   @EMAIL      = "nospam@nospams.com"
    ,   @DATENAME   = "DECEMBER"
    ,   @DAY        = "25"
    ,   @HOUR       = "8"
	
The next example runs sp_html_who every Monday at noon. This example also specifies both a STARTTIME and STOPTIME that limits the execution of this procedure for the year 1997 only.
    exec sp_cron_insert
	
        @PROCNAME   = "http.dbo.sp_html_who"
    ,   @EMAIL      = "nospam@nospams.com"
    ,   @STARTTIME  = "JANUARY 1, 1997"
    ,   @STOPTIME   = "DECEMBER 31, 1997"
    ,   @DATENAME   = "MONDAY"
    ,   @HOUR       = "12"
	
The next example runs the same procedure every day at 8:45 AM. Notice that in this example TIMESTAMP is specified (and not calculated automatically). This allows us to run the procedure immediately (assuming TIMESTAMP is less than or equal to getdate()).
    exec sp_cron_insert
	
        @PROCNAME   = "http.dbo.sp_html_who"
    ,   @EMAIL      = "nospam@nospams.com"
    ,   @TIMESTAMP  = "December 24, 1996"
    ,   @DATENAME   = "DAY"
    ,   @HOUR       = "8"
    ,   @MINUTE     = "45"
	
PROCNAME may also be a UNIX executeable or interpreter file. Sybercron distinguishes these names from stored procedure because they begin with either a period (.) or a slash (/); for example to execute the C program HelloWorld in your cgi-bin directory you could use the following statement:
    exec sp_cron_insert

        @PROCNAME   = "./HelloWorld"          -- A C program
    ,   @SCRIPTNAME = "../htdocs/datafile"    -- required

Notice that SCRIPTNAME was specified in this example. Sybercron will not execute any procedures or C programs unless you specify either an E-Mail address (EMAIL) or a file name (SCRIPTNAME). Specifying an E-Mail address doesn't make any sense in this example because executing a C program and sending mail are mutually exclusive. You could argue, I supppose, that the output from stdout should be mailed to the specified address, but this is not how it works. If you want to send mail in your C program, call sendmail.

Since TIMESTAMP and DATENAME were not specified, sp_cron_insert creates an entry in the CRON table that will be executed immediately and then deleted.

Parameters to your C program are gotten from the table CRON_PARAMETERS. Each VALUE in this table is passed to your program in its argv array. Parameter values in CRON_PARAMETERS should also not contain \n because this character is used to delimit these parameters when they are sent to Sybercron on UNIX.

It is important to specify an explicit path and filename when executing scripts in this fashion because you do not know the path where Sybercron is executing from. Permissions are important too; if Sybercron cannot read this file, it will be unable to execute this command.

./Syberload is a built-in function that allows you to upload UNIX files into Sybase. Files uploaded with this mechnism may contain any Transact-SQL statements and may be batched with the keyword GO.

The following example illustrates how to upload a Sybase script file:

    begin transaction
    exec @ROW_ID = http..sp_cron_insert
    
        @PROCNAME = "./Syberload"             -- The built-in function Syberload.
    ,   @SCRIPTNAME = "/dev/null"             -- I need to give it some name
    ,   @PLATFORM = "SPOCK"                   -- From where should this be executed.
    
    if @ROW_ID != -1
    insert http..CRON_PARAMETERS
    (
        ROW_ID
    ,   NAME
    ,   VALUE
    )
    values
    (
        @ROW_ID
    ,   "arg1"
    ,   "/home/denis/sybase/scriptfile"       -- Name of Sybase script file.
    )

    if @ROW_ID != -1
    insert http..CRON_PARAMETERS
    (
        ROW_ID
    ,   NAME
    ,   VALUE
    )
    values
    (
        @ROW_ID
    ,   "arg2"
    ,   "nospam@nospams.com"                  -- An e-mail address or filename for sending the results to.
    )

    if @ROW_ID != -1
    insert http..CRON_PARAMETERS
    (
        ROW_ID
    ,   NAME
    ,   VALUE
    )
    values
    (
        @ROW_ID
    ,   "arg3"
    ,   "text/html"                           -- Content-type (if arg2 is an e-mail address).
    )

    if @ROW_ID != -1
    insert http..CRON_PARAMETERS
    (
        ROW_ID
    ,   NAME
    ,   VALUE
    )
    values
    (
        @ROW_ID
    ,   "arg4"
    ,   "Here are your results"               -- Subject of e-mail (if arg2 is an e-mail address).
    )
	
    commit transaction
    return @ROW_ID                            -- Success if > -1.
It is important to specify an explicit path and filename when executing scripts in this fashion because you do not know the path where Sybercron is executing from. Permissions are important too; if Sybercron cannot read this file, it will be unable to execute this command.

The remaining arguments are optional. The second argument (arg2) can be either an e-mail address or the name of a disk file. It's an e-mail address if it contains an at-sign in its value. If it's a disk file name, the file is opened for append. If the second argument is not specified, the results of your script are written to the Sybernet log file.

The third argument (arg3) allows you to specify the content-type (text/html or text/plain), and the fourth argument (arg4) allows you to give a title for the subject of your e-mail.

All arguments are passed by order, not by name. You can't specify a Content-type unless you also specify a e-mail address, for example.

NOTE: Versions 2.18 and below of Sybercron do not recognize the optional attributes documented here.

sp_cron_insert_parameter

Inserts into the CRON table have always been discouraged while inserts into CRON_PARAMETERS was the only method available to pass parameters to your stored procedure or script. This procedure performs that ability. The advantage is that sp_cron_insert_parameter will update the VALUE part for the same NAME and ROW_ID rather than creating a new row. The disadvantage is that execute statements are not as flexible as insert statements; for example, it is not possible to create string expressions when calling a stored procedure.

    exec sp_cron_insert_parameter
	
        @ROW_ID = @ROW_ID
    ,   @NAME = "EMPLID"
    ,   @VALUE = substring(user_name(),2,5)
While tempting, such a construct is not allowed. Instead, you will need to create a temporary variable for VALUE:

    declare @VALUE varchar(30)
    select @VALUE = substring(user_name(),2,5)
    exec sp_cron_insert_parameter
	
        @ROW_ID = @ROW_ID
    ,   @NAME = "EMPLID"
    ,   @VALUE = @VALUE
If the call is successful, sp_cron_insert_parameter returns ROW_ID; otherwise, -1 is returned.

The ability to insert directly into CRON_PARAMETERS has not been removed.

sp_cron_wait

Sybercron is inherently an asynchronous operation. You call Sybercron (whether directly by calling sp_cron_insert or with the Sybercron Register which also calls sp_cron_insert) and then wait for it to do its thing.

It is now possible to call Sybercron and wait for its return result by calling the stored procedure sp_cron_wait. sp_cron_wait expects the ROW_ID returned from sp_cron_insert (and also the the ROW_ID passed to sp_cron_insert_parmater) as its only parameter; for example, the example above could be modified as follows:

    declare @ROW_ID int
    declare @RESULT int
    begin transaction
    exec @ROW_ID = http..sp_cron_insert

        @PROCNAME = "./shell2.csh"
    ,   @PLATFORM = "SPOCK"
    ,   @SENDER = "nospam@nospams.com"
    ,   @EMAIL = "nospam@nospams.com"

    if ( @ROW_ID != -1 )
    exec @ROW_ID = http..sp_cron_insert_parameter

        @ROW_ID
    ,   "argv1"
    ,   "nospam@nospams.com"

    if ( @ROW_ID != -1 )
    exec @ROW_ID = http..sp_cron_insert_parameter

        @ROW_ID
    ,   "argv2"
    ,   "ade-lj4"

    if ( @ROW_ID != -1 )
        commit transaction
    else
        rollback transaction
	
    exec @RESULT = http..sp_cron_wait @ROW_ID
	
    if @RESULT >= 0
        select "Success!  Your job completed successfully!"
    else
        select "Oops! Your job failed!"
Note that if you pass an invalid ROW_ID to sp_cron_wait, that ROW_ID will be returned back. In the example above, if the insert into CRON or CRON_PARAMETERS failed, sp_cron_wait would have returned -1.

Remember that your browser will timeout after 5 minutes without any response. Calling sp_cron_wait so that you can display this result is not always practical.

Example

A more practical use of this facility might be used to control a batch job initiated from Sybercron; for example, suppose you wanted to execute 3 stored procedures. The first two could be run asynchronously, but the third is dependent upon the successful completion of the first two. Here is how you would do that:

    exec @X1 = http..sp_cron_insert @PROCNAME = "sp_task_1"
    exec @X2 = http..sp_cron_insert @PROCNAME = "sp_task_2"

    exec @R1 = http..sp_cron_wait @X1
    exec @R2 = http..sp_cron_wait @X2

    if ( @R1 >= 0 and @R2 >=0 )
        exec sp_task_3
Some of the "details" have been omitted, but you get the idea.

Sybercron Register

sp_html_cron_register is a form-based utility that you may use to register your procedure in the CRON table. This procedure provides a convenient and safe way to add entries since it too calls sp_cron_insert to make sure that the columns specified are valid.

When sp_html_cron_register is invoked, three windows are built on your screen. They are the Directory Window, the Input Window, and the Output Window:

The Directory Window

The Directory Window contains a list of hypertext links of all procedures in your database. This list is sorted by name and grouped by owner.

At the top of this list is a hypertext link that identifies the current database. To change the current database simply click on this link. A menu of all databases on the system is displayed in this window. Just click on the database you want and a new directory will be created.

If there are any registered procedures in the current database, they will appear in this window. You can edit an existing entry by clicking on its hyper-text link. You can create a new entry by selecting a procedure name from the list that follows. You can also create a new entry by editing one of the registered procedures and clicking on the COPY button.

Clicking on the hyper-text link of a registered procedure will do one of two things: If the procedure name is unique--in other words, it appears in the CRON table only once--, selecting that procedure will take you to the detail edit for that procedure. If the procedure name is not unique, a summary window is invoked sorted by E-Mail address which displays the parameters passed to each invocation. Simply find the one you want to edit and click on its hyper-text link.

The Input Window

The Input Window is where you register your procedure. You need to select a procedure name from the Directory Window before it can be registered. That name should appear on the input form. The fields on this form are described above. The buttons on this form correspond to the following functions:

Except for the RELOAD button which updates the directory window, all results are sent to the input window.

The Output Window

The Output Window shows the results of any changes you've made including any error messages. If a procedure was successfully registered, its next execution date will be displayed.

sp_html_cron

sp_html_cron is called on a regular bases from UNIX, currently every 5 minutes. This procedures scans the CRON table looking for procedures that should be executed. This procedure uses a Better Late Than Never approach in this determination.

The first thing this procedure does it look for any TIMESTAMPs in table GROUPS that are in a NULL state. A NULL value for TIMESTAMP in any entry in table GROUPS means that the alias file (/etc/aliases) needs to be rewritten. If this is the case, a current list of aliases is returned to Sybercron.

If any procedures are ready for execution, its next TIMESTAMP is calculated and updated, and the run statement for this procedure is built. If for some reason the execution of this procedure should fail or an error result was returned by Sybase, it will not be re-executed until its next scheduled date is reached.

Sybercron

Sybercron is a UNIX daemon that periodically looks in Sybase for things to do. It does this by executing the procedure sp_html_cron. The results of this procedure will determine if /etc/aliases needs to be rewritten or a stored procedure is ready to execute. As long as any non-OK result is returned, Sybercron will continue to call this procedure until there are no more procedures to execute.

Sybercron was written before Sybernet was multi-tasking. This meant that Sybercron would distribute tasks among the available servers. Sybercron now takes advantage of Sybernet's multi-tasking ability and will initiate as many tasks as there are connections available.

Whereas the load used to be distributed, Sybercron now initiates tasks from the same port that Sybercron is suppose to listen at.

The previous limit for the number of asynchronous tasks initiated by Sybercron was 5 (the number of servers minus one). The current limit is now the maximum number of connections minus the number of active connections minus one. During peek periods, the average number of user connections (per server) is between 20 and 30. During unpeak periods, Sybercron now has the ability to execute about 60 tasks simultaneously.

Sybercron resides in your cgi-bin directory and recognizes the following command line arguments:

After successfully logging on to Sybase, Sybercron calls sp_html_cron. If the table CRON_GROUPS has been modified since it was last called, that information is returned and Sybercron will rewrite the alias file (/etc/aliases).

If a procedure is ready to execute, its run statement is returned and submitted for execution as a separate, asynchronous process. Sybercron will continue to call sp_html_cron until there are no more procedures ready to run.

C programs, on the other hand, are executed in the main thread. Future calls to sp_html_cron will not occur until that program has exited.

After all procedures ready for execution have been submitted, Sybercron sleeps until the next iteration.

The safest way to terminate Sybercron is by sending a -QUIT signal; for example,

kill -QUIT pid

Sending the quit signal allows the program to wait for all child processes to finish before terminating.


More Examples

Group Type

So far all of these examples addressed the problem of executing a stored procedure whose output is sent to one or more E-Mail address, but this only works when the output of that procedure is the same for each user. In practice, this may not be the case because each recipient should get a report tailored just for them or, more precisely, their E-Mail address.

GROUPTYPE helps Sybercron determine how to interpret E-Mail addresses in the GROUPS table. If specified as "Y", Sybercron will recurse on each E-Mail address in the CRON_GROUPS table, invoking the specified procedure once for each E-Mail address.

Pretend that the next three procedures have been registered with a GROUPTYPE of Y. These procedures will be executed once for each E-Mail address in the GROUPS table, and that E-Mail address is also passed to these procedures.

The E-Mail address passed to these procedures determines a range of orgcodes that this user is allowed see.

sp_html_envelope_1

    create procedure sp_html_envelope_1(@EMAIL_ADDRESS varchar(255) = NULL)
    as
    declare
        @ts_dt  varchar(10)         -- (current) week ending date
    ,   @org_id varchar(10)         -- org id   

        declare orgs cursor for select 
        select org_id
        from    http.dbo.CRON_EMAILS
        ,       deltek.deltek.USER_REPORTING_PRIVILEGE
        where   NAME = login_name and ADDRESS  = @EMAIL_ADDRESS
	
        /*
        ** Get the current week ending date
        */
        select  @ts_dt = convert(varchar,dateadd(weekday,-datepart(weekday,getdate())-7,getdate()),101)

        /*
        ** Step through our cursor for each org I can see and run report.
        */
        open orgs
        fetch orgs into @org_id
        if @@sqlstatus = 2
        begin
            select '<HTML>'
            select '<BODY>'
            select '<H2>I''m sorry...</H2>'
            select 'You are not listed to receive any Sold Time reports.<P>'
            if exists(select * from http.dbo.CRON_EMAILS where ADDRESS = @EMAIL_ADDRESS)
            begin
                select 'Although your E-Mail address and username in Sybase is valid, '
                select 'you have not been granted access to see any orgs.<P>'
            end else
            begin
                select 'You must have a valid username in Sybase that maps to your '
                select 'E-Mail address.  This is not the case so no reports can be '
                select 'created.<P>'
            end
            select '</BODY>'
            select '</HTML>'
        end
    
        while @@sqlstatus = 0
        begin
            exec http.dbo.run_sold_time_report @org_id, @ts_dt
            fetch orgs into @org_id
        end
        close orgs
        deallocate cursor orgs
        return
sp_html_envelope_1 simply executes Sold_Time_Report for each orgcode that is allowed for this user. The result is one E-Mail message containing those reports.

sp_html_envelope_2

    create procedure sp_html_envelope_2(@EMAIL_ADDRESS varchar(255) = NULL)
    as
    declare
        @ts_dt  varchar(10)         -- (current) week ending date
    ,   @org_id varchar(10)         -- org id
    ,   @ROW_ID int                 -- ROW_ID from insert.

        declare orgs cursor for 
        select  org_id
        from    http.dbo.CRON_EMAILS
        ,       deltek.deltek.USER_REPORTING_PRIVILEGE
        where   NAME = login_name and ADDRESS  = @EMAIL_ADDRESS

        /*
        ** Get the current week ending date
        */
        select  @ts_dt = convert(varchar,dateadd(weekday,-datepart(weekday,getdate())-7,getdate()),101)

        select '<HTML>'
        select '<HEAD><TITLE>Sold Time Report Scheduler</TITLE></HEAD>'
        select '<BODY>'

        /*
        ** Step through our cursor for each org I can see and run report.
        */
        open orgs
        fetch orgs into @org_id
        if @@sqlstatus = 2
        begin
            select '<H1>I''m sorry...</H1>'
            select 'You are not listed to receive any reports.<P>'
            if exists(select * from http.dbo.CRON_EMAILS where ADDRESS = @EMAIL_ADDRESS)
            begin
                select 'Although your E-Mail address and username in Sybase is valid, '
                select 'you have not been granted access to see any orgs.<P>'
            end else
            begin
                select 'You must have a valid username in Sybase that maps to your '
                select 'E-Mail address.  This is not the case so no reports can be '
                select 'created.<P>'
            end
        end else
        begin
            select '<H2>Your Sold Time Reports are being scheduled now.</H2>'
            select '<OL>'
        end
    
        while @@sqlstatus = 0
        begin
            exec @ROW_ID = http.dbo.sp_cron_insert
        
                @PROCNAME = "http.dbo.run_sold_time_report"     -- name of procedure
            ,   @EMAIL = @EMAIL_ADDRESS                         -- where it goes

            /*
            ** And put the parameters in the CRON_PARAMETERS table.
            */
            if ( @ROW_ID != -1 )
            exec @ROW_ID = http..cron_insert_parameters
            
                @ROW_ID = @ROW_ID
            ,   @NAME = "org_id"
            ,   @VALUE = @org_id
            
            if ( @ROW_ID != -1 )
            exec @ROW_ID = http..cron_insert_parameters
            
                @ROW_ID = @ROW_ID
            ,   @NAME = "ts_dt"
            ,   @VALUE = @ts_dt
        
            select '<LI>Org ID ' + @org_id + ' (' + @ts_dt + ')'
        
            fetch orgs into @org_id
        end
        close orgs
        deallocate cursor orgs

        select '</OL>'  
        select '</BODY>'
        select '</HTML>'
        return	
sp_html_enevlope_2 causes each report to be delivered as a separate E-Mail message. It does this by creating an entry in the CRON table that is submitted for immediate execution and deleted from the CRON table after it has been submitted.

sp_html_envelope_3

    create procedure sp_html_envelope_3(@EMAIL_ADDRESS varchar(255) = NULL)
    as
    declare
        @ts_dt  varchar(12)         -- (current) week ending date
    ,   @org_id varchar(10)         -- org id
    ,   @ROW_ID int                 -- ROW_ID from insert.
    ,   @SCRIPTNAME varchar(63)     -- Output filename.

        declare orgs cursor for 
        select  org_id
        from    http.dbo.CRON_EMAILS
        ,       deltek.deltek.USER_REPORTING_PRIVILEGE
        where   NAME = login_name and ADDRESS  = @EMAIL_ADDRESS

        /*
        ** Get the current week ending date
        */
        select  @ts_dt = convert(varchar,dateadd(weekday,-datepart(weekday,getdate())-7,getdate()),101)

        select '<HTML>'
        select '<HEAD><TITLE>Sold Time Report Scheduler</TITLE></HEAD>'
        select '<BODY>'

        /*
        ** Step through our cursor for each org I can see and run report.
        */
        open orgs
        fetch orgs into @org_id
        if @@sqlstatus = 2
        begin
            select '<H1>I''m sorry...</H1>'
            select 'You are not listed to receive any reports.<P>'
            if exists(select * from http.dbo.CRON_EMAILS where ADDRESS = @EMAIL_ADDRESS)
            begin
                select 'Although your E-Mail address and username in Sybase is valid, '
                select 'you have not been granted access to see any orgs.<P>'
            end else
            begin
                select 'You must have a valid username in Sybase that maps to your '
                select 'E-Mail address.  This is not the case so no reports can be '
                select 'created.<P>'
            end
        end else
        begin
            select '<H2>Your Sold Time Reports are being scheduled now.</H2>'
            select 'You may select these reported by pointing your browser '
            select 'to any of the following URLs.<P>'
            select '<UL>'
        end
    
        while @@sqlstatus = 0
        begin
            select @SCRIPTNAME = "../htdocs/" + @EMAIL_ADDRESS + @org_id + '.html'

            exec @ROW_ID = http.dbo.sp_cron_insert
        
                @PROCNAME = "http.dbo.run_sold_time_report"     -- name of procedure
            ,   @SCRIPTNAME = @SCRIPTNAME                       -- name of file.

            /*
            ** And put the parameters in the CRON_PARAMETERS table.
            */
            if ( @ROW_ID != -1 )
            exec @ROW_ID = http..cron_insert_parameters
            
                @ROW_ID = @ROW_ID
            ,   @NAME = "org_id"
            ,   @VALUE = @org_id
            
            if ( @ROW_ID != -1 )
            exec @ROW_ID = http..cron_insert_parameters
            
                @ROW_ID = @ROW_ID
            ,   @NAME = "ts_dt"
            ,   @VALUE = @ts_dt
		
            select '<LI><A HREF="HTTP://mis.sri.com/' + @EMAIL_ADDRESS + @org_id + '.html">'
            select 'ORG ' + @org_id
            select '</A>'        
            fetch orgs into @org_id
        end
        close orgs
        deallocate cursor orgs

        select '</UL>' 
        select '</BODY>'
        select '</HTML>'
        return

sp_html_enevelope_3 submits each report for immediate execution. Instead of sending the output to an E-Mail address, this example directs the output to a disk file. The name of each file is used to construct a hyper-text link to the location of that file. The user receives one E-Mail that allows them to pick and choose which report they actually want to see.

Note: This method of sending reports (however cool) is very insecure!


Sybercron Utilities

sp_print

Sybercron is not the easiest process to explain. The fact that it happens behind the scenes only complicates its explanation. If you are familiar with Sybercron, you know that it can send e-mail, create files, and run shell scripts, but it is not capable of doing more than one of these tasks at a time. But it is possible, however, to accomplish this feat by having Sybercron schedule itself in your stored procedure. sp_print is such a procedure.

sp_print is a stored procedure in sybsystemprocs that allows you to create a disk file and then print that file to a true printer device. It does all of this in one procedure call.

Here is the prototype of sp_print:

    create procedure sp_print
    (   @PROCNAME          varchar(92)                -- This is your stored procedure.
    ,   @PARAM1            varchar(255) = NULL        -- 1st unnamed parameter (optional)
    ,   @PARAM2            varchar(255) = NULL        -- 2nd unnamed parameter (optional)
    ,   @PARAM3            varchar(255) = NULL        -- 3rd unnamed parameter (optional)
    ,   @SHELLSCRIPT       varchar(255) = "lpr.csh"   -- Name of shell script.
    ,   @FILENAME          varchar(255) = NULL        -- Name of disk file (optional).
    ,   @DEVICENAME        varchar(30)                -- Printer name.
    ,   @SENDER            varchar(255)               -- Your e-mail address.
    )
PROCNAME is the name of your stored procedure. This procedure would either create HTML or plain text. PARAM1, PARAM2 and PARAM3 are 3 unnamed parameters that are optionally passed to your stored procedure. Do whatever you want with them.

SHELLSCRIPT is the name of the shell script that will print the output result of your stored procedure. You may write your own shell script or use one of the scripts already provided for this purpose:

Note that the html2ps converter is incapable of printing more than about 10 pages. It is also very sensitive to missing tags in your HTML; for example, omitting the ending cell tag (</TD>) in an HTML table will cause this routine to fail.

FILENAME is the name of the disk file that is created. If not specified, a unique filename will be invented.

DEVICENAME must be a valid device name from the table CRON_DEVICENAMES in the http database. Currently, the following device names are recognized:

SENDER is your e-mail address. Sybercron and the above scripts use this name to send e-mail in case your stored procedure fails or there is something wrong with your print file. This parameter is required.

All of the above scripts will remove your file as soon as it has been scheduled to print with lpr. LANDSCAPE.csh and PORTRAIT.csh are available on KIRK only.

Example

Assume we have a procedure that displays Hello World in HTML:
    create procedure sp_HelloWorld
    as
        select '<HTML>'
        ,      '<BODY BGCOLOR=WHITE>'
        ,      '<H1>Hello World</H1>'
        ,      '</BODY>'
        ,      '</HTML>'
        return 0
We can use sp_print to send the output of this procedure to the printer in AD136 with the following statement:

    exec sp_print

        @PROCNAME = "sp_HelloWorld"
    ,   @SHELLSCRIPT = "HTML2PS.csh"
    ,   @DEVICENAME = "hp5ad136"
    ,   @SENDER = "nospam@nospams.com"

Example

Something like Personal Action Reprints would be a good candidate for this facility, and since each print file is no more than a couple of pages, the html2ps converter would be a convenient facility to print these directly to a printer.

The procedure that actually creates the HTML would need two parameters: BUTTON which tells it what to do and EMPLNO which tells it who to do it to.

A cursor, a while loop, and a call to sp_print is about all you will need to accomplish this:

    create procedure sp_schedule_PA_Reprints (@EFFDT datetime)
    as
        declare @EMPLID char(12)

        declare people cursor for
        select EMPLID
        from   PS_JOB_CURRENT
        where  EFFDT >= @EFFDT

        open people
        fetch people into @EMPLID
        while @@sqlstatus = 0
        begin
            exec sp_print

                @PROCNAME    = "HRPRD.dbo.sp_html_reprint"
            ,   @PARAM1      = "REPORT" -- corresponds to BUTTON.
            ,   @PARAM2      = @EMPLID  -- corresponds to EMPLNO.
            ,   @SHELLSCRIPT = "HTML2PS.csh"
            ,   @DEVICENAME  = "hp4mv-ad236"
            ,   @SENDER      = "yamini@unix.sri.com"

            fetch people into @EMPLID
        end
        close people
        deallocate cursor people
        return 0
You would want to make certain that BUTTON and EMPLNO are the first two parameters to your stored procedure since these values are passed as unnamed string parameters.

Waiting on the ROW_ID returned from sp_print will wait for the completion status of the stored procedure. It does not wait on the completion status of HTML2PS.csh, however.

While useful in its own right, sp_print is also a great example on how to perform multiple tasks with Sybercron.

sp_Syberscript

The Sybercron Register only has the ability to schedule stored procedures. Until now, it has not been possible to register shell scripts because those are not stored procedures.

sp_Syberscript in the http database is a stored procedure that invokes a shell script. Simply register this procedure and pass the name of your script and any parameters to the parameters of this procedure. Sybercron will schedule it for repeated execution just like any other stored procedure.

Example

You can also call sp_Syberscript from a stored procedure. This is a convenient way to run a script without worrying about all the rules when calling sp_cron_insert.

    create procedure sp_StartScript
    as
        exec sp_Syberscript

                @SHELLSCRIPT = "./updateDirectoryServer.csh"
        ,       @PLATFORM    = "SPOCK"
        ,       @ARG1        = "ldif.update"
        ,       @ARG2        = "ldif.result"

        select '<HTML>'
        ,      '<BODY BGCOLOR=WHITE>'
        ,      '<H1>Your shell script has been started!</H1>'
        ,      '</BODY>'
        ,      '</HTML>'

        return 0
This example simply runs the shell script called updateDirectoryServer.csh on SPOCK and passes two parameters to it. sp_Syberscript calls sp_cron_insert and exits immediately with the ROW_ID returned by that procedure; it does not wait for the script to complete. You can wait yourself, however, by calling sp_cron_wait.

sp_sendmail

sp_sendmail provides a convenient mechanism for sending a "simple" e-mail message. Most of the time you will probably need something more elaborate than what is provided here, but for those instances when one or two lines will suffice, sp_sendmail will do the job.

The prototype for sp_sendmail is as follows:

    create procedure sp_sendmail
    (   @SUBJECT          varchar(255)                -- Subject:
    ,   @TO               varchar(255)                -- To:
    ,   @FROM             varchar(255)                -- From:
    ,   @MESSAGE          varchar(255)                -- Body:
    ,   @CONTENTTYPE      varchar(30) = "text/plain"  -- text/plain or text/html.
    ,   @TIMESTAMP        datetime    = NULL          -- When to deliver (NULL if now).
    )
The first four parameters should be obvious enough. CONTENTTYPE can be either "text/plain" or "text/html." Use the latter if you want to send HTML in your MESSAGE. TIMESTAMP allows you to defer the delivery of your e-mail message.

Example

The following example sends a "simple" plain-text message to myself:

    exec sp_sendmail

        @FROM = "nospam@nospams.com"
    ,   @TO = "nospam@nospams.com"
    ,   @SUBJECT = "This is some mail from sp_sendmail"
    ,   @MESSAGE = "Can you read this?"

sp_writetape

sp_writetape sends e-mail to operators@unix.sri.com and gives them instructions for creating a tape. Sybercron certainly has the ability to run a shell script that calls writetape, but operator intervention is required to mount a tape. Sending e-mail is the safest solution.

The prototype for sp_writetape is as follows:

    create procedure sp_writetape
    (   @SHELLSCRIPT      varchar(255)                 -- Name of shell script.
    ,   @SENDER           varchar(64)                  -- Your e-mail address.
    )
SHELLSCRIPT is the name of the shell script that the operator is to execute, and SENDER is your e-mail address so Sybercron can send you mail in the event of failure or the operator can send you mail when the tape has successfully been created.

Example

The following example is how the Vision Service Plan tape is created:

    exec sp_writetape

        @SHELLSCRIPT = "/usr/local/home/operator/vsptape.csh"
    ,   @SENDER      = "kenneth.bailey@sri.com"
The stored procedure that invokes this command does so after creating a disk file with Sybercron. The rest is up to the operator who receives the following e-mail:

    Subject: PLEASE HANG SCRATCH TAPE ON ASTRO TO WRITE
    From: kenneth.bailey@sri.com
    To: operators@unix.sri.com
 
    Please mount a scratch tape on Astro to write!

    Instructions: 

    1. Mount a scratch tape and note the serial number.
    2. /usr/local/home/operator/vsptape.csh BXXXXX

    where BXXXXX is the serial number of the tape.

    Then please send me a reply saying the tape was successfully written.

    Thanks from kenneth.bailey@sri.com.
The specified shell script uses writetape to write the tape. Note that the return status from writetape is interrogated, and the operator is instructed to restart this script if writing to the tape fails:

    #!/bin/csh

    # $1 serialno

    if ( $1 == "" ) then
        echo usage: $0 serialno
        echo Error: serialno expected
        exit 1
    endif

    cd /Sybernet

    #
    # Initialize globals...
    #
    set email      = kenneth.bailey@sri.com
    set LABELTYPE  = standard
    set SERIALNO   = $1
    set MAXRECSIZE = 242
    set BLOCKSIZE  = 242
    set UNITS      = characters
    set EXTMODE    = ebcdic
    set INTMODE    = ascii
    set VID        = SRI
    set FILENAME   = /Sybernet/document/vsptape.out

    umask 0
    set PROMPT = "nothing"
    echo ""
    echo "#PLEASE MOUNT SCRATCH TAPE ["$SERIALNO"] THEN ENTER OK TO TO GO"
    read $PROMPT

    echo "#WRITING "$FILENAME" TO TAPE..."
    /usr/local/bin/writetape                            \
        -LABELTYPE=$LABELTYPE                           \
        -SERIALNO=$SERIALNO                             \
        -MAXRECSIZE=$MAXRECSIZE                         \
        -BLOCKSIZE=$BLOCKSIZE                           \
        -UNITS=$UNITS                                   \
        -EXTMODE=$EXTMODE                               \
        -INTMODE=$INTMODE                               \
        -V=$VID                                         \
        $FILENAME                           

    if ( $status == "0" ) then
        echo ""
        echo "*******************************************************"
        echo "                         SUCCESS"   
        echo "*******************************************************"
        echo ""
        /usr/ucb/mail -s 'Tape was successfully created' $email < /dev/null
    else     
        echo ""
        echo "********************************************************"
        echo "                 WARNING - TAPE NOT CREATED"
        echo "********************************************************"
        echo "" 
        echo "#PLEASE RESTART IF PROBLEM WAS WITH TAPE DEVICE"
        echo ""
        /usr/ucb/mail -s 'OOPS - tape not created' $email < /dev/null
    endif

sp_permissions

This procedure can be used to save and restore permissions on any object in your database.  It was specifically designed for restoring permissions on a stored procedure after it has been dropped and recreated, but can also be used on any object.

Normally, permissions in the database are maintained by Dorothy Sloma  This means she will both grant and revoke permissions as requests are made.  When a programmer makes a change to a stored procedure, the procedure must be dropped before it can be recreated.  Dropping a procedure means you lose all permissions on this object.  When the permissions are not restored, this makes Dorothy extremely unhappy (not to mention the user who wants to execute this application).  sp_permissions was designed to eliminate this problem.

Here is the prototype for sp_permissions:

create procedure sp_permissions
(
    @BUTTON varchar(30)
,   @OBJECTNAME varchar(92)
)

BUTTON is one of SAVE or RESTORESAVE causes the current permissions to be captured.  RESTORE applies those permissions back again.  OBJECTNAME is the name of this object in your database.

In your script that recreates a stored procedure you would invoke this procedure at the beginning of your script to save this procedure's permissions and at the end of your script to restore them.

If your procedure does not compile, you do not need to do anything special.  The next time you call sp_permissions with BUTTON=SAVE, the procedure will detect that your procedure does not exist and will hold on to the last permissions that were valid.

Example

    use HRPRD 
    go 
    exec sp_permissions @BUTTON = "SAVE", @OBJECTNAME = "sp_html_example" 
    go 
    drop procedure sp_html_example 
    go 
    create procedure sp_html_example 
    as 
        /* 
        ** Main body of procedure. 
        */ 
        return 0 
    go 
    if object_id("sp_html_example") != NULL 
    begin 
        select "<BR>Restoring permissions:<P>" 
        select "<SEND_PARTIAL>" 
        exec sp_permissions @BUTTON = "RESTORE", @OBJECTNAME = "sp_html_example" 
    end 
    go 

The test on object_id is not required.  If your procedure did not compile, sp_permissions will know this and not do anything.

Note too sp_permissions calls sp_cron_wait to wait for Sybercron to complete.  It then displays the complete list of users or groups who have been granted execute permission on your stored procedure.


Sybercron Tables

Thanks Robbie!


Program Notes

Deadlocks

Sybercron would sometimes cause a deadlock situation when updating the table CRON_HISTORY. This resulted in your stored procedure failing to execute, and instead of delivering the output results of your stored procedure, the user would get a message saying that they were chosen as a dead-lock victim and should attempt to re-run your stored procedure.

This problem has been corrected.

Zombies

When one or more tasks completed at the same time, Sybercron would sometimes fail to recognize that all had finished and other tasks could be initiated. In some cases, Sybercron would not initiate any tasks until it was restarted. Other times, Sybercron might run only one task at a time.

It is interesting to note that this particular bug has only of late become problematic. There are now more tasks being initiated and the chances that two or more finished at the same time has simply aggravated the situation.

This problem has been corrected.

Empty Files

If a request to Sybernet attempted to connect a user, issue a command, and send mail or create a disk file all in one fell swoop, Sybernet would accidently create a zero-length file in /var/tmp. Previously, this error could go as long as 6 months before this directory needed to be purged. With the addition of the online PSR's, this problem as been aggravated with the numerous e-mail messages sent every Monday morning.

If you have not guessed, only Sybercron attempts to connect, issue a command and create e-mail in one fell swoop.

This problem has been corrected.


Sybernet is a trademark of SRI International.
Copyright © 1996-1999 SRI International. All Rights Reserved.
Denis D. Workman /
http://Sybernet.sri.com/