CHAPTER 3: Define UDF at the Kernel Level


NOTE:

If you are using PSM mechanism to create User Defined Functions, you may skip the rest of the chapters of this document. Defining UDF at the kernel level interface mechanism was provided since 1983, way before the existence of the Standard.

For those users who are customed to use this interface and for the new users who are interested in the internal implementations of the Persistent Stored Modules may proceed with the rest of the document.



3.1 Scope of the User Defined Functions

When a Query Language command is sent to Empress, the input is parsed and actions taken based on the keywords it contains. Empress allows you to add keywords to invoke your own C routines to perform these specialized operations.

Empress keeps a list of user defined keywords in user keyword tables. When an input line is parsed, the user keyword tables are searched in an attempt to identify the keyword. If the tables contain an entry for the keyword, the input line is parsed according to the syntax set for that keyword.

Empress allows User Defined Functions to be used in several places:

In the Empress SQL they can be used in:

Their usage is the same as Build-In Functions in Empress.

In the Empress 4GL they can be used in:

The usage is the same as Build-In Functions and called 4GL procedures in Empress.

In Empress Report Writer, they can be used in:

Their usage is the same as Build-In Functions in Empress.

In Empress Hypermedia - Internet Application Toolkit, they can be used in:

Their usage is the same as Build-In Functions in Empress.

User Defined Functions can also be used in C programs through expressions built with mr expression routines. (See Empress Host Language: C Kernel Level Interface mr Routines).



3.2 Classes of the User Defined Functions

There are two classes of User Defined Functions: Data Type (DT) and Standard C (SC). The Standard C User Defined Functions are called using an argc, argv convention. This class is created for the 4GL. For the Persistent Stored Modules, the parameters are used and always interface to the Data Type UDF mechanism.

The Standard C mechanism is always slower than the DT mechanism because the argc, argv interface is always ASCII; everything is converted to ASCII on calling the UDF and the UDF has to return ASCII which is re-converted to whatever data type it's supposed to be. The DT mechanism only converts if necessary.

Each class has its own Keyword Table and User Function Table. SC functions have higher priority than DT functions. A keyword that invokes a User Defined Function may contain entries for both DT and SC functions; but if so only the SC function will be invoked to evaluate the function result. The priority of DT and SC functions will be described in more detail in Priority of User Defined Functions.

User Defined Functions can operate on either tuples or aggregates. Simple tuple functions can be defined as DT or SC functions, but aggregate functions may only be defined as DT type functions.

In general, User Defined Functions of the simple tuple type can be defined either through the DT interface, or the SC interface. There are several more steps required to define DT type functions than SC functions. As well, there are more structures that must be referenced, and the level of difficulty in coding DT functions is greater than with SC functions. However, there are some significant advantages in writing DT type functions. Execution time is faster for DT type functions and there is much greater flexibility in the handling of null values and errors. Of great significance to the applications programmer is the fact that DT type functions allow for a much finer control over the data types of both the function's input and output.



3.3 User Defined Function Source Files

Three source files and a header file are provided with Empress distributions which make User Defined Functions easy to define. They are located in the Empress directory ($EMPRESSPATH) under rdbms/custom/src/usrfns and under include directories.

The source files under custom/src/usrfns directory are:

usrfns.c

is used to define DT type User Defined Functions.

usrstdfn.c

is used to define SC type User Defined Functions.

usrtabs.c

contains the tables which determine the priority of DT, SC and system math functions. Normally, this file does not need to be modified, but it is possible to do so.

The include (.h) file under include directory is:

usrfns.h contains User Keyword Table and User Function Table structure definitions.


3.4 Priority of User Defined Functions

Empress keeps a list of user defined keywords in user keyword tables. The user keyword tables are listed in the table usrktabs in the file usrtabs.c. When an input line is parsed, the user keyword tables are searched in an attempt to identify the keyword. If one of the tables contains an entry for the keyword, the search stops at the first instance of the keyword and based on the grammar rules for that keyword, further actions can then be taken.

The default search order of keyword tables in usrktabs is:

If the keyword is not encountered in any of these tables, the Empress internal keyword tables are then searched. The default search order (for user keyword tables) can be changed simply by changing the order of the entries in usrktabs.

If a keyword is located in one of the user keyword tables, the user function tables are then searched to locate the function entry. The default search order of user function tables is:

Entries in usrsftabs and usrxftabs may be modified and new entries may be added. The SC function tables have priority over DT function tables. This default priority can be changed by changing the order of entries in the table usrktabs in the file usrtabs.c.

Note that Empress keywords which are used to invoke functions may be redefined by creating an entry in one of the user keyword tables with the same name as the Empress keyword. All of the Empress keywords are listed in the file include/parseql.h. This file should be searched to ensure that a user defined keyword will not override an existing Empress keyword.

The relationship between the keyword tables and function tables is described in the following diagram:

Figure 3-1 Relationship between the Keyword Tables and the Function Tables



3.5 Create Standard C Functions

There are several structures which Empress references when an SC UDF is invoked. These structures are defined in the file usrfns.h in the include directory. The purpose of these structures is summarized in the following table:

Table 3-1: SC User Defined Function Structures

Structure Usage
keyitem Entries in the User Keyword Table (in array usrsktab for Standard C functions).
dtstdfn Entries in the User Function Table (in array usrstab).


The structures are listed and briefly described below:

   The keyitem structure defines entries for keywords added to Empress.

   typedef   struct
   {
             char     *keyname;    /* keyword name */
             int       keytag;     /* grammar rule for parsing the keyword */
   } keyitem;


   The dtstdfn structure defines entries for function definitions.

   typedef   struct
   {
             char    *fnName;          /* function/operator name */
             char    *(*fnFunc) ();    /* procedure */
             char     fnType;;         /* return type */
             int      fnArgs;          /* number of arguments     */
   } dtstdfn;

Schematically, the structures are related in the following way:


Figure 3-2 Relationship of the structure tables of the SC Type User Defined Function



The following sections illustrate the building of SC type UDFs. Each step of the procedure will be explained in detail. Argument and result value handling and returning error messages and null values will be discussed.

The example program below performs a factorial calculation. It accepts a single integer argument and returns a long integer result value. The procedure shows how a simple standalone program can be modified to work as a UDF.

   # include <stdio.h>

   main(argc, argv)
      int argc;
      char *argv[];
   {
      int num;
      num = atoi(argv[1]);
      num = num * factor(num -1);
      printf("factorial is %d\n", num);
   }

   long factor(n)
      int n;
   {
      if (n > 1)
      n = n * fact(n - 1);
      return(n);
   }

To create an SC type UDF, invoke system editor to edit usrstdfn.c under custom/src/usrfns directory and do the he following steps:

  1. Make an entry in the User Keyword Table usrsktab. The keyword invokes the function or operator from the Empress SQL (or Empress 4GL or Empress Report Writer or mr program).

    This entry specifies the grammar rules for the keyword, and in the case of an operator, precedence and number of operands.

  2. Make an entry in the User Function Table usrstab. This associates the keyword with a specification for the function or operator. If the function can be invoked with a variable number of arguments, the number of arguments is set to -1.

  3. Writing the actual C routine(s) is the last step. Arguments are handled using the (argc, argv) convention. Result values are returned as strings to the calling program. Null values and values that result from expression errors can not be handled by SC UDFs.

    Null values and customized error messages can be returned from the UDF.

  4. Compile and relink the Empress executables.

    SC type functions are automatically available to Empress SQL, Empress Report Writer and to C programs containing mr routines in Empress. SC functions can be made available in Empress 4GL. In the 4GL, SC functions can be used everywhere that 4GL functions can be used, except in call statements.

3.5.1 Defining the Keyword

The first step in defining an SC type UDF is to establish the name of the keyword which will invoke the function. This keyword may be a new name, or it may override an existing system keyword. In general, it is not recommended that system keywords be redefined unless the programmer is confident of the actions being taken.

An entry in the User Keyword Table is defined by the C structure keyitem in usrfns.h include file:

   typedef struct
   {
           char *keyname;    /* the name of the function/operator */
           int keytag;       /* specifies a grammar rule          */
   } keyitem;

For each keyword, an entry must be made in the table usrsktab in the file usrstdfn.c. An entry consists of two parts:

  1. The keyword (keyname) to be added.

    The name of the keyword must be entered as a quoted string, and the entries in the Keyword Table must be in ascending ASCII collating order.

  2. The keytag (keytag) specification for the function or operator.

    The keytag is a code that specifies a grammar rule for evaluating the keyword. The rule determines the following:

    The valid keytags are in the file parseql.h. This file should never be modified by the user. Please refer to Table 3-1 Keytag Specification in this chapter.

The following is an example of the SC User Keyword Table for the factorial calculation.

   keyitem usrsktab[] =
   {
           {     "!",       op1_xp      },  /* factorial operator */
           {     (char *)0              }
   };

It indicates that ! will be used as keyword for this function. For example, in SQL:

   SELECT (attr_name)! FROM table_name;

   SELECT * FROM table_name WHERE (attr_name)! > 2001;

There must be one entry in the usrsktab table for each keyword that is added. The last entry in the table must be null, i.e., (char *)0.

3.5.2 Making an Entry in the User Function Table

The next step in creating an Standard C UDF is to make an entry in the User Function Table usrstab in the file usrstdfn.c.

An entry in the User Function Table is defined by the C structure dtstdfn in usrfns.h include file:

   typedef struct
   {
           char    *fnName;        /* function/operator name */
           char    *(*fnFunc) ();  /* procedure */
           char     fnType;;       /* return type */
           int      fnArgs;        /* number of arguments */
   } dtstdfn;

Entries in this table consists of four parts:

  1. The keyword (fnFunc) used to invoke the function.

    It is entered as a quoted string and corresponds to the keyname in the User Keyword Table usrsktab.

  2. The name of the C routine that will be used to evaluate the function.

  3. A character to indicate the return type of the function.

    When specified in the User Function Table, this character value must be quoted using single quot ('). The program from which the UDF was invoked will convert the result value of the function to the appropriate return data type. The following are valid return type:

    qcharacter
    zdate
    ddecimal
    ffloat
    iinteger
    vnull

  4. The number of arguments to be passed to the function.

    If the function can accept a variable number of arguments, this should be set to -1. Note that user defined operators are described at this point in the same way as user defined functions. For unary operators, the number 1 should be entered in the User Function Table to indicate the number of expected operands, and for binary operators the number should be 2.


An example of this table containing entries for the functions whose keywords were defined above is:

   dtstdfn     usrstab[] =
   {
               { "!",      fact,  'i',     1     },     /* factorial operator*/
               (char *) 0
   };

Through this table, Empress will invoke a routine named fact in association with the keyword "!". It expects one argument. Since it is a SC function, a conversion will occur if the input data is not a character string. Another conversion to the generic integer value will occur upon receiving the return value since the return type is indicated as an integer ('i').

There must be exactly one entry in the usrstab table for each SC function or operator that is added. The last entry in the table must be null (i.e., (char *)0).

3.5.3 Writing the C Routine

To incorporate this routine into a UDF, the following should be noted:

  1. The return type of the routine must be declared char *. The argument list must be defined using the (argc, argv) argument handling convention.

    argc is an integer containing the number of arguments passed to the function plus one for the function name itself. argv is a pointer to a list of strings comprising the arguments passed. The value of argv[0] is the name of the function; argv[1] is the first argument, argv[2] the second argument, and so on, up to argv[argc -1].

    Regardless of the data types of the arguments passed to the function, they are converted to strings, so argv always references strings.

  2. The argument values must be converted (if necessary) within the function to the appropriate data type(s), for the purpose of performing arithmetic or whatever operations are required.

  3. The result value must be converted to a character string. The calling process must be able to convert the return value of the function to the data type specified in the User Function Table for that function.

  4. The C routine returns the character string to the calling process. To indicate that an error has occurred, the function should return the value CHARNIL.

The following is an example for the factorial routine fact:


   static  char *fact (argc, argv)          /* 1) char * declaration  */
           int      argc;                   /*    argc - argument count including
                                                  the name of the function        */
           char    *argv[];                 /*    argv - the arguments are stored
                                                  in an array of character arrays */
   {
      int num;
      static  char    result[30];
      extern  msbool  cvai();              /* Empress routine to convert string
                                               to integer                         */
      extern  char    *cvix();              /* Empress routine to convert integer
                                               to string                          */

      if (!cvai (&num, argv[1]))            /* 2) convert the argument to integer */
      {
         dtudferr("fact: cannot convert argument to integer");
         return (CHARNIL);                  /* if an error occurs return an
                                              appropriate error message     */
      }
      num = num * fact1(num -1);

      cvix(result, num);                    /* 3) convert the result to a string */

      return (result);                      /* 4) return (result) to indicate  */
                                            /*    successful completion of     */
                                            /*    the function                 */
   }

   static  long       fact1(n)
           int n;
   {
      if (n > 1)
      n = n * fact1(n - 1);
      return(n);
   }

The following is the complete listing of usrstdfn.c file for the above example:


   /***********************************************************************
    *		(c) Copyright	Empress Software Inc. 1983, 2003
    ***********************************************************************/
   
   /***********************************************************************
    *  WARNING: as of version 6.4, this file MUST be compiled with "empcc"
    *           (not "cc") in order to find the correct header files.
    ***********************************************************************/
   
   # include  <usrfns.h>
   
   /******************************************************************************
    *			USRSKTAB
    *
    *	Structure:	Table structure
    *
    *	Purpose:	contains the name and parsing information for each
    *			of the standard C user defined functions to be
    *			used from the QL level.
    *			Should be kept in ascending alphabetical order
    *			according to function name.
    *
    ******************************************************************************/
   keyitem	usrsktab[] =
   {
           {     "!",       op1_xp      },  /* factorial operator */
   	   {     (char *)0              }
   };
   
   int	usrzsktab = sizeof (usrsktab) / sizeof (keyitem) - 1;
   
   /******************************************************************************
    *
    *	Standard C User Defined Functions
    *		- using argc, argv style arguments
    *
    ******************************************************************************/
   
   /******************************************************************************
    *			FACTORIAL (!)
    *
    *	Purpose:	Illustrate the construction of a Standard C User
    *			Defined Function.  This function produces the factorial
    *			of a LONGINTEGER argument.
    *
    *	Arguments:	argc - number of arguments (must be 2)
    *			argv[0] or *argv - pointer to character string
    *				containing the argument.
    *
    *	Returns:	pointer to the character string to be printed.
    *
    ******************************************************************************/
   
   static  char *fact (argc, argv)          /* 1) char * declaration  */
           int     argc;                    /*    argc - argument count including
                                                  the name of the function        */
           char    *argv[];                 /*    argv - the arguments are stored
                                                  in an array of character arrays */
   {
      int num;
      static  char    result[30];
      extern  msbool  cvai();              /* Empress routine to convert string
                                               to integer                         */
      extern  char    *cvix();              /* Empress routine to convert integer
                                               to string                          */
   
      if (!cvai (&num, argv[1]))            /* 2) convert the argument to integer */
      {
         dtudferr("fact: cannot convert argument to integer");
         return (CHARNIL);                  /* if an error occurs return an
                                              appropriate error message     */
      }
      num = num * fact1(num -1);
   
      cvix(result, num);                    /* 3) convert the result to a string */
   
      return (result);                      /* 4) return (result) to indicate  */
                                            /*    successful completion of     */
                                            /*    the function                 */
   }
   
   static  long       fact1(n)
           int n;
   {
      if (n > 1)
      n = n * fact1(n - 1);
      return(n);
   }
   
   
   /******************************************************************************
    *
    *	The Standard C User Defined Functions tables and variables
    *
    ******************************************************************************/
   
   dtstdfn	usrstab[] =
   {
           { "!",      fact,  'i',     1     },     /* factorial operator*/
           (char *) 0
   };
   
   int	usrzstab = sizeof (usrstab) / sizeof (dtstdfn) - 1;


3.5.3.1 Handling a Variable Number of Arguments

The Standard C UDF interface supports variable number of arguments. The following example illustrates a function that can handle a variable number of arguments. This function will concatenate all the argument values into one string. To handle different numbers of arguments, the C routine can check the value of argc and perform different actions based on that value.

   static char *attach(argc, argv)
          int   argc;
          char *argv[];
   {
      static char buffer[256];
      int arg_num, position, arg_char;

      if (argc < 10)
      {
         position = 0;                               /* position in buffer */
         arg_char = 0;                               /* character position of current argument */

         for(arg_num = 1; arg_num < argc; arg_num++) /* for each argument copy the
                                                        characters into a buffer
                                                        - maximum size is 256  */
         {
            while (( argv[arg_num][arg_char] != '\0') && (position < 256) )
            {
               buffer[position] = argv[arg_num][arg_char];
               position++;
               arg_char++;
            }
            arg_char = 0;
         }
         buffer[position] = '\0';
         return(buffer);
      }
      else
      {
         dtudferr("attach: maximum of 8 arguments");
         return (CHARNIL);
      }
   }

For a function to be able to handle a variable number of arguments, the entry in the User Function Table for the function must be set to -1 for the number of arguments.

The entry in the User Function Table for the attach function is as follows:

   dtstdfn usrstab[] =
   {
               .
               .
               .

       { "attach", attach, 'q', -1     },

               .
               .
               .

       (char *) 0
   };

3.5.3.2 Returning Error Messages and Null Values

The procedure for returning error messages and null values is very simple.

As in the example for the fact function, to return an error message, the routine dtudferr() must be called, and the function must return CHARNIL. For example:

   else
   {
      dtudferr("fact: cannot convert argument to integer");
      return (CHARNIL);
   }

For every invocation of the function which generates this error condition, the following error message will be reported:

   *** User Error *** fact: cannot convert argument to integer

To return a null value to the calling process, the return statement in the C routine should simply return the empty string as in the following branch of code.

   else
   {
      return( "" );
   }

3.5.4 Compiling UDFs and Relinking the Executables

Once the file usrstdfn.c has been modified to include the necessary entries for defining a UDF, the following steps must be taken to make the UDFs available to Empress.

For UNIX systems:

  1. cd $EMPRESSPATH/rdbms/custom/src/usrfns
  2. empcc -c usrstdfn.c (produces usrstdfn.o)
  3. ar rv $EMPRESSPATH/rdbms/lib/stdudf.a usrstdfn.o (archive the object file in $EMPRESSPATH/rdbms/lib/stdudf.a)
  4. ranlib $EMPRESSPATH/rdbms/lib/stdudf.a (randomize the library; this step is not necessary on all machines)
  5. cd $EMPRESSPATH/rdbms/conf_bin
  6. mkexec -s (rebuild all Empress executables)
    or
    mkexec -s empsql emp4ful emp4fulg emp4rt emp4rtg (or rebuild the specific executables)

For Windows NT:

  1. cd %EMPRESSPATH%\rdbms\custom\src\usrfns
  2. mklib


3.6 Create Data Type Functions

There are several structures which Empress references when a DT UDF is invoked. These structures are defined in the file usrfns.h in the include directory. The purpose of these structures is summarized in the following table:

Table 3-2 DT User Defined Function Structures

Structure Usage
keyitem Entries in the User Keyword Table (in array usrxktab for DT functions).
dtxdes Entries in the User Function Table (in array usrxtab).
dtxades Entries in Function Specification Table (one array for each new function).
dtxpdes Entry in Function Procedures Table (list all five actual pointers which correspond to five separate procedures for each UDF).
dtpar Data type specification (for input arguments and result).
dtxd Input arguments/operands and result of function/operator.
dtxr Identifies the C routines and contains pointers to input arguments/operands and result.


The structures are listed and briefly described below:

   The keyitem structure defines entries for keywords added to Empress.

   typedef   struct
   {
             char     *keyname;       /* keyword name */
             int       keytag;        /* grammar rule for parsing the keyword */
   } keyitem;


   The dtxdes structure defines entries for function definitions.

   typedef   struct
   {
             char        *dtxname;       /* function/operator name */
             int          dtxnargs;      /* number of arguments*/
             dtxades     *dtxpades;      /* pointer to specification table */
             int          dtxinput;      /* argument handling flags */
             msbool       dtxaval;       /* aggregate function flag */
             int         *dtxargmodes;   /* array of argument modes;
                                            NIL means all input only */
   } dtxdes;


   The dtxades structure defines entries for the function I/O table.

   typedef   struct
   {
             dtxpdes     *dtxprocs;   /* pointer to procedures tables */
             void        *dtxparam;   /* parameter to procedures */
             dtpar       *dtxresult;  /* result data type */
             dtpar      **dtxargs;    /* argument data types */
   } dtxades;


   The dtxpdes structure defines entries for Function Procedures Tables.

   typedef   struct
   {
             msbool     (*dtxifns)();        /* operator init procedure */
             msbool     (*dtxigrp)();        /* begin group procedure */
             msbool     (*dtxrfns)();        /* run operator procedure */
             msbool     (*dtxtgrp)();        /* end group procedure */
             msbool     (*dtxtfns)();        /* operator cleanup procedure */
   } dtxpdes;


   The dtpar structure defines entries for data type identifiers.

   typedef   struct
   {
             int       dttypeid;     /* type identifier */
             int       dtpar1,       /* parameters */
                       dtpar2,
                       dtpar3,
                       dtpar4;
             void     *dtpfptr;      /* pointer for file operations */
   }    dtpar;


   The dtxr structure provides access to the function arguments or operands and
   the result of the function or operator.

   typedef   struct
   {
             struct  dtxr    *dtxrnext;                /* next operator */
             dtxpdes         *dtxrprocs;               /* pointer to procedures */
             msbool           (*dtxrrun)();            /* duplicate pointer to rfns */
             void            *dtxrparam;               /* parameter to procedures */
             unsigned int     dtxrnargs : BITSPERBYTE;  /* number of arguments */
             unsigned int     dtxraval : 1;             /* a-valued operator */
             unsigned int     dtxrdval : 1;             /* delayed operator */
             unsigned int     dtxrnullok : 1;           /* procedures handle nulls */
             unsigned int     dtxrerrok : 1;            /* procedures handle errors */
             unsigned int     dtxrconvt : 1;            /* conversion operator */
             unsigned int     dtxrufunc : 1;            /* M-Builder type operator */
             unsigned int     dtxrifns : 1;             /* was ifns run ? */
             void            *dtxrwksp;                 /* workspace */
             dtxd            *dtxrresult;               /* the result */
             dtxd            *dtxds[1];                 /* the operands */
                                                        /* space for operands */
   } dtxr;


   The dtxd structure provides access to the function arguments or operands and
   contains other important information about the function arguments.

   typedef   struct
   {
             dtpar         *dtxddtp;                 /* the data type */
             unsigned int   dtxdnull : BITSPERBYTE;  /* null (run-time) type */
             unsigned int   dtxdtype : BITSPERBYTE;  /* operand (static) type */
             unsigned int   dtxdanydt : 1;           /* opr will handle any dt */
             unsigned int   dtxdconsflag : 1;        /* constant ? */
             unsigned int   dtxdcvar : 1;            /* control var used  ? */
             unsigned int   dtxdnucheck : 1;         /* check for null ? */
             unsigned int   dtxdresult : 1;          /* result operand ? */
             unsigned int   dtxdgsbuf : 1;           /* generic string buffer ? */
             unsigned int   dtxdlinkcount : 2;       /* link count */
             void          *dtxdnuval;               /* the null value */
             long           dtxdzval;                /* size of value */
             void         **dtxdival;                /* indirect pointer to value */
             void          *dtxdval;                 /* pointer to value */
   } dtxd;

Schematically, the structures are related in the following way:


Figure 3-3 Relationship of the structure tables of the DT Type User Defined Function



The following sections illustrate the building of DT type UDFs. Each step of the procedure will be explained in detail. At the end of this section, error and null value handling will be discussed.

The example program below performs a exponential calculation. It accepts a float argument and returns a float result value. The procedure shows how a simple standalone program can be modified to work as a UDF.


   #include    <math.h>
   #include    <stdio.h>
   #include    <stdlib.h>
   
   main (int argc, 
         char** argv)
   {
      double  x;
      double  y;
      double  res;
   
      if (argc != 3)
         {
            printf("USAGE: expon base power\n");
            exit (0);
         };      
      sscanf (argv[1], "%lf", &x);
      sscanf (argv[2], "%lf", &y);
   
      res = pow (x,y);
           
      printf("%lf^%lf = %lf\n", x, y, res);
   }

To create a DT type UDF, invoke system editor to edit usrfns.c under custom/src/usrfns directory and do the following steps:

  1. Make an entry in the User Keyword Table usrxktab. The keyword invokes the function or operator from the Empress SQL (or Empress 4GL or Empress Report Writer or mr program).

    This entry specifies the grammar rules for the keyword, and in the case of an operator, precedence and number of operands.

  2. Make an entry in the User Function Table usrxtab. This associates the keyword with a specification for the function or operator. If the function can be invoked with a variable number of arguments, separate entries must be made in this table for each case.

  3. Create a Function I/O Table for each entry in the User Function Table. This table identifies the Function Procedures Table, the data type(s) of the input arguments or operands, and the data type of the return value.

  4. Create a Function Procedures Table for each entry in the Function I/O Table. This table contains pointers to the actual C routines that evaluate the UDF.

  5. Create Data type identifiers to represent the input/output data types of the UDF. These are specified in terms of an Empress internal code.

  6. Writing the actual C routine(s) is the last step. Arguments are handled through structures. Additional parameters (optional) can be passed to the C routines through the function argument list.

    Checking for null and error values can be handled either by Empress or explicitly by the user.

    Null values and customized error messages can be returned from the UDF.

    Additional routines and variables are available for performing data conversion and space allocation.

  7. Compile and relink the Empress executables.

    DT type functions are automatically available to Empress SQL, Empress 4GL and to C programs containing mr routines in Empress. In the 4GL, DT functions can be used everywhere that 4GL functions can be used, except in call statements.

3.6.1 Defining the Keyword

The first step in defining a DT type UDF is to establish the name of the keyword which will invoke the function. This keyword may be a new name, or it may override an existing system keyword. In general it is not recommended that system keywords be redefined unless the programmer is confident of the actions being taken.

An entry in the User Keyword Table is defined by the C structure keyitem in usrfns.h include file:

   typedef struct
   {
           char *keyname;    /* the name of the function/operator */
           int   keytag;     /* specifies a grammar rule          */
   } keyitem;

For each keyword, an entry must be made in the table usrxktab in the file usrfns.c. An entry consists of two parts:

  1. The keyword (keyname) to be added.

    The name of the keyword must be entered as a quoted string, and the entries in the Keyword Table must be in ascending ASCII collating order.

  2. The keytag (keytag) specification for the function or operator.

    The keytag is a code that specifies a grammar rule for evaluating the keyword. The rule determines the following:

    The valid keytags are listed in the file parseql.h. This file should never be modified by the user.

    Table 3-3: Keytag Specifications

    Keytag Type Arguments Result Also Specifies
    op2_b2 binary operator boolean boolean precedence 2
    op2_b1 binary operator boolean boolean precedence 1
    op1_b unary operator boolean boolean
    op2_c binary operator arithmetic boolean comparison
    op2_ceq binary operator arithmetic boolean equality comparison
    op2_x6 binary operator arithmetic arithmetic precedence 6
    op2_x5 binary operator arithmetic arithmetic precedence 5
    op2_x4 binary operator arithmetic arithmetic precedence 4
    op2_x3 binary operator arithmetic arithmetic precedence 3
    op2_x2 binary operator arithmetic arithmetic precedence 2
    op2_x1 binary operator arithmetic arithmetic precedence 1
    op2_xr binary operator arithmetic arithmetic right associative
    op1_xp unary operator arithmetic arithmetic postfix
    op1_x unary operator arithmetic arithmetic prefix
    opf_b function any # > 0 boolean
    opf0_b function none boolean
    opf_x function any # > 0 arithmetic
    opf0_x function none arithmetic
    opf_ax aggregate function any # > 0 arithmetic

The following is an example of the DT User Keyword Table containing keyword for the exponential function.

   keyitem usrxktab[] =
   {
           { "**",      op2_x1        },   /* binary exponential operator */
                                           /* with precedence 1           */
           { (char *)0               }
   }

The keyword list must be sorted in ascending ASCII collating order. Both tuple and aggregate functions are listed in this table. There must be one entry in the usrxktab table for each keyword that is added. The last entry in the table must be null, i.e., (char *)0.

3.6.2 Making an Entry in the User Function Table

The next step in creating a DT UDF is to make one or more entries in the User Function Table usrxtab in the file usrfns.c.

An entry in the User Function Table is defined by the C structure dtxdes in usrfns.h include file:

   typedef  struct
   {
            char      *dtxname;     /* the name of the function/operator */
            int        dtxnargs;    /* # of arguments the function/operator takes */
            dtxades   *dtxpades;    /* pointer to the function spec table */
            int        dtxinput;    /* indicates whether the function handles nulls 
                                       and/or errors as arguments */
            msbool     dtxaval;     /* TRUE for an aggregate function     */
                                    /* FALSE if not an aggregate function */
            int       *dtxargmodes; /* array of argument modes;
                                       NIL means all input only */
   }  dtxdes;

Entries in this table consists of five parts:

  1. The keyword (dtxname) used to invoke the function.

    It is entered as a quoted string. The name corresponds to the keyname in the User Keyword Table usrxktab.

  2. The number of arguments (dtxnargs) required to invoke the function.

    Note that user defined operators are described at this point in the same way as user defined functions. For unary operators, the number 1 should be entered in the User Function Table to indicate the number of expected operands, and for binary operators the number should be 2.

  3. A pointer to the function I/O table (dtxpades) for the function or operator.

    There must be a unique Function I/O Table for each entry in the User Function Table.

  4. A variable (dtxinput) to indicate whether nulls and/or errors are valid input arguments for the function.

    The possible values for this field are:

    NNEN indicates "Null NO Error NO". This means the function will not handle null value and no error checking.
    NNEY indicates "Null NO Error Yes". This means the function will not handle null value but does error handling.
    NYEN indicates "Null Yes Error NO". This means the function will handle null value but no error checking.
    NYEY indicates "Null Yes Error Yes". This means the function will handle null value and error checking.

  5. A flag (dtxaval) to indicate whether the function is an aggregate (value set to TRUE) or tuple function (value set to FALSE).

  6. The example of this table for the exponential function whose keyword was defined above is:

       dtxdes usrxtab[] =
       {
              { "**",         2,     expontab,     NNEN,          false     },
              { (char *)0                                                   }
       };
    
    

    There must be at least one entry in the usrxtab table for each DT function or operator that is added. The last entry in the table must be null (i.e., (char *)0).

    3.6.3 Creating the Function I/O Table

    For each UDF that is added, it is necessary to create a Function I/O Table. If the function can be invoked with a variable number of arguments, there must be a separate Function I/O Table for each case.

    An entry in the Function I/O Table is defined by the C structure dtxades in usrfns.h include file:

       typedef struct
       {
               msbool  (*dtxproc)();      /* the name of the function procedures table */
               addr     dtxparam;          /* second parameter to the routine */
               dtpar   *dtxresult;         /* data type of the result     */
               dtpa   **dtxargs;           /* data type of the arguments */
       } dtxades;
    
    

    Each entry in the Function I/O Table consists of four parts:

    1. A pointer (dtxproc) to the Function Procedures Table structure (dtxpdes) that identifies the C routines that will be executed when the function is invoked.

    2. The optional second parameter (dtxparam) to the C function that will be passed as an argument to the function when it is invoked.
    3. A pointer (dtxresult) to a Data Type Identifiers structure (dtpar) of the result value of the C function.

    4. An array of pointers (dtxargs) to a Data Type Identifiers structure (dtpar) of the argument(s) data type to the C function.

    An example entry for the expon function could be made as follows:

       dtxades expontab[] =
       {
               { & expontabproc[0], (addr)0,  & dtgflt,  gfltgflt  },
               { NULLENTRY                                         }
       };
    
    

    For the expon function, there must be two entries in the function I/O table, to handle the 2 cases of different argument data types (i.e., dist() can be invoked with either bulk or integer arguments). The result data type of the function (dtgint) is the same in either case.

    The procedure for defining the data type identifiers for the arguments/results is described in the next section.

    3.6.4 Defining Data Type Identifiers

    Data Type Identifiers describe the data types of the arguments/operands for the function or operator and the data type of the result value.

    Data Type Identifiers are defined by the structure dtpar in usrfns.h include file:

       typedef  struct
       {
                int       dttypeid;   /* data type identifier */
                int       dtpar1,     /* parameters */
                          dtpar2,
                          dtpar3,
                          dtpar4;
                addr      dtpfptr;    /* pointer for file operations */
                                      /* no entry is required for this pointer */
       } dtpar;
    
    

    The definition of a Data Type Identifier for the function arguments is an array of pointers to individual Data Type Identifiers for each argument. The Data Type Identifier for each individual argument is a structure of type dtpar.

    The definition of a Data Type Identifier for the function's result is simply a structure of type dtpar.

    Data Type Identifiers may be used by more than one function I/O table, and the same identifier may be used for both argument and result values.

    An entry for a Data Type Identifier consists of six parts:

    1. The name (dttypeid) of an Empress generic or SQL data type.

      Empress generic data types are defined as (the definitions are listed in usrfns.h include file):

      Table 3-4 Empress Generic Data Types

      Generic Data Type Description
      DTGCHAR is char *, pointer to a character string.
      DTGINTEGER is long, a longinteger.
      DTGDECIMAL is gen_dec, a structure containing character string.
      DTGFLOAT is double, a C type double.
      DTGDATE is gen_date, a structure containing a series longinteger values for year, month, day, hour, minute, second and microsecond.
      DTGBOOLEAN is int, an integer.
      DTGEXTERNAL is char *, pointer to a character string.
      DTGINTERVAL is gen_date, a structure containing a series longinteger values for year, month, day, hour, minute, second and microsecond.
      DTGBINARY is gen_binary, a structure containing a series binary segment structure, length and number of segments.


      Empress SQL data types are defined as (the definitions are listed in usrfns/generic.h include file):

      Table 3-5 Empress SQL Data Types

      Empress SQL Data Type Description
      DTCHAR
      DTCHARACTER
      is char *, pointer to a character string.
      DTSHORTINT
      DTSMALLINT
      is short, a short integer.
      DTINTEGER
      DTINT
      is long, a long integer.
      DTLONGINT is long, a long integer.
      DTDATE is gen_date, a structure containing a series longinteger values for year, month, day, hour, minute, second and microsecond.
      DTDOLLAR is gen_dec, a structure containing character string.
      DTFLOAT is float, a floating point number.
      DTLONGFLOAT
      DTPERCFLOAT
      DTDBLPERCISION
      is double, a double precision floating point number.
      DTTEXT is char *, pointer to a character string.
      DTBULK is gen_binary, a structure containing a series binary segment structure, length and number of segments.
      DTDECIMAL
      DTDEC
      is gen_dec, a structure containing character string.
      DTTIME is gen_date, a structure containing a series longinteger values for year, month, day, hour, minute, second and microsecond.
      DTNLSCHAR
      DTNLSCHARACTR
      is char *, pointer to a character string.
      DTNLSTEXT is char *, pointer to a character string.
      DTWON is gen_dec, a structure containing character string.
      DTMICROTIMESTAMP is gen_date, a structure containing a series longinteger values for year, month, day, hour, minute, second and microsecond.

    2. The value (dtpar1) of the first parameter.

      This is only applicable for the user data type. If there is no parameter required, the value is 0. Generic data types do not require parameters, therefore, the value should set to 0.

    3. The value (dtpar2) of the second parameter.

      This is only applicable for the user data type. If there is no parameter required, the value is 0. Generic data types do not require parameters, therefore, the value should be t to 0.

    4. The value (dtpar3) of the third parameter.

      This is only applicable for the user data type. If there is no parameter required, the value is 0. Generic data types do not require parameters, therefore, the value should be t to 0.

    5. The value (dtpar4) of the fourth parameter.

      This is only applicable for the user data type. If there is no parameter required, the value is 0. Generic data types do not require parameters, therefore, the value should be t to 0.

    6. dtpfptr

      This field is not used.

    The example below define the data types of the arguments and result value for the expon function:

       static   dtpar   dtgflt = { DTGFLOAT, 0, 0, 0, 0 },
                        *gfltgflt[] = { &dtgflt, &dtgflt };
    
    

    Two data type identifiers are required to describe the argument and result value data types for this function. dtgflt is the data type of the result, and gfltgflt is the data type of the argument list.

    3.6.5 Defining the Function Procedures Table

    After the Function I/O Table has been defined for the function, a Function Procedures Table must be created. For each entry in the Function I/O Table, there must be one entry in the Function Procedures Table. An entry in a Function Procedures Table simply names the routines that will be invoked to execute the tuple or aggregate function.

    The Function Procedures Table is defined by the C structure dtxpdes in usrfns.h include file:

       typedef   struct
       {
                 msbool     (*dtxifns)();        /* operator init procedure */
                 msbool     (*dtxigrp)();        /* begin group procedure */
                 msbool     (*dtxrfns)();        /* run operator procedure */
                 msbool     (*dtxtgrp)();        /* end group procedure */
                 msbool     (*dtxtfns)();        /* operator cleanup procedure */
       } dtxpdes;
    
    

    Each entry in the Function Procedures Table consists of five parts:

    1. A pointer (dtxifns) to a C subroutine to be used to initialize the workspace for an aggregate UDF. For the tuple function, this value is NILPROC which represents a null entry.

    2. A pointer (dtxigrp) to a C subroutine to be used to initialize variable values for an aggregate UDF. For the tuple function, this value is NILPROC which represents a null entry.

    3. A pointer (dtxrfns) to a C subroutine to be used to perform the actual calculation on the tuple, or on each tuple in a group for an aggregate function.

    4. A pointer (dtxtgrp) to a C subroutine to be used to retrieve the values resulting from the calculation in the working space for an aggregate UDF. For the tuple function, this value is NILPROC which represents a null entry.

    5. A pointer (dtxtfns) to a C subroutine to be used to clean up the working space for an aggregate UDF, and return the space to the system. For the tuple function, this value is NILPROC which represents a null entry.

    An example of this table for the expon function is:

       dtxpdes expontabproc[] =
       {
               { NILPROC, NILPROC, expon, NILPROC, NILPROC }
       };
    
    

    NILPROC represents a null entry.

    3.6.6 Writing the C Routine

    The actual C routine is the last component of the UDF. The easiest way to incorporate the C routine into the UDF is to write the routine so that it can run as either a standalone program, or as a standard function, and then modify it to allow for argument and result value handling, any necessary data conversions, and null and error value handling.

    The following examples illustrate how functions or standalone programs can be incorporated into the UDF interface as DT functions.

    To incorporate this routine into a UDF, only a few simple changes need to be made:

    1. The routine is declared boolean and the argument list must be modified to:
         static msbool function_name (opr, param)
                dtxr    *opr;
                addr     param;
      
      

      Note that the second argument is not actually used in this example.

      The dtxr structure contains all the information required to invoke the function or operator. It provides access to the function arguments or operands and the result of the function or operator.

         typedef   struct
         {
            struct  dtxr    *dtxrnext;                /* next operator */
            dtxpdes         *dtxrprocs;               /* pointer to procedures */
            msbool           (*dtxrrun)();            /* duplicate pointer to rfns */
            void            *dtxrparam;               /* parameter to procedures */
            unsigned int     dtxrnargs : BITSPERBYTE;  /* number of arguments */
            unsigned int     dtxraval : 1;             /* a-valued operator */
            unsigned int     dtxrdval : 1;             /* delayed operator */
            unsigned int     dtxrnullok : 1;           /* procedures handle nulls */
            unsigned int     dtxrerrok : 1;            /* procedures handle errors */
            unsigned int     dtxrconvt : 1;            /* conversion operator */
            unsigned int     dtxrufunc : 1;            /* M-Builder type operator */
            unsigned int     dtxrifns : 1;             /* was ifns run ? */
            void            *dtxrwksp;                 /* workspace */
            dtxd            *dtxrresult;               /* the result */
            dtxd            *dtxds[1];                 /* the operands */
                                                       /* space for operands */
         } dtxr;
      
      

    2. In the body of the function, a pointer to a structure of type dtxd must be declared. This enables access to the function argument(s).

      The dtxd structure is used to define and store the function arguments or operands and the result value of the function.

         typedef   struct
         {
            dtpar         *dtxddtp;                 /* the data type */
            unsigned int   dtxdnull : BITSPERBYTE;  /* null (run-time) type */
            unsigned int   dtxdtype : BITSPERBYTE;  /* operand (static) type */
            unsigned int   dtxdanydt : 1;           /* opr will handle any dt */
            unsigned int   dtxdconsflag : 1;        /* constant ? */
            unsigned int   dtxdcvar : 1;            /* control var used  ? */
            unsigned int   dtxdnucheck : 1;         /* check for null ? */
            unsigned int   dtxdresult : 1;          /* result operand ? */
            unsigned int   dtxdgsbuf : 1;           /* generic string buffer ? */
            unsigned int   dtxdlinkcount : 2;       /* link count */
            void          *dtxdnuval;               /* the null value */
            long           dtxdzval;                /* size of value */
            void         **dtxdival;                /* indirect pointer to value */
            void          *dtxdval;                 /* pointer to value */
         } dtxd;
      
      

    3. The argument value is cast to a variable of the appropriate type so that it may be accessed.

    4. The result value is assigned to opr->dtxrresult->dtxdival and cast to the appropriate data type.

    5. The C routine returns the value true to indicate successful completion.

    The following is an example for the exponential routine expon:

       #include <math.h>
    
       static msbool expon(opr, params)
              dtxr  *opr;
              addr  params;
       {
          gen_float     x,y;
          dtxd  *opd;
    
          opd = opr->dtxds[0];
          x = *(gen_float *)*(opd->dtxdival);
          opd = opr->dtxds[1];
          y = *(gen_float *)*(opd->dtxdival);
          *(gen_float *)*(opr->dtxrresult->dtxdival) = pow(x,y);
    
          /* pow is the system math function to raise a number to a power */
    
          return( true );
       }
    
    

    The following is the complete listing of usrfns.c file for the above example:

    
       /*******************************************************************
        *
        *     (c) Copyright     Empress Software Inc. 1983, 2003
        *
        *****************************************************************/
    
       /***********************************************************************
        *  WARNING: as of version 6.4, this file MUST be compiled with "empcc"
        *           (not "cc") in order to find the correct header files.
        ***********************************************************************/
       
       # include  "usrfns.h"
    
       /******************************************************************************
        *                      USRXKTAB
        *
        *      Structure:      Table structure
        *
        *      Purpose:        contains the name and parsing information for each
        *                      user defined function to be used from the QL level.
        *                      Should be kept in ascending alphabetical order
        *                      according to function name.
        *
        ******************************************************************************/
    
       keyitem     usrxktab[] =
       {
                   {     "**",      op2_x1         }, /* exponentiation operator */
                   {     (char *)0                 }
       };
    
       int     usrzxktab = sizeof (usrxktab) / sizeof (keyitem) - 1;
    
       /******************************************************************************
        *
        *      DT User Defined Functions
        *              - using dtxr (and dtxd) structures
        *
        ******************************************************************************/
    
       /******************************************************************************
        *                      EXPONENTIAL (**)
        *
        *      Purpose:        Raises a number to a power.
        *
        *      Arguments:      Operator - contains two float arguments.       
        *
        *      Returns:        true, if calculation is successful; false, if not.
        *
        ******************************************************************************/
    
       #include <math.h>
    
       static msbool expon(opr, params)
              dtxr  *opr;
              addr  params;
       {
          gen_float     x,y;
          dtxd  *opd;
          opd = opr->dtxds[0];
          x = *(gen_float *)*(opd->dtxdival);
          opd = opr->dtxds[1];
          y = *(gen_float *)*(opd->dtxdival);
          *(gen_float *)*(opr->dtxrresult->dtxdival) = pow(x,y);
    
          /* pow is the system math function to raise a number
             to a power */
       
          return( true );
       }
    
       /******************************************************************************
        *
        *      DT User Defined Functions structures and tables
        *
        ******************************************************************************/
    
       static   dtpar   dtgflt = {  DTGFLOAT  , 0, 0, 0, 0  },
                        gfltgflt[] = {  &dtgflt,  &dtgflt  };
    
       dtxpdes   expontabproc[] =
       {
                 { NILPROC, NILPROC, expon, NILPROC, NILPROC }
       };
    
       dtxades expontab[] =
       {
               {       & expontabproc[0], (addr)0,  & dtgflt,  gfltgflt  },
               {     NULLENTRY          }
       };
    
       dtxdes   usrxtab[] =
       {
                {     "**",      2,  expontab,    NNEN,     false     },
                {     (char *)0          }
       };
    
       int     usrzxtab = (sizeof (usrxtab) / sizeof (dtxdes)) - 1;
    
    

    3.6.6.1 Error Handling in DT User Defined Functions

    Errors may occur as the result of invalid operand/argument values or invalid operand/argument data types. Error conditions may also arise within the UDF itself.

    The following types of error conditions can be handled within the UDF:

    In the user function table of usrfns.c (usrxtab), for each function entry there is a flag (dtxinput) indicating the action to be taken when an error condition for an argument/operand is passed to the UDF.

    If you wish to handle input errors in the function, this flag should be set to either NNEY or NYEY. NNEY indicates that null values are not valid as input arguments, but errors are. NYEY indicates that both null and error values are valid input arguments. (i.e., NNEY - null values no, error values yes)

    If this flag is not set to accept errors as input to the function, and an argument/operand of invalid data type is passed to the UDF, the following message will be reported:

       *** User Error *** operator/function 'NAME' invalid
                          or argument(s) incompatible
    
    

    This error will be reported only once, and the function will not be evaluated.

    If the flag is not set to accept errors as input to the function, and if an argument value of the correct data type, but which evaluates to an error is passed to the function, the following message will be issued for each invocation of the function:

       *** User Error *** Expression Error: cannot evaluate
                          expression - Skipping record
    
    

    To indicate that an error condition has been detected in the function, the UDF must return the value false to the program which invoked it.

    For each invocation of the function which returns the value false, the same error message (as above) will be reported:

       *** User Error *** Expression Error: cannot evaluate
                          expression - Skipping record
    
    

    To return a more meaningful error message when the UDF returns false, the routine dtudferr() can be used. This routine accepts one argument which must evaluate to a string, and this message will be printed for every invocation of the function that returns false.

    The following branch of code shows how a function can return a specific error message with dtudferr().

       if ( *(gen_integer *)*(opd->dtxdival) == 0 )
       {
          dtudferr( "div: zero denominator" );
          return(false);
       }
    
    

    For every invocation of the function for which the argument value evaluates to 0, the error message will be reported as follows:

       *** User Error *** div: zero denominator - skipping record
    
    

    To test for error values in the UDF itself, the routine dtxnuerr is available. The error handling flag must be set in the user function table in order to use this feature.

    The following example illustrates handling of:

       if ( dtxnuerr(opd->dtxdnull) == DTXERR )
    
       /* invalid argument value (expression error) */
    
       {
          *(gen_integer *)*(opr->dtxrresult-dtxdival) = INFINITY;
          return(true);
       }
       else if ( *(gen_integer *)*(opd->dtxdival) == 0 )
    
       /* invalid argument value - would result in division by 0 */
    
       {
          dtudferr( "div: zero denominator" );
          return(false);
       }
       else
    
       /* valid argument data type/value */
       {
          y = *(gen_integer *)*(opd->dtxdival);
          *(gen_integer *)*(opr->dtxrresult->dtxdival) = x/y;
       }
       return( true );
    
    

    3.6.6.2 Null Value Handling in DT User Defined Functions

    Arguments to user defined functions which evaluate to null can be handled similarly to error values.

    In the user function table of usrfns.c (usrxtab), for each function entry the dtxinput flag can be set to indicate the action to be taken when a null value is passed as an argument to a user defined function.

    If null values are to be allowed as acceptable values for the C routine, this flag should be set to either NYEN or NYEY. NYEN indicates that null values are valid as input arguments, but error values are not. NYEY indicates that both null and error values are valid input arguments.

    If the dtxinput flag is set to either of the above values, when a null value is encountered, the C routine for the user defined function will be invoked. If the flag is not set, the C routine will not be invoked, and the result value of the function will be set to null.

    If the dtxinput flag is set, null values can be tested for in the C routine as in the following example using the routine dtxnunul:

       if ( dtxnunul(opd->dtxdnull) == DTXNULL )
       {
          dtudferr( "vacated: found a null value" );
          return ( false );
       }
       else
          *(gen_integer *)*(opr->dtxrresult->dtxdival) = 0;
    
    

    In this example, for each invocation of the function where a null value is detected, an error message will be printed.

    Alternatively, the UDF can return a null value by assigning a null value to the result field of the dtxr structure, as in the following example:

       if ( dtxnunul(opd->dtxdnull) == DTXNULL )
          opr->dtxrresult->dtxdnull = DTXOPDNUL;
       else
          *(gen_integer *)*(opr->dtxrresult->dtxdival) = 0;
    
    

    If a function returns a null value, any error messages that the function would return are suppressed, as are any other result assignments.

    3.6.7 Compiling UDFs and Relinking the Executables

    Once the file usrfns.c has been modified to include the necessary entries for defining a UDF, the following steps must be taken to make the UDFs available to Empress.

    For UNIX systems:

    1. cd $EMPRESSPATH/rdbms/custom/src/usrfns
    2. empcc -c usrfns.c (produces usrfns.o)
    3. ar rv $EMPRESSPATH/rdbms/lib/stdudf.a usrfns.o (archive the object file in $EMPRESSPATH/rdbms/lib/stdudf.a)
    4. ranlib $EMPRESSPATH/rdbms/lib/stdudf.a (randomize the library; this step is not necessary on all machines)
    5. cd $EMPRESSPATH/rdbms/conf_bin
    6. mkexec -s (rebuild all Empress executables)
      or
      mkexec -s empsql emp4ful emp4fulg emp4rt emp4rtg (or rebuild the specific executables)

    For Windows NT:

    1. cd %EMPRESSPATH%\rdbms\custom\src\usrfns
    2. empcc /c usrfns.c (produces usrfns.obj)
    3. LIB %EMPRESSPATH%\rdbms\lib\stdudf.lib usrfns.obj;
    4. cd %EMPRESSPATH%\rdbms\conf_bin
    5. mkexec

    6. or
      mkexec empsql emp4ful emp4rt



    3.7 Example Programs

    3.7.1 User Defined Function Include File

    The following is the listing of the User Defined Function include file usrfns.h in the include directory:

    
       /***********************************************************************
        *		(c) Copyright	Empress Software Inc. 1983, 2003
        ***********************************************************************/
       
       #include "c2.h"
       #include "public.h"
       
       #include  "mscc.h"
       
       /*
        *	for the keyword table
        */
       
       #include	<usrfns/dtpar.h>
       #include	<usrfns/dttypes.h>
       #include	<usrfns/dtxoper.h>
       #include	<usrfns/dtxstdfns.h>
       #include	<usrfns/generic.h>
       #include	<usrfns/keyitem.h>
       #include	<usrfns/dtparvar.hx>
       
       #ifdef __cplusplus
       extern "C" {
       #endif
       
       #include	<usrfns/mrtrig.hx>
       
       #define	mspsm_trig_abort_operation	msmrtrig_abort_operation
       #define	mspsm_trig_get_record_info	msmrtrig_get_record_info
       
       /*
        *	useful extern's
        */
       
       global_shared_func	msbool	dtchkpar ();
       global_shared_func	msbool	dtqf ();
       global_shared_func	msbool	dttype ();
       
       global_shared_func	char*	dtputex ();
       global_shared_func	char*	dtputgex ();
       global_shared_func	char*	dtputgin ();
       global_shared_func	char*	dtputin ();
       
       global_shared_func	int	dtcmp ();
       
       global_shared_func	void	dtcontrol ();
       global_shared_func	void	dtfadd ();
       global_shared_func	void	dtfclose ();
       global_shared_func	void	dtfdel ();
       global_shared_func	void	dtfgethdr ();
       global_shared_func	void	dtfopen ();
       global_shared_func	void	dtfputhdr ();
       global_shared_func	void	dtgetex ();
       global_shared_func	void	dtgetgex ();
       global_shared_func	void	dtgetgin ();
       global_shared_func	void	dtgetin ();
       global_shared_func	void	dtgeninfo ();
       global_shared_func	void	dtinfo ();
       global_shared_func	void	dtvcontrol ();
       
       /*
        *	routine for setting error messages
        */
       
       global_shared_func	void	dtudferr();
       global_shared_func	void	dtudfsqlstate();
       
       /*
        *	Space allocation routine
        */
       
       global_shared_func	void*	spmget (int);
       global_shared_func	void	spfree (void*);
       
       #define	dtalloc(n)	spmget(n)
       
       /*
        *	useful string handling routines
        */
       
       global_shared_func	char*	msstr_save(char*);
       
       #define	strsave(s)	msstr_save (s)
       #define	strfree(s)	spfree(s)
       
       #ifdef __cplusplus
       }
       #endif
       
    
    

    3.7.2 Standard C User Defined Functions Example Program

    3.7.2.1 Keyword Definition

       /*******************************************************************

    * (c) Copyright Empress Software Inc. 1983, 2003 * *****************************************************************/ # include "../include/usrfns.h"

    usrsktab


    Structure: Table structure.

    Purpose: Contains the name and parsing information for each of the Standard C user defined functions to be used from the QL level. This should be kept in ascending alphabetical order according to function name.

       keyitem   usrsktab[] =
       {
                 {     "!",         op1_xp        },  /* factorial operator */
                 {     "attach",    opf_x         },
                 {     "echo",      opf_x         },
                 {     "odd",       opf_x         },
                 {     "ret_null",  opf0_x        },
                 {     (char *)0     }
       };
    
       int     usrzsktab = sizeof (usrsktab) / sizeof (keyitem) - 1;
    
    
    

    3.7.2.2 Routines


    attach


    Purpose: Illustrate the construction of a Standard C User Defined Function that can handle a variable number of arguments. All the arguments to a maximum of 8 are concatenated into one string.

    Arguments: argc -- number of arguments (must be from 2 to 9)
    argv[1] - argv[8] or *++argv -- pointer to character string containing the argument.

    Returns: Pointer to the character string to be printed.

       static   char   *attach(argc, argv)
                int   argc;
                char   *argv[];
       {
          static char buffer[256];         /* return a value with maximum 256
          characters                      */
          int arg_num, position, offset;
          if (argc > 9)
          {
             dtudferr("attach: maximum of 8 arguments");  
             return (CHARNIL);
          }
          else
          {
             position = 0;
             arg_num = 2;
             offset = 0;
             for(arg_num = 1; arg_num < argc; arg_num++) /* for each argument, copy
                                                            the characters into a
                                                            buffer (ignore everything
                                                            beyond 256 characters)   */
             {
                while (( argv[arg_num][offset] != '\0') && (position < 256) )
                {
                   buffer[position] = argv[arg_num][offset];
                   position++;
                   offset++;
                }
                offset = 0;
             }
             buffer[position] = '\0';
             return(buffer);
          }
       }
    
    
    

    echo


    Purpose: Illustrate the construction of a Standard C User Defined Function. This function simply prints the argument contained in argv[1].

    Arguments: argc -- number of arguments (must be 2)
    argv[1] or *++argv -- pointer to character string containing the argument.

    Returns: pointer to the character string to be printed.

       static   char  echo ( argc, argv )
                int   argc;
                char  **argv;
       {
          /* Echo whatever is contained in the string argument */
    
          if ( argc == 2 )
             return( *++argv );
          else
          {
             dtudferr( "echo: incorrect number of arguments" );
             return ( CHARNIL );
          }
       }
    
    
    

    factorial ( ! )


    Purpose: Illustrate the construction of a Standard C User Defined Operator. This operator produces the factorial of a LONGINTEGER argument.

    Arguments: argc -- number of arguments (must be 2)
    argv[1] or *++argv -- pointer to character string containing the argument.

    Returns: Pointer to the character string to be printed.

       char  *fact1 (argc, argv)
             int argc;
             char **argv;
       {
          extern     msbool      cvai();
          extern     char         *cvlx();
          static     char         result[11];
          int                     x;
          long                    y;
    
          if ( ! (cvai(&x, argv[1])))      /* convert string to integer */
          {
             dtudferr("fact: invalid argument");
             return(CHARNIL);
          }
          y = factorial(x);
          cvlx(result, y);        /* convert longinteger to string */
          return( result );       /* return the result as a string */
       }
    
       static   long factorial(a)
                int a;
       {
          return a <= 0 ? 1L : a == 1 ? (long) a : (long) a * factorial(a-1);
       }
    
    
    

    odd


    Purpose: To illustrate a function to accept integer arguments and to handle its own error reporting. This function returns "1" for odd integer arguments and "0" for even integer arguments.

    Arguments: argc -- number of arguments (must be 1)
    argv[1] or *++argc -- pointer to character string with integer in character form.

    Returns: Pointer to character string containing either 1 if the argument is odd; otherwise, the string contains 0. If an error occurred a CHARNIL is returned.

       static   char   *odd ( argc, argv )
                int     argc;
                char     **argv;
       {
          extern     msbool     cval();
          extern     char     *cvlx();
          gen_integer     i;
          static     char     str[30];
    
          if ( argc == 2 )
          {
             if ( cval (&i, argv[1]) )
             {
                i = ( (i % 2) != 0 );
                cvlx ( str, i );
                return( str );
             }
             else
             {
                dtudferr( "odd: argument is not an integer" );
                return( CHARNIL );
             }
          }
          else
          {
             dtudferr( "odd: incorrect number of arguments" );
             return ( CHARNIL );
          }
       }
    
    
    

    ret_null


    Purpose: Illustrate the construction of a Standard C User Defined Function that returns a null value. Useful for generating null values arbitrarily.

    Arguments: argc -- number of arguments is 1.

    Returns: Pointer to the character string to be printed (a null string).

       char   *ret_null (argc, argv)
              int argc;
              char *argv;
       {
          return( "" );
       }
    
    
    

    3.7.2.3 Function Tables and Variables

    
       dtstdfn   usrstab[] =
       {
                 { "!",         fact1,   'i',    1     },   /* factorial operator */
                 { "attach",    attach,  'q',   -1     },
                 { "echo",      echo,    'q',   -1     },
                 { "odd",       odd,     'q',   -1     },
                 { "ret_null",  ret_null,'v',    0     },   /* invoked with no ()s */
                 (char *) 0
       };
    
       int     usrzstab = sizeof (usrstab) / sizeof (dtstdfn) - 1;
    
    

    3.7.3 Data Type User Defined Functions Example Program

    3.7.3.1 Keyword and Type Definition

    
       /*******************************************************************
        *     (c) Copyright     Empress Software Inc. 1983, 2003
        *****************************************************************/
    
       # include  <usrfns.h>
    

    usrxktab


    Structure: Table structure.

    Purpose: Contains the name and parsing information for each user defined function to be used from the QL level.

       keyitem   usrxktab[] =
       {
                 {     "**",      op2_x1         }, /* exponentiation operator */
                 {     "append",  opf_x          },
                 {     "dist",    opf_x          },
                 {     "div",     opf_x          },
                 {     "ssd",     op1_x          },
                 {     "vacated", opf_x          },
                 {     "xlate",   opf_x          },
                 {     (char *)0                 }
       };
    
       int     usrzxktab = sizeof (usrxktab) / sizeof (keyitem) - 1;
    
    
    

    3.7.3.2 Routines


    stats


    Purpose: Defines the format of a statistics buffer for a DT User Defined Function.

       typedef   struct
       {
                 double     n;         /* Size of the sample of statistic x */
                 double     xsum;      /* Sum of single statistic x samples */
                 double     x2sum;     /* Sum of the squares of single statistic
                                          x samples */
       } stats;
    
    
    

    ssdifns


    Purpose: To illustrate the use of the Function initialization procedure segment of the Aggregate User Defined Function. This function allocate the necessary workspace to contain the structures to accumulate the statistics.

    Arguments: Operator -- at this point does not contain any valid data, but may have useful information such as data types that may be needed for executing a function initialization setup. In this case, it is not required.

    Returns: The actual function (ssdifns) will return true if the function initialization was successful; otherwise, false. This function normally allocates the workspace required to perform internal calculations.

       static   msbool   ssdifns( opr, param )
                dtxr       *opr;
                addr       param;
       {
          /* Allocate the necessary space required for internal statistics gathering. */
    
          opr->dtxrwksp = dtalloc( sizeof(stats) );
          return(true);
       }
    
    

    ssdigrp


    Purpose: To illustrate the use of the Group initialization procedure segment of the AGGREGATE User Defined Function. This function initializes the structures to accumulate the statistics.

    Arguments: Operator -- at this point does not contain any valid data, but may have useful information such as data types that may be needed for executing a group initialization setup. In this case, it is not required.

    Returns: The actual function (ssdigrp) will return true if the group initialization was successful; otherwise, false. This function normally initializes the workspace created by the ssdifns function.

       static   msbool   ssdigrp( opr, param )
                dtxr        *opr;
                addr        param;
       {
          stats   *statistic;
    
          /* Obtain the statistic record */
    
          statistic = (stats *) opr->dtxrwksp;
    
          /* Initialize the size of the sample of single statistic x to 0 */
    
          statistic->n = 0.0;
    
          /* Initialize the sum of the samples of single statistic x to 0 */
    
          statistic->xsum  = 0.0;
    
          /* Initialize the sum of the squares of the samples of the 
             single statistic x to 0 */
    
          statistic->x2sum = 0.0;
          return(true);
       }
    
    

    ssdrfns


    Purpose: To illustrate the use of the actual function execution procedure segment of the Aggregate User Defined Function. This function performs the actual accumulation of the statistics.

    Arguments: Operator -- at this point contains any valid argument data, that are needed for the accumulative calculations performed in this function. In particular, it contains the statistic x data.

    Returns: The actual function (ssdifns) will return true if the execution/accumulation was successful; otherwise, false.

     
       static   msbool   ssdrfns( opr, param )
                dtxr     *opr;
                addr     param;
       {
          dtxd     *opd;          /* Operand */
          stats     *statistic;     /* Statistic record */
          double     x;          /* Statistic x */
    
          /* Obtain the statistic record */
    
          statistic = (stats *) opr->dtxrwksp;
    
          /* Obtain the first operand information structure */
    
          opd = opr->dtxds[0];
    
          /* Obtain the x value from the operand information structure */
    
          x = *(gen_float *)*(opd->dtxdival);
    
          /* Accumulate the sum of x-squared samples */
    
          statistic->x2sum += x*x;
    
          /* Accumulate the sum of x samples */
    
          statistic->xsum += x;
    
          /* Increment the value of the size of the sample */
    
          statistic->n += 1.0;
          return( true );
       }
    
    
    

    ssdtgrp


    Purpose: To illustrate the use of the terminate group function procedure segment of the Aggregate User Defined Function. This function returns the final aggregate value associated with the accumulated calculations performed by the ssdrfns function.

    Arguments: Operator -- at this point, is ready to accept the result as determined by the accumulated calculations. In particular, it will accept the squared standard deviation of the statistic x data.

    Returns: The actual function (ssdtgrp) will return true if the result determination was successful; otherwise, false.

       static   msbool   ssdtgrp( opr, param )
                dtxr     *opr;
                addr     param;
       {
          stats   *statistic;
    
          /* Obtain the statistic record */
    
          statistic = (stats *) opr->dtxrwksp;
    
          /* Squared standard deviation (SSD) is
             (sum of squares)/n - [ (sum of x)/n ]^2 */
    
          if (statistic->n==0.0)
             *(gen_float *)*(opr->dtxrresult->dtxdival) = 0.0;
          else
             *(gen_float *)*(opr->dtxrresult->dtxdival) = 
               (gen_float)(( statistic->x2sum 
               - statistic->xsum * statistic->xsum / statistic->n)
               /statistic->n);
    
          return (true);
       }
    
    

    ssdtfns


    Purpose: To illustrate the use of the terminate function procedure segment of the Aggregate User Defined Function. This function performs cleanups and usually frees up any workspace used by the function.

    Arguments: Operator -- at this point, the only structure pertinent is the workspace. In particular, it will free the workspace.

    Returns: The actual function (ssdtfns) will return true if the cleanup was successful; otherwise, false.

       static   msbool   ssdtfns( opr, param )
                dtxr     *opr;
                addr     param;
       {
          extern   void   spfree ();
          spfree ( opr->dtxrwksp );
          return( true );
       }
    
    

    point


    Purpose: Defines the structure of bulk containing the x and y coordinates of the point (Type definition for distproc).

       typedef  struct
       {
                int     pt_nbytes;     /* number of bytes */
                long     pt_x;         /* x coordinate */
                long     pt_y;         /* y coordinate */
       } point;
    
    
    

    distproc


    Purpose: Finds the square of the distance between two 2-D points. This function also handles its own errors.

    Arguments: Operator -- contains two bulk arguments each containing a point specification.

    Returns: True, if calculation is successful; false, if not.


       msbool   distproc (opr, param)
                 dtxr     *opr;
                 addr     param;
       {
          int     i;
          dtxd    *opd;
          long    x1, x2, xd, y1, y2, yd;
          point   *p;
    
          opd = opr->dtxds[0];
          dtgetin (opd->dtxddtp, *(opd->dtxdival));
          p = (point *)dtval;
          if (p->pt_nbytes != 2 * sizeof (long))
          {
             dtudferr( "Dist : Error in size of X1-Y1 co-ordinates\n" );
             return (false);
          }
          x1 = p->pt_x, y1 = p->pt_y;
          opd = opr->dtxds[1];
          dtgetin (opd->dtxddtp, *(opd->dtxdival));
          p = (point *)dtval;
          if (p->pt_nbytes != 2 * sizeof (long))
          {
             dtudferr( "Dist : Error in size of X2-Y2 co-ordinates\n" );
             return (false);
          }
          x2 = p->pt_x, y2 = p->pt_y;
          xd = x1 - x2, yd = y1 - y2;
          *(gen_integer *)*(opr->dtxrresult->dtxdival) = xd * xd + yd * yd;
          return (true);
       }
    
    

    exponential


    Purpose: Raises a number to a power.

    Arguments: Operator -- contains two float arguments.

    Returns: True, if calculation is successful; false, if not.

       #include <math.h>
    
       static   msbool   expon(opr, params)
                dtxr  *opr;
                addr  params;
       {
          gen_float   x,y;
          dtxd  *opd;
    
          opd = opr->dtxds[0];
          x = *(gen_float *)*(opd->dtxdival);
          opd = opr->dtxds[1];
          y = *(gen_float *)*(opd->dtxdival);
          *(gen_float *)*(opr->dtxrresult->dtxdival) = pow(x,y);
    
          /* pow is the system math function to raise a number
            to a power */
    
          return( true );
       }
    
    

    append


    Purpose: Illustrate how to return a variable length result.

    Arguments: Operator -- contains two character string arguments.

    Returns: True, if calculation is successful; false, if not.

       static   msbool   append (opr, param)
                dtxr     *opr;
                addr     param;
       {
          dtxd    *opd1;
          dtxd    *opd2;
          addr    *ival;
          addr    buf;
          char    null[1];
          int     sze, sze1, sze2;
    
          extern char *memcpy();
          *null = '\0';
          opd1 = opr->dtxds[0];
          opd2 = opr->dtxds[1];
          sze1 = strlen(*opd1->dtxdival);
          sze2 = strlen(*opd2->dtxdival);
          buf = (char *) dtalloc ((sze1 + sze2 + 1) * sizeof(char));
          memcpy(buf, *opd1->dtxdival, sze1);
          memcpy(buf + sze1, *opd2->dtxdival, sze2);
          memcpy(buf + sze1 + sze2, null,1);
          ival = opr->dtxrresult->dtxdival;
          strfree (*ival);              /* free the current space     */
          *ival = strsave(buf);         /* assign the result value    */
          spfree (buf);
          return (true);
       }
    
    

    distp2


    Purpose: Finds the difference of two integers.

    Arguments: Operator -- contains two integer arguments.

    Returns: True, if calculation is successful; false, if not.
       static   msbool   distp2 (opr, param)
                dtxr     *opr;
                addr     param;
       {
          dtxd     *opd;
          long     x1, x2, xd;
    
          opd = opr->dtxds[0];
          x1 = *(gen_integer *)*(opd->dtxdival);
          opd = opr->dtxds[1];
          x2 = *(gen_integer *)*(opd->dtxdival);
          xd = x1 - x2;
          *(gen_integer *)*(opr->dtxrresult->dtxdival) = xd;
          return( true );
       }
    
    

    func_xlate


    Purpose: Illustrate use of the param argument and handling of null values.

    Arguments: Operator -- contains three integer arguments or float arguments.

    Returns: True, if calculation is successful; false, if not.

       static   msbool  func_xlate(opr, param)
                dtxr     *opr;
                addr     param;
       {
          dtxd      *opd;
          long      x1, x2, x3;
          float     f1, f2, f3;
          msbool   x1_null, x2_null, x3_null;
          int       iparam;
    
          iparam = *( int * ) param;
          x1_null = x2_null = x3_null = false;
          opr->dtxrresult->dtxdnull = DTXVALUE;
          opd = opr->dtxds[0];
          if ( dtxnunul(opd->dtxdnull) == DTXNULL )
             x1_null = true;
          else
          {
             switch( iparam ) {
                case 0  : x1 = *(gen_integer *)*(opd->dtxdival);
                          break;
                case 1  : f1 = *(gen_float *)*(opd->dtxdival);
                          break;
             }
          }
          opd = opr->dtxds[1];
          if ( dtxnunul(opd->dtxdnull) == DTXNULL )
             x2_null = true;
          else
          {
             switch( iparam ) {
                case 0  : x2 = *(gen_integer *)*(opd->dtxdival);
                          break;
                case 1  : f2 = *(gen_float *)*(opd->dtxdival);
                          break;
             }
          }
          opd = opr->dtxds[2];
          if ( dtxnunul(opd->dtxdnull) == DTXNULL )
             x3_null = true;
          else
          {
             switch( iparam ) {
                case 0  : x3 = *(gen_integer *)*(opd->dtxdival);
                          break;
                case 1  : f3 = *(gen_float *)*(opd->dtxdival);
                          break;
             }
          }
          if(( x1_null && x2_null ) ||
             ( !x1_null && !x2_null &&
             ( (iparam==0) ? (x1==x2) : f1 == f2 ) ) )
          {
             if ( x3_null )
                opr->dtxrresult->dtxdnull = DTXOPDNUL;
             else
             {
                switch( iparam ) {
                   case 0  : *(gen_integer *)*(opr->dtxrresult->dtxdival) = x3;
                             break;
                   case 1  : *(gen_float *)*(opr->dtxrresult->dtxdival) = f3;
                             break;
                }
             }
          }
          else
          {
             if ( x1_null )
                opr->dtxrresult->dtxdnull = DTXOPDNUL;
             else
             {
                switch( iparam ) {
                   case 0  : *(gen_integer *)*(opr->dtxrresult->dtxdival) = x1;
                             break;
                   case 1  : *(gen_float *)*(opr->dtxrresult->dtxdival) = f1;
                             break;
                }
             }
          }
          return( true );
       }
    
    

    vacated


    Purpose: Illustrate use of the handling of null values.

    Arguments: Operator -- contains one numeric argument.

    Returns: True, if calculation is successful; false, if not.

       static   msbool   vacated (opr, params)
                dtxr     *opr;
                addr     params;
       {
          gen_integer     x;
          dtxd     *opd;
    
          opd = opr->dtxds[0];
    
          /* If the operand is null then return a NULL
             otherwise, return a 0 */
    
          if ( dtxnunul(opd->dtxdnull) == DTXNULL )
          {
             dtudferr( "vacated: found a null value" );
             return(false);
          }
          else
             *(gen_integer *)*(opr->dtxrresult->dtxdival) = 0;
          return( true );
       }
    
    

    div


    Purpose: Illustrate use of the handling of error values.

    Arguments: Operator -- contains one numeric argument.

    Returns: True, if calculation is successful; false, if not.

       # define INFINITY     (0x7FFFFFFF)
    
       static   msbool   div (opr, params)
                dtxr     *opr;
                addr     params;
       {
          gen_integer     x,y;
          dtxd     *opd;
    
          opd = opr->dtxds[0];
    
          /* Checks the numerator for expression errors */
    
          if ( dtxnuerr(opd->dtxdnull) == DTXERR )
          {
             *(gen_integer *)*(opr->dtxrresult->dtxdival) = INFINITY;
             return(true);
          }
          else
             x = *(gen_integer *)*(opd->dtxdival);
          opd = opr->dtxds[1];
    
          /* Checks the denominator for expression errors */
    
          if ( dtxnuerr(opd->dtxdnull) == DTXERR )
          {
             *(gen_integer *)*(opr->dtxrresult->dtxdival) = INFINITY;
             return(true);
          }
    
          /* Otherwise if the denominator is zero then report error */
    
          else if ( *(gen_integer *)*(opd->dtxdival) == 0 )
             {
                dtudferr( "div: zero denominator" );
                return(false);
             }
             else
             {
                y = *(gen_integer *)*(opd->dtxdival);
                *(gen_integer *)*(opr->dtxrresult->dtxdival) = x/y;
             }
          return( true );
       }
    
    

    3.7.3.3 Structures and Tables

    
       static  dtpar   dtbulk = {  DTBULK,     2, 10, 16, 1  },
                       dtgint = {  DTGINTEGER, 0, 0, 0, 0  },
                       dtgflt = {  DTGFLOAT  , 0, 0, 0, 0  },
                       dtgchar  = { DTGCHAR  , 0, 0, 0, 0  },
                       gflt[] = { & dtgflt },
                       gint[] = { & dtgint },
                       buk[]  = { & dtbulk },
                       gchargchar[] = { &dtgchar, &dtgchar},
                       gintgint[] = {  & dtgint,  & dtgint  },
                       gfltgflt[] = {  & dtgflt,  & dtgflt  },
                       bukbuk[] = {  & dtbulk,  & dtbulk  },
                       gfgfgf[] = {  & dtgflt,  & dtgflt, & dtgflt  },
                       gigigi[] = {  & dtgint,  & dtgint, & dtgint  };
    
                       static int intdt = 0;
                       static int fltdt = 1;
    
       dtxpdes   ssdtabproc[] =
       {
                 { ssdifns, ssdigrp, ssdrfns, ssdtgrp, ssdtfns }
       };
    
       dtxpdes   expontabproc[] =
       {
                 { NILPROC, NILPROC, expon, NILPROC, NILPROC }
       };
    
       dtxpdes   appendtabproc[] =
       {
                 { NILPROC, NILPROC, append, NILPROC, NILPROC }
       };
    
       dtxpdes   disttabproc[] =
       {
                 { NILPROC, NILPROC, distproc, NILPROC, NILPROC },
                 { NILPROC, NILPROC, distp2,   NILPROC, NILPROC }
       };
    
       dtxpdes   divtabproc[] =
       {
                 { NILPROC, NILPROC, div, NILPROC, NILPROC }
       };
    
       dtxpdes   vactabproc[] =
       {
                 { NILPROC, NILPROC, vacated, NILPROC, NILPROC }
       };
    
       dtxpdes   xlatetabproc[] =
       {
                 { NILPROC, NILPROC, func_xlate, NILPROC, NILPROC }
       };
    
       dtxades   disttab[] =
       {
                 {     & disttabproc[0],  (addr)0,  & dtgint,  bukbuk   },
                 {     & disttabproc[1],  (addr)0,  & dtgint,  gintgint },
                 {     NULLENTRY          }
       };
    
       dtxades   expontab[] =
       {
                 {       & expontabproc[0], (addr)0,  & dtgflt,  gfltgflt  },
                 {     NULLENTRY          }
       };
    
       dtxades   appendtab[] =
       {
                 {     & appendtabproc[0],  (addr)0,  & dtgchar,  gchargchar },
                 {     NULLENTRY          }
       };
    
       dtxades   divtab[] =
       {
                 {     & divtabproc[0],  (addr)0,  & dtgint,  gintgint },
                 {     NULLENTRY          }
       };
    
       dtxades   ssdtab[] =
       {
                 {     & ssdtabproc[0],  (addr)0,  & dtgflt,  gflt },
                 {     NULLENTRY          }
       };
    
       dtxades   vactab[] =
       {
                 {     & vactabproc[0],  (addr)0,  & dtgint,  gint },
                 {     & vactabproc[0],  (addr)0,  & dtgint,  gflt },
                 {     & vactabproc[0],  (addr)0,  & dtgint,  buk  },
                 {     NULLENTRY          }
       };
    
       static   dtxades     xlatetab[] =
       {
                 {     & xlatetabproc[0], (addr)&intdt,  & dtgint,  gigigi },
                 {     & xlatetabproc[0], (addr)&fltdt,  & dtgflt,  gfgfgf },
                 {     NULLENTRY          }
       };
    
       dtxdes   usrxtab[] =
       {
                 {     "**",      2,  expontab,    NNEN,     false     },
                 {     "append",  2,  appendtab,   NNEN,     false     },
                 {     "dist",    2,  disttab,     NNEN,     false     },
                 {     "div",     2,  divtab,      NNEY,     false     },
                 {     "ssd",     1,  ssdtab,      NYEN,     true      },
                 {     "vacated", 1,  vactab,      NYEN,     false     },
                 {     "xlate",   3,  xlatetab,    NYEN,     false     },
                 {     (char *)0          }
       };
    
       int   usrzxtab = (sizeof (usrxtab) / sizeof (dtxdes)) - 1;