/*************************************************************************
** (c) Copyright IBM Corp. 2007 All rights reserved.
** 
** The following sample of source code ("Sample") is owned by International 
** Business Machines Corporation or one of its subsidiaries ("IBM") and is 
** copyrighted and licensed, not sold. You may use, copy, modify, and 
** distribute the Sample in any form without payment to IBM, for the purpose of 
** assisting you in the development of your applications.
** 
** The Sample code is provided to you on an "AS IS" basis, without warranty of 
** any kind. IBM HEREBY EXPRESSLY DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR 
** IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Some jurisdictions do 
** not allow for the exclusion or limitation of implied warranties, so the above 
** limitations or exclusions may not apply to you. IBM shall not be liable for 
** any damages you suffer as a result of using, copying, modifying or 
** distributing the Sample, even if IBM has been advised of the possibility of 
** such damages.
***************************************************************************
**
** SOURCE FILE NAME: spserver.sqC
**
** SAMPLE: Code implementations of various types of stored procedures
**
**         The stored procedures defined in this program are called by the
**         client application spclient.sqC. Before building and running
**         spclient.sqC, build the shared library by completing the following
**         steps:
**
** BUILDING THE SHARED LIBRARY:
** 1. Ensure the Database Manager Configuration file has the keyword
**    KEEPFENCED set to "no". This allows shared libraries to be unloaded
**    while you are developing stored procedures. You can view the file's
**    settings by issuing the command: "db2 get dbm cfg". You can set
**    KEEPFENCED to "no" with this command: "db2 update dbm cfg using
**    KEEPFENCED no". NOTE: Setting KEEPFENCED to "no" reduces performance
**    the performance of accessing stored procedures, because they have
**    to be reloaded into memory each time they are called. If this is a
**    concern, set KEEPFENCED to "yes", stop and then restart DB2 before
**    building the shared library, by entering "db2stop" followed by
**    "db2start". This forces DB2 to unload shared libraries and enables
**    the build file or the makefile to delete a previous version of the
**    shared library from the "sqllib/function" directory. 
** 2. To build the shared library, enter "bldrtn spserver", or use the 
**    makefile: "make spserver" (UNIX) or "nmake spserver" (Windows).
**
** CATALOGING THE STORED PROCEDURES
** 1. The stored procedures are cataloged automatically when you build
**    the client application "spclient" using the appropriate "make" utility
**    for your Operating System and the "makefile" provided with these 
**    samples. If you wish to catalog or recatalog them manually, enter 
**    "spcat". The spcat script (UNIX) or spcat.bat batch file (Windows) 
**    connects to the database, runs spdrop.db2 to uncatalog the stored 
**    procedures if they were previously cataloged, then runs spcreate.db2 
**    which catalogs the stored procedures, then disconnects from the 
**    database.
**
** CALLING THE STORED PROCEDURES IN THE SHARED LIBRARY:
** 1. Compile the spclient program with "bldapp spclient" or use the 
**    makefile: "make spclient" (UNIX) or "nmake spclient" (Windows).
** 2. Run spclient: "spclient" (if calling remotely add the parameters for
**    database, user ID and password.)
**
** SQL STATEMENTS USED:
**         CLOSE
**         DECLARE
**         FETCH
**         FREE LOCATOR
**         OPEN
**         SELECT
**         SELECT INTO
**         UPDATE
**         VALUES 
**         WHENEVER 
**
** STRUCTURES USED:
**         sqlca
**         sqlda
**
** EXTERNAL DEPENDENCIES:
**        This program must be built on a DB2 server.
**        Ensure existence of the sample database.
**        Precompile with the SQL precompiler (PREP in DB2)
**        Bind to a database (BIND in DB2)
**        Compile and link loop with the compiler supported on your 
**        platform.
**
**                           
***************************************************************************
**
** For more information on the sample programs, see the README file.
**
** For information on developing C++ applications, see the Application
** Development Guide.
**
** For information on using SQL statements, see the SQL Reference.
**
** For the latest information on programming, building, and running DB2 
** applications, visit the DB2 Information Center: 
**     http://publib.boulder.ibm.com/infocenter/db2luw/v9r7/index.jsp
***************************************************************************/
 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sqlda.h>
#include <sqlca.h>
#include <sqludf.h>
#include <sql.h>
#include <memory.h>

//  NOTE:
//  -----
//  If a routine (stored procedure or user-defined function
//  (UDF)) calls another routine within the same library, you must
//  declare the function prototypes here (above the actual routine
//  implementations).

// declare function prototypes for this stored procedure library
extern "C" SQL_API_RC SQL_API_FN OutLanguage(
                                   char *,
                                   sqlint16 *,
                                   char *,
                                   char *,
                                   char *,
                                   char *);
extern "C" SQL_API_RC SQL_API_FN OutParam(
                                   double *,
                                   sqlint16 *,
                                   char *,
                                   char *,
                                   char *,
                                   char *);
extern "C" SQL_API_RC SQL_API_FN InParams(
                                   double *,
                                   double *, 
                                   double *,
                                   char *, 
                                   sqlint16 *, 
                                   sqlint16 *,
                                   sqlint16 *, 
                                   sqlint16 *, 
                                   char *, 
                                   char *, 
                                   char *, 
                                   char *);
extern "C" SQL_API_RC SQL_API_FN InOutParam(
                                   double *, 
                                   sqlint16 *, 
                                   char *, 
                                   char *,
                                   char *, 
                                   char *);
extern "C" SQL_API_RC SQL_API_FN ExtractFromClob(
                                   char *, 
                                   char *,
                                   sqlint16 *, 
                                   sqlint16 *,
                                   char *, 
                                   char *,
                                   char *, 
                                   char *);
extern "C" SQL_API_RC SQL_API_FN DbinfoExample(
                                   char *,
                                   double *,
                                   char *,
                                   char *,
                                   sqlint16 *,
                                   sqlint16 *,
                                   sqlint16 *,
                                   sqlint16 *,
                                   char *,
                                   char *,
                                   char *,
                                   char *,
                                   struct sqludf_dbinfo *);
extern "C" SQL_API_RC SQL_API_FN MainExample(
                                   int,
                                   char **);
extern "C" SQL_API_RC SQL_API_FN AllDataTypes(
                                   sqlint16 *,
                                   sqlint32 *,
                                   sqlint64 *,
                                   float *,
                                   double *,
                                   char *,
                                   char *,
                                   char *,
                                   char *,
                                   char *,
                                   sqlint16 *,
                                   sqlint16 *,
                                   sqlint16 *,
                                   sqlint16 *,
                                   sqlint16 *,
                                   sqlint16 *,
                                   sqlint16 *,
                                   sqlint16 *,
                                   sqlint16 *,
                                   sqlint16 *,
                                   SQLUDF_TRAIL_ARGS);
extern "C" SQL_API_RC SQL_API_FN OneResultSetToClient(
                                   double *, 
                                   sqlint16 *, 
                                   char *,
                                   char *,
                                   char *,
                                   char *);
extern "C" SQL_API_RC SQL_API_FN TwoResultSets(
                                   double *, 
                                   sqlint16 *, 
                                   SQLUDF_TRAIL_ARGS);
extern "C" SQL_API_RC SQL_API_FN GeneralExample(
                                   sqlint32 *,
                                   sqlint32 *,
                                   char *);
extern "C" SQL_API_RC SQL_API_FN GeneralWithNullsExample(
                                   sqlint32 *,
                                   sqlint32 *,
                                   char *,
                                   sqlint16 *);  

//  a description of each routine and its parameters is provided
//  with the code body of each routine in this file (see below). 

//*************************************************************************
//  Stored Procedure: OutLanguage
// 
//  Purpose:  Returns the code implementation language of
//            routine 'OutLanguage' (as it appears in the
//            database catalog) in an output parameter.
//
//            Shows how to:
//             - define an OUT parameter in PARAMETER STYLE SQL
//             - define a NULL indicator for the parameter
//             - execute an SQL statement
//             - how to set a Null indicator when parameter is
//               not null
//
//  Parameters:
//
//   IN:      (none)
//   OUT:     outLanguage - the code language of this routine
//
//            When the PARAMETER STYLE SQL clause is specified 
//            in the CREATE PROCEDURE statement for the procedure
//            (see the script spcreate.db2), in addition to the 
//            parameters passed at procedure invocation time, the 
//            following parameters are passed to the routine
//            in the following order:
//
//             - one null indicator for each IN/INOUT/OUT parameter
//               is specified in the same order as the corresponding
//               parameter declarations.
//             - sqlstate: to be returned to the caller to indicate 
//               state (output)
//             - routine-name: qualified name of the routine (input)
//             - specific-name: the specific name of the routine (input)
//             - diagnostic-message: an optional text string returned to the
//               caller (output)
//
//            See the actual parameter declarations below to see
//            the recommended datatypes and sizes for them.
//
//            CODE TIP:
//            --------
//            As an alternative to coding the non-functional parameters 
//            required with parameter style SQL (sqlstate, routine-name, 
//            specific-name, diagnostic-message), you can use a macro:
//            SQLUDF_TRAIL_ARGS. This macro is defined in DB2 include
//            file sqludf.h
//   
//            TIP EXAMPLE:
//            ------------
//            The following stored procedure prototype is equivalent 
//            to the actual prototype implementation for this stored 
//            procedure. It is simpler to implement. See stored 
//            procedure sample TwoResultSets in this file to see the
//            SQLUDF_TRAIL_ARGS macro in use.
//    
//              SQL_API_RC SQL_API_FN OutLanguage(
//                                      char outLanguage[9],
//                                      sqlint16 *outLanguageNullInd,
//                                      SQLUDF_TRAIL_ARGS)
//
//  Note: With parameter style SQL it is mandatory to declare either the 
//        four non-functional parameters (sqlstate, routine-name, 
//        specific-name, diagnostic-message) or SQLUDF_TRAIL_ARGS macro.
// 
//*************************************************************************
SQL_API_RC SQL_API_FN OutLanguage(char outLanguage[9], // CHAR(8)
                                  sqlint16 *outLanguageNullInd,
                                  char sqlstate[6],      
                                  char qualName[28],  
                                  char specName[19],  
                                  char diagMsg[71])
{
  EXEC SQL INCLUDE SQLCA; 
    
  // SQL host variable declaration section 
  // each host variable names must be unique within a code
  // file, or the precompiler will raise SQL0307 error 
  EXEC SQL BEGIN DECLARE SECTION;
    char hvOutLangLanguage[9];
  EXEC SQL END DECLARE SECTION;

  // text information appended to diagMsg upon the event of an error
  char sqlStmtInfo[60];
  
  // initialize output parameter string to NULL 
  memset(outLanguage, '\0', 9);
  *outLanguageNullInd = -1;
  
  // initialize sqlStmtInfo to NULL
  memset(sqlStmtInfo, '\0', 60);   

  EXEC SQL WHENEVER SQLERROR GOTO return_error;
  
  strcpy(sqlStmtInfo, "SELECT statement failed");

  // select the implementation language of this routine 
  // from the DB2 catalog table sysibm.sysprocedures    
  EXEC SQL SELECT language INTO :hvOutLangLanguage
             FROM sysibm.sysprocedures
               WHERE procname = 'OUT_LANGUAGE';
   
  // copy value of hvLanguage to output parameter language 
  strcpy(outLanguage, hvOutLangLanguage);

  // set NULL indicator for parameter 'outLanguage' to 0 to
  // indicate that output parameter 'outLanguage' is not NULL.
  // When the value to be returned is intended to be a NULL,
  // set the null indicator for that parameter to -1 
  *outLanguageNullInd = 0;

  return (0);
  
  // set custom diagMsg and sqlstate to return to client 
  return_error:
  {
    strcpy(sqlstate, sqlca.sqlstate);  
    
    sprintf(diagMsg, "%d", sqlca.sqlcode);
    strcat(diagMsg, ": ");
    strcat(diagMsg, sqlStmtInfo);

    return (0);  
  }  
} // OutLanguage function 

//*************************************************************************
//  Stored Procedure: OutParam
//
//  Purpose:  Sorts table STAFF by salary, locates and returns
//            the median salary
//
//            Shows how to:
//             - define OUT parameters in PARAMETER STYLE SQL
//             - execute SQL to declare and work with a cursor
//             - how to set a Null indicator when parameter is
//               not null
//             - define the extra parameters associated with
//               PARAMETER STYLE SQL 
//
//  Parameters:
//
//   IN:      (none)
//   OUT:     outMedianSalary - median salary in table STAFF
//
//            When the PARAMETER STYLE SQL clause is specified 
//            in the CREATE PROCEDURE statement for the procedure
//            (see the script spcreate.db2), in addition to the 
//            parameters passed at procedure invocation time, the 
//            following parameters are passed to the routine
//            in the following order:
//
//             - one null indicator for each IN/INOUT/OUT parameter
//               is specified in the same order as the corresponding
//               parameter declarations.
//             - sqlstate: to be returned to the caller to indicate 
//               state (output)
//             - routine-name: qualified name of the routine (input)
//             - specific-name: the specific name of the routine (input)
//             - diagnostic-message: an optional text string returned to the
//               caller (output)
//
//            See the actual parameter declarations below to see
//            the recommended datatypes and sizes for them.
//
//            CODE TIP:
//            --------
//            As an alternative to coding the non-functional parameters 
//            required with parameter style SQL (sqlstate, routine-name, 
//            specific-name, diagnostic-message), you can use a macro:
//            SQLUDF_TRAIL_ARGS. This macro is defined in DB2 include
//            file sqludf.h
//   
//            TIP EXAMPLE:
//            ------------
//            The following stored procedure prototype is equivalent 
//            to the actual prototype implementation for this stored 
//            procedure. It is simpler to implement. See stored 
//            procedure sample TwoResultSets in this file to see the
//            SQLUDF_TRAIL_ARGS macro in use.
//    
//              SQL_API_RC SQL_API_FN OutParam(
//                                      double *outMedianSalary, 
//                                      sqlint16 *outMedianSalaryNullInd,
//                                      SQLUDF_TRAIL_ARGS)
//
//  Note: With parameter style SQL it is mandatory to declare either the 
//        four non-functional parameters (sqlstate, routine-name, 
//        specific-name, diagnostic-message) or SQLUDF_TRAIL_ARGS macro.
// 
//*************************************************************************
SQL_API_RC SQL_API_FN OutParam(double *outMedianSalary, 
                               sqlint16 *outMedianSalaryNullInd,
                               char sqlstate[6],      
                               char qualName[28],  
                               char specName[19],  
                               char diagMsg[71])
{
  EXEC SQL INCLUDE SQLCA; 
  
  // SQL host variable declaration section 
  // each host variable names must be unique within a code
  // file, or the precompiler will raise SQL0307 error 
  EXEC SQL BEGIN DECLARE SECTION;
    sqlint32 hvOutParamNumRecords;
    double hvOutParamSalary;
  EXEC SQL END DECLARE SECTION;
  
  // text information appended to diagMsg upon the event of an error
  char sqlStmtInfo[60];

  // local variable 
  int counter = 0;
  
  // initialize output parameter 
  *outMedianSalary = 0;
  *outMedianSalaryNullInd = -1;
  
  // initialize sqlStmtInfo to NULL
  memset(sqlStmtInfo, '\0', 60); 

  EXEC SQL WHENEVER SQLERROR GOTO return_error;

  strcpy(sqlStmtInfo, "DECLARE statement failed");
    
  EXEC SQL DECLARE curOutParam CURSOR FOR 
             SELECT CAST(salary AS DOUBLE)
               FROM staff
                 ORDER BY salary;

  strcpy(sqlStmtInfo, "SELECT statement failed");
  
  EXEC SQL SELECT COUNT(*) INTO: hvOutParamNumRecords FROM staff; 

  strcpy(sqlStmtInfo, "OPEN statement failed");
  
  EXEC SQL OPEN curOutParam;
  
  strcpy(sqlStmtInfo, "FETCH statement failed");
  
  while (counter < (hvOutParamNumRecords / 2 + 1))
  {                                
    EXEC SQL FETCH curOutParam INTO :hvOutParamSalary; 
    counter = counter + 1;
  }

  // set value of OUT parameter to host variable                             
  *outMedianSalary = hvOutParamSalary; 
  *outMedianSalaryNullInd = 0;
  
  strcpy(sqlStmtInfo, "CLOSE statement failed");
  
  EXEC SQL CLOSE curOutParam;

  return (0); 
  
  // set custom diagMsg and sqlstate to return to client 
  return_error:
  { 
    strcpy(sqlstate, sqlca.sqlstate);  
    
    sprintf(diagMsg, "%d", SQLCODE);
    strcat(diagMsg, ": ");
    strcat(diagMsg, sqlStmtInfo);   
    
    return (0);
  }
} // OutParam function 

//*************************************************************************
//  Stored Procedure: InParams
//
//  Purpose:  Updates salaries of employees in department indept
//            using inputs inLowSal, inMedSal, inHighSal as
//            salary raise or adjustment values.
//
//            Shows how to:
//             - define IN parameters using PARAMETER STYLE SQL
//             - define and use NULL indicators for parameters
//             - define the extra parameters associated with
//               PARAMETER STYLE SQL
//
//  Parameters:
//
//   IN:      inLowSal    - new salary for low salary employees
//            inMedSal    - new salary for mid salary employees
//            inHighSal   - new salary for high salary employees
//            inDept      - department to use in SELECT predicate
//   OUT:     (none)
//
//            When the PARAMETER STYLE SQL clause is specified 
//            in the CREATE PROCEDURE statement for the procedure
//            (see the script spcreate.db2), in addition to the 
//            parameters passed at procedure invocation time, the 
//            following parameters are passed to the routine
//            in the following order:
//
//             - one null indicator for each IN/INOUT/OUT parameter
//               is specified in the same order as the corresponding
//               parameter declarations.
//             - sqlstate: to be returned to the caller to indicate 
//               state (output)
//             - routine-name: qualified name of the routine (input)
//             - specific-name: the specific name of the routine (input)
//             - diagnostic-message: an optional text string returned to the
//               caller (output)
//
//            See the actual parameter declarations below to see
//            the recommended datatypes and sizes for them.
//
//            CODE TIP:
//            --------
//            As an alternative to coding the non-functional parameters 
//            required with parameter style SQL (sqlstate, routine-name, 
//            specific-name, diagnostic-message), you can use a macro:
//            SQLUDF_TRAIL_ARGS. This macro is defined in DB2 include
//            file sqludf.h
//   
//            TIP EXAMPLE:
//            ------------
//            The following stored procedure prototype is equivalent 
//            to the actual prototype implementation for this stored 
//            procedure. It is simpler to implement. See stored 
//            procedure sample TwoResultSets in this file to see the
//            SQLUDF_TRAIL_ARGS macro in use.
//    
//              SQL_API_RC SQL_API_FN InParams(
//                                      double *inLowSal,
//                                      double *inMedSal,
//                                      double *inHighSal,
//                                      char inDept[4],
//                                      sqlint16 *inLowSalNullInd,
//                                      sqlint16 *inMedSalNullInd,
//                                      sqlint16 *inHighSalNullInd,
//                                      sqlint16 *inDeptNullInd,
//                                      SQLUDF_TRAIL_ARGS)
//
//  Note: With parameter style SQL it is mandatory to declare either the 
//        four non-functional parameters (sqlstate, routine-name, 
//        specific-name, diagnostic-message) or SQLUDF_TRAIL_ARGS macro.
// 
//*************************************************************************
SQL_API_RC SQL_API_FN InParams(double *inLowSal,
                               double *inMedSal,
                               double *inHighSal,
                               char inDept[4], // CHAR(3)
                               sqlint16 *inLowSalNullInd,
                               sqlint16 *inMedSalNullInd,
                               sqlint16 *inHighSalNullInd,
                               sqlint16 *inDeptNullInd,
                               char sqlstate[6],      
                               char qualName[28],  
                               char specName[19],  
                               char diagMsg[71]) 
{
  EXEC SQL INCLUDE SQLCA; 
  
  // SQL host variable declaration section 
  // each host variable names must be unique within a code
  // file, or the precompiler will raise SQL0307 error 
  EXEC SQL BEGIN DECLARE SECTION;
    double hvInParamLowSal;
    double hvInParamMedSal;
    double hvInParamHighSal;
    double hvInParamSalary;
    char hvInParamDept[4];
  EXEC SQL END DECLARE SECTION;

  // text information appended to diagMsg upon the event of an error
  char sqlStmtInfo[60];

  // initialize sqlStmtInfo to NULL
  memset(sqlStmtInfo, '\0', 60); 

  EXEC SQL WHENEVER SQLERROR GOTO return_error;
 
  if ((*inLowSalNullInd) < 0 || 
      (*inMedSalNullInd) < 0 ||  
      (*inHighSalNullInd) < 0 ||
      (*inDeptNullInd) < 0)
  {
    // set custom sqlstate to return to client. 
    strcpy(sqlstate, "38100");

    // set custom message to return to client. Note that although the
    // OUT parameter is declared as CHAR(70), DB2 prepends the
    // procedure name and shared library entry point to the message.
    // Keep the custom message short to avoid truncation. 
    strcpy(diagMsg, "Received null input.");
    
    return(0);
  }
  
  // copy input parameters to local host variables 
  hvInParamLowSal = *inLowSal;
  hvInParamMedSal = *inMedSal;
  hvInParamHighSal = *inHighSal;
  strcpy(hvInParamDept, inDept);

  strcpy(sqlStmtInfo, "DECLARE statement failed");
    
  EXEC SQL DECLARE curInParam CURSOR FOR
             SELECT CAST(salary AS DOUBLE)
               FROM employee
                 WHERE workdept = :hvInParamDept FOR UPDATE;
  
  strcpy(sqlStmtInfo, "OPEN statement failed");
  
  EXEC SQL OPEN curInParam;

  strcpy(sqlStmtInfo, "FETCH statement failed");
   
  EXEC SQL FETCH curInParam INTO :hvInParamSalary;

  if (sqlca.sqlcode == 100)
  {
    strcpy(sqlstate, "38200");  
      
    // fetch returned no data, so exit the stored procedure 
    strcpy(diagMsg, " 100: NO DATA FOUND");

    strcpy(sqlStmtInfo, "CLOSE statement failed");

    // close cursor before exiting 
    EXEC SQL CLOSE curInParam;

    return (0);
  }    
  
  while (sqlca.sqlcode == 0)
  {
    strcpy(sqlStmtInfo, "UPDATE statement failed");   

    if (hvInParamLowSal > hvInParamSalary)
    {
      EXEC SQL UPDATE employee SET salary = :hvInParamLowSal
	         WHERE CURRENT OF curInParam;
    }
    else if (hvInParamMedSal > hvInParamSalary)
    {
      EXEC SQL UPDATE employee SET salary = :hvInParamMedSal 
	         WHERE CURRENT OF curInParam;
    }
    else if (hvInParamHighSal > hvInParamSalary)
    {
      EXEC SQL UPDATE employee SET salary = :hvInParamHighSal 
	         WHERE CURRENT OF curInParam;
    }
    else
    {
      EXEC SQL UPDATE employee
                 SET salary = :hvInParamSalary * 1.10
                   WHERE CURRENT OF curInParam;
    }
    
    strcpy(sqlStmtInfo, "FETCH statement failed");
    
    EXEC SQL FETCH curInParam INTO: hvInParamSalary;
  }

  strcpy(sqlStmtInfo, "CLOSE statement failed");
    
  EXEC SQL CLOSE curInParam;
  
  return (0);

  // set custom diagMsg and sqlstate to return to client 
  return_error:
  {  
    strcpy(sqlstate, sqlca.sqlstate);  
    
    sprintf(diagMsg, "%d", sqlca.sqlcode);
    strcat(diagMsg, ": ");
    strcat(diagMsg, sqlStmtInfo);
    return (0);
  }
} // InParams function 

//*************************************************************************
//  Stored Procedure: InOutParam
//
//  Purpose:  Calculates the median salary of all salaries above
//            the input median salary.
//
//            Shows how to:
//             - define an INOUT parameter using PARAMETER STYLE SQL
//             - define and use NULL indicators for parameters
//             - define the extra parameters associated with
//               PARAMETER STYLE SQL
//
//  Parameters:
//
//   IN/OUT:  inOutMedian - median salary
//                          input value used in SELECT predicate
//                          output set to median salary found
//
//            When the PARAMETER STYLE SQL clause is specified 
//            in the CREATE PROCEDURE statement for the procedure
//            (see the script spcreate.db2), in addition to the 
//            parameters passed at procedure invocation time, the 
//            following parameters are passed to the routine
//            in the following order:
//
//             - one null indicator for each IN/INOUT/OUT parameter
//               is specified in the same order as the corresponding
//               parameter declarations.
//             - sqlstate: to be returned to the caller to indicate 
//               state (output)
//             - routine-name: qualified name of the routine (input)
//             - specific-name: the specific name of the routine (input)
//             - diagnostic-message: an optional text string returned to the
//               caller (output)
//
//            See the actual parameter declarations below to see
//            the recommended datatypes and sizes for them.
//
//            CODE TIP:
//            --------
//            As an alternative to coding the non-functional parameters 
//            required with parameter style SQL (sqlstate, routine-name, 
//            specific-name, diagnostic-message), you can use a macro:
//            SQLUDF_TRAIL_ARGS. This macro is defined in DB2 include
//            file sqludf.h
//   
//            TIP EXAMPLE:
//            ------------
//            The following stored procedure prototype is equivalent 
//            to the actual prototype implementation for this stored 
//            procedure. It is simpler to implement. See stored 
//            procedure sample TwoResultSets in this file to see the
//            SQLUDF_TRAIL_ARGS macro in use.
//    
//              SQL_API_RC SQL_API_FN InOutParam(double *inOutMedian,  
//                                      sqlint16 *inOutMedianNullInd, 
//                                      SQLUDF_TRAIL_ARGS)
//
//  Note: With parameter style SQL it is mandatory to declare either the 
//        four non-functional parameters (sqlstate, routine-name, 
//        specific-name, diagnostic-message) or SQLUDF_TRAIL_ARGS macro.
// 
//*************************************************************************
SQL_API_RC SQL_API_FN InOutParam(double *inOutMedian,  
                                 sqlint16 *inOutMedianNullInd,   
                                 char sqlstate[6],      
                                 char qualName[28],  
                                 char specName[19],  
                                 char diagMsg[71]) 
{
  EXEC SQL INCLUDE SQLCA; 
  
  // SQL host variable declaration section 
  // each host variable name must be unique within a code
  // file, or the precompiler will raise SQL0307 error 
  EXEC SQL BEGIN DECLARE SECTION;
    double hvInOutParamMedianSal;
    sqlint32 hvInOutParamNumRecords;
  EXEC SQL END DECLARE SECTION;

  // text information appended to diagMsg upon the event of an error
  char sqlStmtInfo[60];

  // local variable declaration 
  int counter;
  
  // initialize sqlStmtInfo to NULL
  memset(sqlStmtInfo, '\0', 60); 

  EXEC SQL WHENEVER SQLERROR GOTO return_error;
  
  if ((*inOutMedianNullInd) < 0)
  {
    // set custom sqlstate to return to client. 
    strcpy(sqlstate, "38100");
    
    // set the null indicator
    *inOutMedianNullInd = -1;
    
    // set custom message to return to client. Note that although the
    // OUT parameter is declared as CHAR(70), DB2 prepends the
    // procedure name and shared library entry point to the message.
    // Keep the custom message short to avoid truncation. 
    strcpy(diagMsg, "Received null input.");
    return (0);
  }
  
  if ((*inOutMedian) < 0)
  {
    // set custom sqlstate to return to client. 
    strcpy(sqlstate, "38100");

    // set the null indicator
    *inOutMedianNullInd = -1;
    
    strcpy(diagMsg, "Received invalid input.");
    return (0);
  }
    
  counter = 0;
  hvInOutParamMedianSal = *inOutMedian;
   
  strcpy(sqlStmtInfo, "DECLARE statement failed");
    
  EXEC SQL DECLARE curInOutParam CURSOR FOR
    SELECT CAST(salary AS DOUBLE)
      FROM staff
        WHERE salary > :hvInOutParamMedianSal
          ORDER BY salary;

  strcpy(sqlStmtInfo, "SELECT statement failed");

  EXEC SQL SELECT COUNT(*) INTO :hvInOutParamNumRecords
             FROM staff
               WHERE salary > :hvInOutParamMedianSal;

  if (hvInOutParamNumRecords > 0) // at least one record was found 
  {
    strcpy(sqlStmtInfo, "OPEN statement failed");
  
    EXEC SQL OPEN curInOutParam USING :hvInOutParamMedianSal;
     
    strcpy(sqlStmtInfo, "FETCH statement failed");
     
    while (counter < (hvInOutParamNumRecords / 2 + 1))
    {
      EXEC SQL FETCH curInOutParam INTO :hvInOutParamMedianSal;
      counter = counter + 1;
    }

    // set value of INOUT parameter to host variable 
    *inOutMedian = hvInOutParamMedianSal;     

    strcpy(sqlStmtInfo, "CLOSE statement failed");

    EXEC SQL CLOSE curInOutParam;
  }
  else // no records were found 
  {
    *inOutMedianNullInd = -1;
       
    // return the custom error state and error message to client 
    strcpy(sqlstate, "38200");
    strcpy(diagMsg, "100: NO DATA FOUND");
  }
  
  return (0);
  
  // set custom diagMsg and sqlstate to return to client 
  return_error:
  { 
    strcpy(sqlstate, sqlca.sqlstate);  
    
    sprintf(diagMsg, "%d", sqlca.sqlcode);
    strcat(diagMsg, ": ");
    strcat(diagMsg, sqlStmtInfo); 
    return (0);
  }
} // InOutParam function 

//*************************************************************************
//  Stored Procedure: ExtractFromClob
//
//  Purpose:  Extracts department information from a large object (LOB) 
//            resume of employee data returns this information
//            to the caller in output parameter outDeptInfo.
//
//            Shows how to:
//             - define IN and OUT parameters in STYLE SQL 
//             - define a local lob locator variable
//             - locate information within a formatted lob
//             - extract information from within a clob/copy it
//               to a host variable
//
//  Parameters:
//  
//   IN:      inEmpNumber - employee number
//   OUT:     outDeptInfo - department information section of the 
//            employee's resume     
//
//            When the PARAMETER STYLE SQL clause is specified 
//            in the CREATE PROCEDURE statement for the procedure
//            (see the script spcreate.db2), in addition to the 
//            parameters passed at procedure invocation time, the 
//            following parameters are passed to the routine
//            in the following order:
//
//             - one null indicator for each IN/INOUT/OUT parameter
//               is specified in the same order as the corresponding
//               parameter declarations.
//             - sqlstate: to be returned to the caller to indicate 
//               state (output)
//             - routine-name: qualified name of the routine (input)
//             - specific-name: the specific name of the routine (input)
//             - diagnostic-message: an optional text string returned to the
//               caller (output)
//
//            See the actual parameter declarations below to see
//            the recommended datatypes and sizes for them.
//
//            CODE TIP:
//            --------
//            As an alternative to coding the non-functional parameters 
//            required with parameter style SQL (sqlstate, routine-name, 
//            specific-name, diagnostic-message), you can use a macro:
//            SQLUDF_TRAIL_ARGS. This macro is defined in DB2 include
//            file sqludf.h
//   
//            TIP EXAMPLE:
//            ------------
//            The following stored procedure prototype is equivalent 
//            to the actual prototype implementation for this stored 
//            procedure. It is simpler to implement. See stored 
//            procedure sample TwoResultSets in this file to see the
//            SQLUDF_TRAIL_ARGS macro in use.
//
//              SQL_API_RC SQL_API_FN ExtractFromClob(
//                                      char inEmpNumber[7],
//                                      char outDeptInfo[1001],
//                                      sqlint16 *inEmpNumberNullInd,
//                                      sqlint16 *outDeptInfoNullInd,
//                                      SQLUDF_TRAIL_ARGS)
//
//  Note: With parameter style SQL it is mandatory to declare either the 
//        four non-functional parameters (sqlstate, routine-name, 
//        specific-name, diagnostic-message) or SQLUDF_TRAIL_ARGS macro.
// 
//*************************************************************************
SQL_API_RC SQL_API_FN ExtractFromClob(char inEmpNumber[7], // CHAR(6) 
                                      char outDeptInfo[1001], // VARCHAR(1000)
                                      sqlint16 *inEmpNumberNullInd,
                                      sqlint16 *outDeptInfoNullInd,
                                      char sqlstate[6],
                                      char qualName[28],
                                      char specName[19],
                                      char diagMsg[71])
{
  EXEC SQL INCLUDE SQLCA; 

  // SQL host variable declaration section  
  // Each host variable names must be unique within a code
  // file, or the precompiler will raise SQL0307 error 
  EXEC SQL BEGIN DECLARE SECTION;
    char     hvExtFromClobEmpNum[7];
    char     hvExtFromClobResume[1001];
    sqlint32 hvExtFromClobDeptInfoBeginLoc;
    sqlint32 hvExtFromClobDeptInfoEndLoc;
    SQL TYPE IS CLOB_LOCATOR locExtFromClobResume;
    SQL TYPE IS CLOB_LOCATOR locExtFromClobDept;
    sqlint16 hvExtFromClobDeptLobInd;
  EXEC SQL END DECLARE SECTION;

  // text information appended to diagMsg upon the event of an error
  char sqlStmtInfo[60];

  // initialize sqlStmtInfo to NULL
  memset(sqlStmtInfo, '\0', 60); 

  // initialize output parameters (strings set to NULL) 
  memset(hvExtFromClobResume, '\0', 1001);
  memset(outDeptInfo, '\0', 1001);
  *outDeptInfoNullInd = -1;
  
  EXEC SQL WHENEVER SQLERROR GOTO return_error;  
  
  // copy input into local host variable 
  strcpy(hvExtFromClobEmpNum, inEmpNumber);
  
  strcpy(sqlStmtInfo, "SELECT statement failed");

  EXEC SQL SELECT resume INTO :locExtFromClobResume:hvExtFromClobDeptLobInd
             FROM emp_resume
             WHERE resume_format = 'ascii' AND empno =:hvExtFromClobEmpNum;

  if (hvExtFromClobDeptLobInd < 0)
  {
    // set custom sqlstate to return to client. 
    strcpy(sqlstate, "38200");

    // set custom message to return to client. Note that although the
    // OUT parameter is declared as CHAR(70), DB2 prepends the
    // procedure name and shared library entry point to the message.
    // Keep the custom message short to avoid truncation. 
    strcpy(diagMsg,
           "NULL value returned for CLOB.\n");
  }
  else
  {
    // locate the beginning of the 'Department Information' section of
    // the lob resume 
    
    strcpy(sqlStmtInfo, "VALUES statement failed(1)");
  
    EXEC SQL VALUES(POSSTR(:locExtFromClobResume, 'Department Information'))
               INTO :hvExtFromClobDeptInfoBeginLoc;

    // locate the beginning of the 'Education' section - this marks the end
    // of the 'Department Information' section of the lob resume 
    strcpy(sqlStmtInfo, "VALUES statement failed(2)");
           
    EXEC SQL VALUES(POSSTR(:locExtFromClobResume, 'Education')) 
	       INTO :hvExtFromClobDeptInfoEndLoc;

    // obtain ONLY the "Department Information" section by using SUBSTR 
    strcpy(sqlStmtInfo, "VALUES statement failed(3)");
           
    EXEC SQL VALUES(SUBSTR(:locExtFromClobResume, 
			               :hvExtFromClobDeptInfoBeginLoc,
                           :hvExtFromClobDeptInfoEndLoc -
                           :hvExtFromClobDeptInfoBeginLoc))
               INTO:locExtFromClobDept;

    // append the "Department Information" section to the :out_resume var. 
    strcpy(sqlStmtInfo, "VALUES statement failed(4)");
           
    EXEC SQL VALUES(:hvExtFromClobResume || :locExtFromClobDept) 
	       INTO :hvExtFromClobResume;

    strcpy(outDeptInfo, hvExtFromClobResume);
    *outDeptInfoNullInd = 0;
  } // endif 
 
  strcpy(sqlStmtInfo, "FREE statement failed");

  EXEC SQL FREE LOCATOR :locExtFromClobResume, :locExtFromClobDept;

  return (0);

  // set custom diagMsg and sqlstate to return to client 
  return_error:
  {   
    strcpy(sqlstate, sqlca.sqlstate);  
    
    sprintf(diagMsg, "%d", sqlca.sqlcode);
    strcat(diagMsg, ": ");
    strcat(diagMsg, sqlStmtInfo);    
    return (0);
  }
} // ExtractFromClob procedure 

//*************************************************************************
//  Stored Procedure: DbinfoExample
//
//  Purpose:  This routine takes in a job type and returns the
//            average salary of all employees with that job, as
//            well as information about the database (name,
//            version of database).  The database information
//            is retrieved from the dbinfo object.
//
//            Shows how to:
//             - define IN/ OUT parameters in PARAMETER STYLE SQL
//             - declare a parameter pointer to the dbinfo structure
//             - retrieve values from the dbinfo structure
//
//  Parameters:
//
//   IN:      inJob  - a job type, used in a SELECT predicate 
//   OUT:     outSalary - average salary of employees with job specified 
//            by injob
//            outDbName - database name retrieved from DBINFO
//            outDbVersion - database version retrieved from DBINFO
//            sqludf_dbinfo - pointer to DBINFO structure
//
//            When the PARAMETER STYLE SQL clause is specified 
//            in the CREATE PROCEDURE statement for the procedure
//            (see the script spcreate.db2), in addition to the 
//            parameters passed at procedure invocation time, the 
//            following parameters are passed to the routine
//            in the following order:
//
//             - one null indicator for each IN/INOUT/OUT parameter
//               is specified in the same order as the corresponding
//               parameter declarations.
//             - sqlstate: to be returned to the caller to indicate 
//               state (output)
//             - routine-name: qualified name of the routine (input)
//             - specific-name: the specific name of the routine (input)
//             - diagnostic-message: an optional text string returned to the
//               caller (output)
//
//            See the actual parameter declarations below to see
//            the recommended datatypes and sizes for them.
//
//            CODE TIP:
//            --------
//            As an alternative to coding the non-functional parameters 
//            required with parameter style SQL (sqlstate, routine-name, 
//            specific-name, diagnostic-message), you can use a macro:
//            SQLUDF_TRAIL_ARGS. This macro is defined in DB2 include
//            file sqludf.h
//   
//            TIP EXAMPLE:
//            ------------
//            The following stored procedure prototype is equivalent 
//            to the actual prototype implementation for this stored 
//            procedure. It is simpler to implement. See stored 
//            procedure sample TwoResultSets in this file to see the
//            SQLUDF_TRAIL_ARGS macro in use.
//    
//              SQL_API_RC SQL_API_FN DbinfoExample(char inJob[9],    
//                                      double *outSalary,   
//                                      char outDbName[129],  
//                                      char outDbVersion[9], 
//                                      sqlint16 *inJobNullInd,
//                                      sqlint16 *outSalaryNullInd,
//                                      sqlint16 *outDbNameNullInd,
//                                      sqlint16 *outDbVersionNullInd,
//                                      SQLUDF_TRAIL_ARGS,
//                                      struct sqludf_dbinfo * dbinfo)                                 
//
//  Note: With parameter style SQL it is mandatory to declare either the 
//        four non-functional parameters (sqlstate, routine-name, 
//        specific-name, diagnostic-message) or SQLUDF_TRAIL_ARGS macro.
// 
//*************************************************************************
SQL_API_RC SQL_API_FN DbinfoExample(char inJob[9], // CHAR(8)    
                                    double *outSalary,   
                                    char outDbName[129], // CHAR(128) 
                                    char outDbVersion[9], // CHAR(8)
                                    sqlint16 *inJobNullInd,
                                    sqlint16 *outSalaryNullInd,
                                    sqlint16 *outDbNameNullInd,
                                    sqlint16 *outDbVersionNullInd, 
                                    char sqlstate[6],      
                                    char qualName[28],  
                                    char specName[19],  
                                    char diagMsg[71],
                                    struct sqludf_dbinfo *dbinfo)
{
  EXEC SQL INCLUDE SQLCA; 

  // SQL host variable declaration section 
  // Each host variable names must be unique within a code
  // file, or the precompiler will raise SQL0307 error 
  EXEC SQL BEGIN DECLARE SECTION;
    char hvDbinfoJob[9];
    double hvDbinfoSalary;
  EXEC SQL END DECLARE SECTION;

  // text information appended to diagMsg upon the event of an error
  char sqlStmtInfo[60];

  // initialize sqlStmtInfo to NULL
  memset(sqlStmtInfo, '\0', 60); 

  // initialize output parameters - set strings to NULL 
  memset(outDbName, '\0', 129);
  memset(outDbVersion, '\0', 9);
  *outSalary = 0;
  *outSalaryNullInd = -1;
  *outDbNameNullInd = -1;
  *outDbVersionNullInd = -1;
  
  EXEC SQL WHENEVER SQLERROR GOTO return_error;
  
  if ((*inJobNullInd) < 0)
  {
    // set custom sqlstate to return to client. 
    strcpy(sqlstate, "38100");

    // set custom message to return to client. Note that although the
    // OUT parameter is declared as CHAR(70), DB2 prepends the
    // procedure name and shared library entry point to the message.
    // Keep the custom message short to avoid truncation. 
    strcpy(diagMsg, "Received null input.");
    *outSalaryNullInd = -1;
  }
  else
  {
    // copy input parameter into local host variable 
    strcpy(hvDbinfoJob, inJob);

    strcpy(sqlStmtInfo, "SELECT statement failed");

    EXEC SQL SELECT AVG(salary) INTO :hvDbinfoSalary
               FROM employee
                 WHERE job = :hvDbinfoJob;

    *outSalary = hvDbinfoSalary;
    *outSalaryNullInd = 0;
  }
   
  // copy values from the DBINFO structure into the output parameters 
  // You must explicitly null-terminate the strings.                  
  // Information such as the database name, and the version of the    
  // database product can be found in the DBINFO structure as well as 
  // other information fields. 
  strncpy(outDbName, (char *)(dbinfo->dbname), dbinfo->dbnamelen);
  outDbName[dbinfo->dbnamelen] = '\0';
  strncpy(outDbVersion, (char *)(dbinfo->ver_rel), 8);
  outDbVersion[8] = '\0';
  *outDbNameNullInd = 0;
  *outDbVersionNullInd = 0;  
  
  return (0);
  
  // set custom diagMsg and sqlstate to return to client 
  return_error:
  {  
    strcpy(sqlstate, sqlca.sqlstate);  
    
    sprintf(diagMsg, "%d", sqlca.sqlcode);
    strcat(diagMsg, ": ");
    strcat(diagMsg, sqlStmtInfo);    
    return (0);
  }
} // DbinfoExample function 

//*************************************************************************
//  Stored Procedure: MainExample
//                              
//  Purpose:  Returns the average salary of employees in table
//            employee that have the job specified by argv[1]
//
//            Shows how to:
//             - use standard argc and argv parameters to a main
//               C routine to pass parameters in and out
//             - define IN parameters using PARAMETER STYLE SQL
//             - define and use NULL indicators for parameters
//             - define the extra parameters associated with
//               PARAMETER STYLE SQL
//
//  Parameters:
//
//   IN:      argc    - count of the number of parameters
//            argv[1] - job type (char[8])
//   OUT:     argv[2] - average salary of employees with that job (double)
//                      
//            When the PARAMETER STYLE SQL clause is specified 
//            in the CREATE PROCEDURE statement for the procedure
//            (see the script spcreate.db2), in addition to the 
//            parameters passed at procedure invocation time, the 
//            following parameters are passed to the routine
//            in the following order:
//
//             - one null indicator for each IN/INOUT/OUT parameter
//               is specified in the same order as the corresponding
//               parameter declarations.
//             - sqlstate: to be returned to the caller to indicate 
//               state (output)
//             - routine-name: qualified name of the routine (input)
//             - specific-name: the specific name of the routine (input)
//             - diagnostic-message: an optional text string returned to the
//               caller (output)
//
//  Note: With parameter style SQL it is mandatory to declare either the 
//        four non-functional parameters (sqlstate, routine-name, 
//        specific-name, diagnostic-message) or SQLUDF_TRAIL_ARGS macro.
// 
//*************************************************************************
SQL_API_RC SQL_API_FN MainExample(int argc, char **argv)
{
  EXEC SQL INCLUDE SQLCA; 
  
  // SQL host variable declaration section 
  // Each host variable names must be unique within a code
  // file, or the precompiler will raise SQL0307 error 
  EXEC SQL BEGIN DECLARE SECTION;
    char hvMainJob[9];
    double hvMainSalary;                               
  EXEC SQL END DECLARE SECTION;

  // note:
  // argv[0]: program name
  // argv[1]: job type (char[8], input) 
  // argv[2]: average salary (double, output)
  // argv[3]: null indicator for job type 
  // argv[4]: null indicator for average salary
  // argv[5]: sqlstate (char[6], output)
  // argv[6]: qualName (char[28], output)
  // argv[7]: specName (char[19], output)
  // argv[8]: diagMsg (char[71], output)
  
  // initialize ouput null indicator   
  *(sqlint16 *)argv[4] = -1;
  
  // text information appended to diagMsg upon the event of an error
  char sqlStmtInfo[60];
  char sqlCode[10];

  strcpy(sqlStmtInfo, "\0");
  strcpy(sqlCode, "\0"); 
 
  // initialize sqlStmtInfo to NULL
  memset(sqlStmtInfo, '\0', 60);     
  
  EXEC SQL WHENEVER SQLERROR GOTO return_error;  
    
  // check the null indicator variable for the input parameter 
  if (*(sqlint16 *)argv[3] < 0)
  {
    // set custom SQLSTATE to return to client. 
    strcpy((char *)argv[5], "38100");

    // set custom message to return to client. Note that although the
    // OUT parameter is declared as CHAR(70), DB2 prepends the
    // procedure name and shared library entry point to the message.
    // Keep the custom message short to avoid truncation. 
    strcpy((char *)argv[8], "Received null input.");
    
    // set the null indicator variable for the output parameter
    // to indicate a null value
    *(sqlint16 *)argv[4] = -1;
    
    return (0);
  }
  else
  {
    // argv[0] contains the procedure name, so parameters start at argv[1] 
    strcpy(hvMainJob, (char *)argv[1]);

    strcpy(sqlStmtInfo, "SELECT statement failed");
    
    EXEC SQL SELECT AVG(salary)
      INTO :hvMainSalary
      FROM employee
      WHERE job = :hvMainJob;

    // set the output parameter values including the sqlca.sqlcode
    memcpy((double *)argv[2], (double *)&hvMainSalary, sizeof(double));
    
    // set the null indicator variable for the output parameter
    // to indicate a non-null value
    *(sqlint16 *)argv[4] = 0;
  }
 
  return (0);
 
  // set custom diagMsg and sqlstate to return to client 
  return_error:
  {    
    strcpy((char *)argv[5], sqlca.sqlstate);  
        
    sprintf(sqlCode, "%d", sqlca.sqlcode);    
    strcat(sqlCode, " : ");
    strcpy((char *)argv[8], sqlCode);
    strcat((char *)argv[8], sqlStmtInfo);
      
    return (0);
  }
} // MainExample function 

//*************************************************************************
// Embedded C/C++ stored procedures do not provide direct support for DECIMAL
// data type.
// The following programming languages can be used to directly  
// manipulate DECIMAL type: 
//          - JDBC
//          - SQLJ
//          - SQL routines
//          - .NET common language runtime languages (C#, Visual Basic) 
// Please see the SpServer implementation for one of the above 
// language to see this functionality.
//*************************************************************************

//*************************************************************************
//  Stored Procedure: AllDataTypes 
//
//  Purpose: Take each parameter and set it to a new output value.
//           This sample shows only a subset of DB2 supported data types.
//           For a full listing of DB2 data types, please see the SQL 
//           Reference. 
//
//            Shows how to:
//             - define INOUT/OUT parameters in PARAMETER STYLE SQL 
//             - declare host variables and assign values to them
//             - assign output values to INOUT/OUT parameters
//
//  Parameters:
//  
//   INOUT:   inOutSmall, inOutInt, inOutBig, inOutReal, outDouble
//   OUT:     outChar, outChars, outVarchar, outDate, outTime
//
//            When the PARAMETER STYLE SQL clause is specified 
//            in the CREATE PROCEDURE statement for the procedure
//            (see the script spcreate.db2), in addition to the 
//            parameters passed at procedure invocation time, the 
//            following parameters are passed to the routine
//            in the following order:
//
//             - one null indicator for each IN/INOUT/OUT parameter
//               is specified in the same order as the corresponding
//               parameter declarations.
//             - sqlstate: to be returned to the caller to indicate 
//               state (output)
//             - routine-name: qualified name of the routine (input)
//             - specific-name: the specific name of the routine (input)
//             - diagnostic-message: an optional text string returned to the
//               caller (output)
//
//             In this example we use macro SQLUDF_TRAIL_ARGS
//             (defined in sqludf.h - this file must be included)
//             to replace the 4 'extra' parameters:
//               sqlstate, qualified routine name, specific name of
//               routine, diagnostic string.
//
//             When referencing the 'extra' parameters, use the
//             parameter names provided in the macro definition in
//             sqludf.h:
//
//                sqludf_sqlstate
//                sqludf_fname
//                sqludf_fspecname
//                sqludf_msgtext
//
//  Note: With parameter style SQL it is mandatory to declare either the 
//        four non-functional parameters (sqlstate, routine-name, 
//        specific-name, diagnostic-message) or SQLUDF_TRAIL_ARGS macro.
//        
//*************************************************************************
SQL_API_RC SQL_API_FN AllDataTypes(sqlint16 *inOutSmall,
                                   sqlint32 *inOutInt,
                                   sqlint64 *inOutBig,
                                   float *inOutReal,
                                   double *inOutDouble,
                                   char outChar[2], // CHAR(1)
                                   char outChars[16], // CHAR(15)
                                   char outVarchar[13], // VARCHAR(13)
                                   char outDate[11], // DATE
                                   char outTime[9], // TIME
                                   sqlint16 *inOutSmallNullInd,
                                   sqlint16 *inOutIntNullInd,
                                   sqlint16 *inOutBigNullInd,
                                   sqlint16 *inOutRealNullInd,
                                   sqlint16 *inOutDoubleNullInd,
                                   sqlint16 *outCharNullInd,
                                   sqlint16 *outCharsNullInd,
                                   sqlint16 *outVarcharNullInd,
                                   sqlint16 *outDateNullInd,
                                   sqlint16 *outTimeNullInd,
                                   SQLUDF_TRAIL_ARGS)
{
  EXEC SQL INCLUDE SQLCA; 

  // SQL host variable declaration section 
  // Each host variable names must be unique within a code
  // file, or the precompiler will raise SQL0307 error 
  EXEC SQL BEGIN DECLARE SECTION;
    char hvAllDataTypesChar[2];     
    char hvAllDataTypesChars[16];   
    char hvAllDataTypesVarchar[13]; 
    char hvAllDataTypesDate[11];    
    char hvAllDataTypesTime[9];     
  EXEC SQL END DECLARE SECTION;

  // text information appended to diagMsg upon the event of an error
  char sqlStmtInfo[60];

  // initialize sqlStmtInfo to NULL
  memset(sqlStmtInfo, '\0', 60); 

  // initialize the OUT parameters and Null indicators
  memset(outChar, '\0', 2);
  memset(outChars, '\0', 16);
  memset(outVarchar, '\0', 13);
  memset(outDate, '\0', 11);
  memset(outTime, '\0', 9);
  *outCharNullInd = -1;
  *outCharsNullInd = -1;
  *outVarcharNullInd = -1;
  *outDateNullInd = -1;
  *outTimeNullInd = -1;
  
  EXEC SQL WHENEVER SQLERROR GOTO return_error;
  
  if (*inOutSmall == 0)
  {
    *inOutSmall = 1;
  }
  else
  {
    *inOutSmall = (*inOutSmall / 2);
  }

  if (*inOutInt == 0)
  {
    *inOutInt = 1;
  }
  else
  {
    *inOutInt = (*inOutInt / 2);
  }

  if (*inOutBig == 0)
  {
    *inOutBig = 1;
  }
  else
  {
    *inOutBig = (*inOutBig / 2);
  }

  if (*inOutReal == 0)
  {
    *inOutReal = 1;
  }
  else
  {
    *inOutReal = (*inOutReal / 2);
  }

  if (*inOutDouble == 0)
  {
    *inOutDouble = 1;
  }
  else
  {
    *inOutDouble = (*inOutDouble / 2);
  }
  
  strcpy(sqlStmtInfo, "SELECT midinit,... failed");

  EXEC SQL SELECT midinit, lastname, firstnme
    INTO :hvAllDataTypesChar, :hvAllDataTypesChars, :hvAllDataTypesVarchar
    FROM employee
    WHERE empno = '000180';
    
  strcpy(outChar, hvAllDataTypesChar);
  strcpy(outChars, hvAllDataTypesChars);
  strcpy(outVarchar, hvAllDataTypesVarchar);
  
  *outCharNullInd = 0;
  *outCharsNullInd = 0;
  *outVarcharNullInd = 0;
  
  strcpy(sqlStmtInfo, "VALUES statement failed");

  EXEC SQL VALUES CURRENT DATE INTO :hvAllDataTypesDate;
  strcpy(outDate, hvAllDataTypesDate);
  *outDateNullInd = 0;
  
  strcpy(sqlStmtInfo, "VALUES statement failed");

  EXEC SQL VALUES CURRENT TIME INTO :hvAllDataTypesTime;
  strcpy(outTime, hvAllDataTypesTime);
  *outTimeNullInd = 0;
  
  return (0);

  // set custom diagMsg and sqlstate to return to client 
  return_error:
  {
    // set custom sqlstate to return to client. 
    strcpy(sqludf_sqlstate, sqlca.sqlstate);  
    
    sprintf(sqludf_msgtext, "%d", sqlca.sqlcode);
    strcat(sqludf_msgtext, " : ");
    strcat(sqludf_msgtext, sqlStmtInfo);    
    return (0);
  }
} // AllDataTypes procedure 

//*************************************************************************
//  Stored Procedure: OneResultSetToClient
//
//  Purpose:  Returns a result set to the caller that identifies employees
//            with salaries greater than the value of input parameter
//            inSalary.
//
//            Shows how to:
//             - define IN and OUT parameters in STYLE SQL
//             - define and use NULL indicators for parameters
//             - define the extra parameters associated with
//               PARAMETER STYLE SQL
//             - return a result set to the client
//
//           Note: Extraction of result sets is not supported from 
//                 C/C++ clients
//
//  Parameters:
// 
//   IN:      inSalary - salary
//
//            When the PARAMETER STYLE SQL clause is specified 
//            in the CREATE PROCEDURE statement for the procedure
//            (see the script spcreate.db2), in addition to the 
//            parameters passed at procedure invocation time, the 
//            following parameters are passed to the routine
//            in the following order:
//
//             - one null indicator for each IN/INOUT/OUT parameter
//               is specified in the same order as the corresponding
//               parameter declarations.
//             - sqlstate: to be returned to the caller to indicate 
//               state (output)
//             - routine-name: qualified name of the routine (input)
//             - specific-name: the specific name of the routine (input)
//             - diagnostic-message: an optional text string returned to the
//               caller (output)
//
//            See the actual parameter declarations below to see
//            the recommended datatypes and sizes for them.
//
//            CODE TIP:
//            --------
//            As an alternative to coding the non-functional parameters 
//            required with parameter style SQL (sqlstate, routine-name, 
//            specific-name, diagnostic-message), you can use a macro:
//            SQLUDF_TRAIL_ARGS. This macro is defined in DB2 include
//            file sqludf.h
//   
//            TIP EXAMPLE:
//            ------------
//            The following stored procedure prototype is equivalent 
//            to the actual prototype implementation for this stored 
//            procedure. It is simpler to implement. See stored 
//            procedure sample TwoResultSets in this file to see the
//            SQLUDF_TRAIL_ARGS macro in use.
//
//            SQL_API_RC SQL_API_FN OneResultSetToClient(double *inSalary,
//                                    sqlint16 *inSalaryNullInd,
//                                    SQLUDF_TRAIL_ARGS)
//    
//  Note: With parameter style SQL it is mandatory to declare either the 
//        four non-functional parameters (sqlstate, routine-name, 
//        specific-name, diagnostic-message) or SQLUDF_TRAIL_ARGS macro.
// 
//*************************************************************************
SQL_API_RC SQL_API_FN OneResultSetToClient(double *inSalary,      
                                           sqlint16 *inSalaryNullInd,
                                           char sqlstate[6],  
                                           char qualName[28],
                                           char specName[19],
                                           char diagMsg[71]) 
{
  EXEC SQL INCLUDE SQLCA; 
    
  // SQL host variable declaration section 
  // Each host variable names must be unique within a code
  // file, or the precompiler will raise SQL0307 error 
  EXEC SQL BEGIN DECLARE SECTION;
    double hvOneResultSetSalary;
  EXEC SQL END DECLARE SECTION;

  // text information appended to diagMsg upon the event of an error
  char sqlStmtInfo[60];

  // initialize sqlStmtInfo to NULL
  memset(sqlStmtInfo, '\0', 60);   
 
  EXEC SQL WHENEVER SQLERROR GOTO return_error;

  if ((*inSalaryNullInd) < 0)
  {
    // set custom sqlstate to return to client. 
    strcpy(sqlstate, "38100");

    // set custom message to return to client. Note that although the
    // OUT parameter is declared as CHAR(70), DB2 prepends the
    // procedure name and shared library entry point to the message.
    // Keep the custom message short to avoid truncation. 
    strcpy(diagMsg, "Received null input.");
    
    return (0);
  }
  
  hvOneResultSetSalary = *inSalary;
 
  strcpy(sqlStmtInfo, "DECLARE statement failed");
    
  EXEC SQL DECLARE curOneResultSet CURSOR FOR
             SELECT name, job, CAST(salary AS DOUBLE)
               FROM staff
                 WHERE salary > :hvOneResultSetSalary
                   ORDER BY salary;
  
  strcpy(sqlStmtInfo, "OPEN statement failed");

  // leave cursor open to return result set 
  EXEC SQL OPEN curOneResultSet;

  return (0);

  // set custom diagMsg and sqlstate to return to client 
  return_error:
  {  
    strcpy(sqlstate, sqlca.sqlstate);  
    
    sprintf(diagMsg, "%d", sqlca.sqlcode);
    strcat(diagMsg, ": ");
    strcat(diagMsg, sqlStmtInfo);       
    return (0);
  }
} // OneResultSetToClient function 

//*************************************************************************
//  Stored Procedure: TwoResultSets
//
//  Purpose:  Returns two result sets to the caller. One result set
//            consists of employee data of all employees with salaries
//            greater than inMedianSalary.  The other result set contains
//            employee data for employees with salaries less than
//            inMedianSalary.
//
//            Shows how to:
//              - define IN and OUT parameters in STYLE SQL
//              - define and use NULL indicators for parameters
//              - define the extra parameters associated with
//                PARAMETER STYLE SQL
//              - return more than 1 result set to the client
//
//           Note: Extraction of result sets is not supported from 
//                 C/C++ clients
//
//  Parameters:
// 
//   IN:      inMedianSalary - salary
//
//            When the PARAMETER STYLE SQL clause is specified 
//            in the CREATE PROCEDURE statement for the procedure
//            (see the script spcreate.db2), in addition to the 
//            parameters passed at procedure invocation time, the 
//            following parameters are passed to the routine
//            in the following order:
//
//             - one null indicator for each IN/INOUT/OUT parameter
//               is specified in the same order as the corresponding
//               parameter declarations.
//             - sqlstate: to be returned to the caller to indicate 
//               state (output)
//             - routine-name: qualified name of the routine (input)
//             - specific-name: the specific name of the routine (input)
//             - diagnostic-message: an optional text string returned to the
//               caller (output)
// 
//             CODE TIP:
//             ========
//             See the parameter declarations in stored procedure
//             example OutLanguage for datatypes and sizes for these.
//
//             In this example we use macro SQLUDF_TRAIL_ARGS
//             (defined in sqludf.h - this file must be included)
//             to replace the 4 'extra' parameters:
//               sqlstate, qualified routine name, specific name of
//               routine, diagnostic string.
//
//             When referencing the 'extra' parameters, use the
//             parameter names provided in the macro definition in
//             sqludf.h:
//
//                sqludf_sqlstate
//                sqludf_fname
//                sqludf_fspecname
//                sqludf_msgtext
//
//  Note: With parameter style SQL it is mandatory to declare either the 
//        four non-functional parameters (sqlstate, routine-name, 
//        specific-name, diagnostic-message) or SQLUDF_TRAIL_ARGS macro.
// 
//*************************************************************************
SQL_API_RC SQL_API_FN TwoResultSets(double *inMedianSalary,       
                                    sqlint16 *inMedianSalaryNullInd,
                                    SQLUDF_TRAIL_ARGS)
{
  EXEC SQL INCLUDE SQLCA; 
    
  // SQL host variable declaration section 
  // Each host variable names must be unique within a code
  // file, or the precompiler will raise SQL0307 error 
  EXEC SQL BEGIN DECLARE SECTION;
    double hvTwoResultSetsSalary;
  EXEC SQL END DECLARE SECTION;

  // text information appended to diagMsg upon the event of an error
  char sqlStmtInfo[60];

  // initialize sqlStmtInfo to NULL
  memset(sqlStmtInfo, '\0', 60); 

  EXEC SQL WHENEVER SQLERROR GOTO return_error;

  if (*inMedianSalaryNullInd < 0)
  {
    // set custom sqlstate to return to client. 
    strcpy(sqludf_sqlstate, "38100");

    // set custom message to return to client. Note that although the
    // OUT parameter is declared as CHAR(70), DB2 prepends the
    // procedure name and shared library entry point to the message.
    // Keep the custom message short to avoid truncation. 
    strcpy(sqludf_msgtext, "Received null input.");
    
    return (0);
  }

  hvTwoResultSetsSalary = *inMedianSalary;

  strcpy(sqlStmtInfo, "DECLARE curTwoResultSets1 statement failed");
 
  EXEC SQL DECLARE curTwoResultSets1 CURSOR FOR
             SELECT name, job, CAST(salary AS DOUBLE)
               FROM staff
                 WHERE salary > :hvTwoResultSetsSalary ORDER BY salary;

  strcpy(sqlStmtInfo, "DECLARE curTwoResultSets2 statement failed");

  EXEC SQL DECLARE curTwoResultSets2 CURSOR FOR
             SELECT name, job, CAST(salary AS DOUBLE)
               FROM staff WHERE salary < :hvTwoResultSetsSalary
                 ORDER BY salary DESC;

  strcpy(sqlStmtInfo, "OPEN statement failed");

  EXEC SQL OPEN curTwoResultSets1;

  strcpy(sqlStmtInfo, "OPEN statement failed");

  EXEC SQL OPEN curTwoResultSets2;

  // leave both cursors open to return result sets 
  return (0);

  // set custom diagMsg and sqlstate to return to client 
  return_error:
  {        
    // set custom sqlstate to return to client. 
    strcpy(sqludf_sqlstate, sqlca.sqlstate);  
    
    sprintf(sqludf_msgtext, "%d", sqlca.sqlcode);
    strcat(sqludf_msgtext, " : ");
    strcat(sqludf_msgtext, sqlStmtInfo);     
    return (0);
  }
} // TwoResultSets function 

//*************************************************************************
//  Stored Procedure: GeneralExample
//
//  Purpose:  Return a result set to the caller that identifies those
//            employees with an education level equal to the value of
//            input parameter inEdLevel.
//
//            Shows how to:
//             - define IN and OUT parameters in STYLE GENERAL
//             - execute SQL to declare and work with a cursor 
//             - return a result set to the client
//
//            Note: Extraction of result sets is not supported from 
//                  C/C++ clients
//
//  Parameters:
// 
//   IN:      inEdLevel - education level of the employee
//   OUT:     outReturnCode - sqlcode of error (if one is raised)
//            outErrorMsg - text information returned to the client to
//            locate the error, if any
//
//            When PARAMETER STYLE GENERAL clause is specified in the 
//            CREATE PROCEDURE statement for the procedure 
//            (see the script spcreate.db2), only the parameters passed
//            during invocation are passed to the routine. With 
//            PARAMETER STYLE GENERAL, there is no concept of null. You  
//            cannot assess the nullability of variable, nor can you set  
//            a value to an SQL equivalent to NULL.
//            
//*************************************************************************
SQL_API_RC SQL_API_FN GeneralExample(sqlint32 *inEdLevel,
                                     sqlint32 *outReturnCode,
                                     char outErrorMsg[33]) // CHAR(32) 
{                                     
  EXEC SQL INCLUDE SQLCA; 
  
  // SQL host variable declaration section 
  // Each host variable names must be unique within a code
  // file, or the precompiler will raise SQL0307 error 
  EXEC SQL BEGIN DECLARE SECTION;
    sqlint32 hvGeneralEduLevel;
  EXEC SQL END DECLARE SECTION;

  // initialize output parameters 
  *outReturnCode = 0;
  memset(outErrorMsg, '\0', 33);
  
  EXEC SQL WHENEVER SQLERROR GOTO return_error; 
    
  if ( (*inEdLevel > 25) || (*inEdLevel < 0))
  {
    // set the output error code and message
    *outReturnCode = -1;
    strcpy(outErrorMsg, "Received invalid input");
    
    return (0);   
  }   

  hvGeneralEduLevel = *inEdLevel;  
  
  strcpy(outErrorMsg, "DECLARE curGeneral failed");
  
  EXEC SQL DECLARE curGeneral CURSOR FOR
             SELECT firstnme, lastname, workdept
               FROM employee
               WHERE edlevel = :hvGeneralEduLevel
               ORDER BY workdept;
  
  // leave cursor open to return result set 
  strcpy(outErrorMsg, "OPEN curGeneral failed");
  EXEC SQL OPEN curGeneral;

  return (0);
  
  // set error code information to return to client 
  return_error:
  {
    *outReturnCode = sqlca.sqlcode;
    return (0);
  }
} // GeneralExample function 

//*************************************************************************
//  Stored Procedure: GeneralWithNullsExample
//
//  Purpose:  Returns a result set to the caller that contains sales 
//            information for a particular business quarter, as specified 
//            by input parameter inQuarter.
//
//            Shows how to:
//             - define IN and OUT parameters in STYLE GENERAL WITH NULLS
//             - define and use NULL indicators for parameters
//             - execute SQL to declare and use a cursor 
//             - return a result set to the client
//
//            Note: Extraction of result sets is not supported from 
//                  C/C++ clients
//
//  Parameters:
// 
//   IN:      inQuarter - the business quarter for which sales information 
//            is returned
//   OUT:     outReturnCode - sqlcode of error (if one is raised)
//            outErrorMsg - text information returned to the client to
//            locate the error, if any
//
//            When PARAMETER STYLE GENERAL WITH NULLS is defined
//            for the routine (see routine registration script 
//            spcreate.db2), in addition to the parameters passed during
//            invocation, a vector containing a null indicator for each
//            parameter in the CALL statement is passed to the routine.
// 
//*************************************************************************
SQL_API_RC SQL_API_FN GeneralWithNullsExample(sqlint32 *inQuarter,
                                              sqlint32 *outReturnCode,
                                              char outErrorMsg[33], // CHAR(32)
                                              sqlint16 nullInds[3])  
{                                   
  EXEC SQL INCLUDE SQLCA; 
     
  // SQL host variable declaration section 
  // Each host variable names must be unique within a code
  // file, or the precompiler will raise SQL0307 error 
  EXEC SQL BEGIN DECLARE SECTION;
    sqlint32 hvGenWithNullsQuarter;
  EXEC SQL END DECLARE SECTION;
  
  EXEC SQL WHENEVER SQLERROR GOTO return_error;
  
  // Note: nullInds[0] corresponds to inQuarter
  //       nullInds[1] corresponds to outReturnCode
  //       nullInds[2] corresponds to outErrorMsg 
  
  // initialize output parameters and their corresponding null indicators
  *outReturnCode = 0;
  memset(outErrorMsg, '\0', 33);
  nullInds[1] = 0;
  nullInds[2] = -1;
      
  if (nullInds[0] < 0)
  {
    // set the output error code and message
    *outReturnCode = -1;
    strcpy(outErrorMsg, "Received null input");
    
    // received null inputs, so set the output null indicators accordingly    
    nullInds[1] = 0;
    nullInds[2] = 0;   
    
    return (0);
  }
  
  if ((*inQuarter) < 1 || (*inQuarter) > 4)
  {
    // set the output error code and message
    *outReturnCode = -1;
    strcpy(outErrorMsg, "Received invalid input");
    
    // set the output null indicators to indicate that the output 
    // parameters are not null
    nullInds[1] = 0;
    nullInds[2] = 0;
    
    return (0);
  }  
       
  hvGenWithNullsQuarter = *inQuarter;
    
  strcpy(outErrorMsg, "DECLARE curGenWithNulls failed");
    
  EXEC SQL DECLARE curGenWithNulls CURSOR FOR
             SELECT sales_person, region, sales
               FROM sales
               WHERE quarter(sales_date) = :hvGenWithNullsQuarter;
                       
  // leave cursor open to return result set 
  strcpy(outErrorMsg, "OPEN curGenWithNulls failed");                        
  EXEC SQL OPEN curGenWithNulls;
      
  return (0);

  // set error code information to return to client 
  return_error:
  {
    *outReturnCode = sqlca.sqlcode;
    nullInds[1] = 0;
    nullInds[2] = 0;    
    return (0);
  }
} // GeneralWithNullsExample function