/*******************************************************************************
**                                                                        
** Source File Name = spcall.c                                       
**                                                                        
** Licensed Materials - Property of IBM                                   
**                                                                        
** (C) COPYRIGHT International Business Machines Corp. 2000
** All Rights Reserved.                                                   
**                                                                        
** US Government Users Restricted Rights - Use, duplication or            
** disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
**
**   PURPOSE:
**     Call stored procedures (eg. spcall <procname> <args> 
**                              or spcall <schema>.<procname> <args>)
**
*******************************************************************************/
   
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sqlcli1.h>
#include "utilcli.h"

#define MAX_NUM_PARAMS 25
#define MAX_BUF_LEN 1024

#define MAIN_PANIC \
        sqlrc = SQLEndTran( SQL_HANDLE_ENV, henv, SQL_ROLLBACK ); \
        HANDLE_CHECK( SQL_HANDLE_ENV, henv, sqlrc, &henv, &hdbc ) ;

int main( int argc, char * argv[] )
{
    int rc = 0;
    SQLHANDLE henv, hdbc, hstmt, hstmt1;
    SQLRETURN sqlrc ;

    SQLCHAR sqlstmt[1024];
    char *procname;
    char **procargs;
    short input_args = 0;

    int separator = 0;
    char* temp_str = 0;
    /*
      Variables for database name, user id and password.
    */
    SQLCHAR db[SQL_MAX_DSN_LENGTH + 1] ;
    SQLCHAR uid[MAX_UID_LENGTH + 1] ;
    SQLCHAR pwd[MAX_PWD_LENGTH + 1] ;

    SQLCHAR colNamePattern[] = "%";  
    char colSchemaNamePattern[] = "%";  
    struct
    {   SQLINTEGER ind ;
        SQLCHAR    val[129] ;
    } colName, schemaName;

    struct 
    {   SQLINTEGER ind ;
        SQLINTEGER val; 
    } colLength, colOrdinal;
    
    struct 
    {   SQLINTEGER ind ;
        SQLSMALLINT val; 
    } colScale, colType, colDataType;

    char tempSchema[129] = "\0";

    SQLUSMALLINT      ParameterNumber;  /* ipar */
    SQLSMALLINT       InputOutputType[MAX_NUM_PARAMS];  /* fParamType of each param */
    SQLSMALLINT       ValueType = SQL_C_CHAR; /* fCType; always pass strings to CLI */
    SQLSMALLINT       ParameterType[MAX_NUM_PARAMS];    /* fSqlType of each param */
    SQLUINTEGER       ColumnSize;       /* cbColDef */
    SQLSMALLINT       DecimalDigits;    /* ibScale */
    SQLPOINTER        ParameterValuePtr;/* rgbValue */
    SQLINTEGER        BufferLength = MAX_BUF_LEN;  /* cbValueMax */
    SQLINTEGER        StrLen_or_IndPtr[MAX_NUM_PARAMS];/* pcbValue */
  
    SQLCHAR           ParameterName[MAX_NUM_PARAMS][129];    /* fSqlType of each param */

    sqlint32    longval;
    SQLCHAR     bufs[MAX_NUM_PARAMS][MAX_BUF_LEN]; /* buffers for parameters */
    short       i, has_out_parms = 0, invalid_cmd_line = 0, flen;
    int         nparams = argc - 2; /* # proc parameters doesn't include 
                                       'spcall' and proc name */
    char *field;

    db[0]  = '\0';
    uid[0] = '\0';
    pwd[0] = '\0';

    /*************************************************************/
    /* Parse command-line parameters                             */
    /*************************************************************/

    if (argc < 2)
    {
      invalid_cmd_line = 1;
    }
    else
    {
      i = 1;
      while(*argv[i] == '-')
      {
        if (argc < i + 2)
        {
          invalid_cmd_line = 1;
          break;
        } /* if */
        switch(*(argv[i]+1))
        {
          case 'd':
            flen = SQL_MAX_DSN_LENGTH;
            field = (char *)db;
            break;
          case 'u':
            flen = MAX_UID_LENGTH;
            field = (char *)uid;
            break;
          case 'p':
            flen = MAX_PWD_LENGTH;
            field = (char *)pwd;
            break;
          default:
            invalid_cmd_line = 1;
            break;
        } /* switch */
        strncpy(field, (const char *)argv[i+1], flen);
        field[flen] = 0;
        i += 2;
        nparams -= 2;
      } /* while */
    } /* if */

    if (invalid_cmd_line)
    {
      printf ("\nUSAGE: spcall [-d <db name>] [-u <user name>] [-p <password>] procname [input-arg1 input-arg2 ...]\n\n");
      return 1;
    } /* if */

    /* Remember the procedure to call, and its parameters. */
    procname = argv[i];
    procargs = &argv[i+1];

    /* upper case the proc name */
    for(i = 0; i < strlen(procname); i++)
    {
      if(isalpha(procname[i]))
      procname[i] = toupper(procname[i]);
    } /* for */
    
    /* separate the schema name if specified */
    if ((temp_str = strstr(procname, ".")) != NULL)
    {
      separator = strlen(procname) - strlen(temp_str);
      strncpy(colSchemaNamePattern, procname, separator);
      procname = temp_str+1;
    }

    /*************************************************************/
    /* Setup CLI environment                                     */
    /*************************************************************/

    /* allocate an environment handle */
    sqlrc = SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv ) ;
    if ( sqlrc != SQL_SUCCESS ) 
    {   printf( "\n--ERROR while allocating the environment handle.\n" ) ;
        printf( "  sqlrc             = %d\n", sqlrc);
        printf( "  line              = %d\n", __LINE__);
        printf( "  file              = %s\n", __FILE__);	    
        return( 1 ) ;
    } /* if */
    /* enable sending column names over the network */
    sqlrc = SQLSetEnvAttr( henv,
                        SQL_ATTR_USE_LIGHT_OUTPUT_SQLDA,
                        ( SQLPOINTER ) SQL_FALSE,
                        SQL_FALSE
                      ) ;
    HANDLE_CHECK( SQL_HANDLE_ENV, henv, sqlrc, &henv, &hdbc ) ;

    /*************************************************************/
    /* Connect to the Database at which the SP will execute      */
    /*************************************************************/
    
    /* allocate a database connection handle */
    sqlrc = SQLAllocHandle( SQL_HANDLE_DBC, henv, &hdbc ) ;
    HANDLE_CHECK( SQL_HANDLE_ENV, henv, sqlrc, &henv, &hdbc ) ;

    /* set AUTOCOMMIT off */
    sqlrc = SQLSetConnectAttr( hdbc,
                               SQL_ATTR_AUTOCOMMIT,
                               (SQLPOINTER)SQL_AUTOCOMMIT_OFF, SQL_NTS) ;
    HANDLE_CHECK( SQL_HANDLE_DBC, hdbc, sqlrc, &henv, &hdbc ) ;   

    if(db[0] == '\0')
    {
      strcpy((char *)db, "sample");
    } /* if */

    /* connect to the database */
    printf( "\nConnecting to %s ...\n", db ) ;    
    sqlrc = SQLConnect( hdbc,
                     (SQLCHAR *)db,  SQL_NTS,
                     (SQLCHAR *)uid, SQL_NTS,
                     (SQLCHAR *)pwd, SQL_NTS
                   ) ;
    HANDLE_CHECK( SQL_HANDLE_DBC, hdbc, sqlrc, &henv, &hdbc ) ;     
    printf( "Connected to %s.\n", db ) ;

    /*************************************************************/
    /* Obtain the SP's parameters                                */
    /*************************************************************/

    sqlrc = SQLAllocHandle( SQL_HANDLE_STMT, hdbc, &hstmt ) ;
    HANDLE_CHECK( SQL_HANDLE_STMT, hstmt, sqlrc, &henv, &hdbc ) ;

    sqlrc = SQLProcedureColumns( hstmt, 
                                 NULL, 0,    /* catalog name not used */
                                 (unsigned char *)colSchemaNamePattern, SQL_NTS,    /* schema name not currently used */
                                 (unsigned char *)procname, SQL_NTS,
                                 colNamePattern, SQL_NTS /* all columns */
                               );
    HANDLE_CHECK( SQL_HANDLE_STMT, hstmt, sqlrc, &henv, &hdbc ) ;

    /* Bind the result col for schema name. */
    sqlrc = SQLBindCol( hstmt, 2, SQL_C_CHAR, schemaName.val, 129, &schemaName.ind);
    HANDLE_CHECK( SQL_HANDLE_STMT, hstmt, sqlrc, &henv, &hdbc ) ;

    /* Bind the result col for column name. */
    sqlrc = SQLBindCol( hstmt, 4, SQL_C_CHAR, colName.val, 129, &colName.ind );
    HANDLE_CHECK( SQL_HANDLE_STMT, hstmt, sqlrc, &henv, &hdbc ) ;

    /* Bind the result col for column type (IN, OUT, INOUT). */
    sqlrc = SQLBindCol( hstmt, 5, SQL_C_SHORT, 
                        (SQLPOINTER)&colType.val, sizeof(colType.val), &colType.ind );
    HANDLE_CHECK( SQL_HANDLE_STMT, hstmt, sqlrc, &henv, &hdbc ) ;

    /* Bind the result col for data type (SQL data type). */
    sqlrc = SQLBindCol( hstmt, 6, SQL_C_SHORT, (SQLPOINTER)&colDataType.val, 
                        sizeof(colDataType.val), &colDataType.ind );
    HANDLE_CHECK( SQL_HANDLE_STMT, hstmt, sqlrc, &henv, &hdbc ) ;

    /* Bind the result col for column size. */
    sqlrc = SQLBindCol( hstmt, 8, SQL_C_LONG, (SQLPOINTER)&colLength.val,
                        sizeof(colLength.val), &colLength.ind ) ;
    HANDLE_CHECK( SQL_HANDLE_STMT, hstmt, sqlrc, &henv, &hdbc ) ;

    /* Bind the result col for the scale of the parameter. */
    sqlrc = SQLBindCol( hstmt, 10, SQL_C_SHORT, (SQLPOINTER)&colScale.val,
                        sizeof(colScale.val), &colScale.ind ) ;
    HANDLE_CHECK( SQL_HANDLE_STMT, hstmt, sqlrc, &henv, &hdbc ) ;

    /* Bind the result col for parameter ordinal. */
    sqlrc = SQLBindCol( hstmt, 18, SQL_C_LONG, (SQLPOINTER)&colOrdinal.val,
                        sizeof(colOrdinal.val), &colOrdinal.ind ) ;
    HANDLE_CHECK( SQL_HANDLE_STMT, hstmt, sqlrc, &henv, &hdbc ) ;

    /*************************************************************/
    /* Fetch each parameter for the SP, and:                     */
    /*  o  build the CALL statement                              */
    /*  o  configure input/output parameters for SP call         */
    /*************************************************************/

    sqlrc = SQLAllocHandle( SQL_HANDLE_STMT, hdbc, &hstmt1 );
    HANDLE_CHECK( SQL_HANDLE_STMT, hstmt1, sqlrc, &henv, &hdbc );
    strcpy((char *)sqlstmt, "CALL ");
    strcat((char *)sqlstmt, (const char *)procname);
    strcat((char *)sqlstmt, " (");

    ParameterNumber = 0;
    input_args = 0;
    sqlrc = SQLFetch( hstmt );
    HANDLE_CHECK( SQL_HANDLE_STMT, hstmt, sqlrc, &henv, &hdbc ) ;
    while ( sqlrc != SQL_NO_DATA_FOUND )
    {
      if (ParameterNumber >= MAX_NUM_PARAMS)
      {
        printf ("\nERROR: cannot handle more than %d parameters ...\n\n", MAX_NUM_PARAMS);
        MAIN_PANIC;
      } /* if */
      
      /**************************************************/
      /* Compose CALL SP statement, parm by parm        */
      /**************************************************/
      strcat((char *)sqlstmt, " ?,");

      /**************************************************/
      if (strlen(tempSchema) > 0) {
         if (strcmp((char*)schemaName.val, tempSchema)) {
            printf ("\nERROR: Procedure exists in multiple schema ...\n\n");
            MAIN_PANIC;
         }
      }
      else {
         strncpy(tempSchema, (const char*)schemaName.val, schemaName.ind);
      }

      /**************************************************/
      /* Remember Column name for printing SP output    */
      /**************************************************/
      strncpy((char *) ParameterName[ParameterNumber], 
              (const char *)colName.val, colName.ind);
      ParameterName[ParameterNumber][colName.ind] = '\0';

      /**************************************************/
      /* Obtain parameter type, and data if input.      */
      /**************************************************/

      InputOutputType[ParameterNumber] = colType.val;
      ParameterValuePtr = (SQLPOINTER) bufs[ParameterNumber];

      /* Input parameters of the SP must have
         corresponding cmd-line parameters. */
      if (( colType.val == SQL_PARAM_INPUT_OUTPUT ) ||
          ( colType.val == SQL_PARAM_INPUT ))
      {
        /* Do we have a cmd-line parm for this SP parm? */
        if ( input_args >= nparams )
        {
          printf ("\nERROR: Too few parameters in the command line ...\n\n");
          MAIN_PANIC;
        }
        else /* map cmd-line input parm to SP input parm */
        {
          strcpy((char *)bufs[ParameterNumber], procargs[input_args++]);
        } /* if */

        /* Also, if the 'NULL' keyword is supplied on the
           cmd-line, this means a NULLable input parameter. */
        if ( strcmp((const char *)bufs[ParameterNumber], "NULL") == 0 )
        {
          StrLen_or_IndPtr[ParameterNumber] = SQL_NULL_DATA;
        }
        else /* null-terminated string */
        {
          StrLen_or_IndPtr[ParameterNumber] = SQL_NTS;
        } /* if */
      } /* if */
      
      /* Remember that there was some output
         to process later on. */
      if (( colType.val == SQL_PARAM_INPUT_OUTPUT ) ||
          ( colType.val == SQL_PARAM_OUTPUT ))
      {
        has_out_parms = 1;
      } /* if */

      /* SQL data type of the parameter. */
      ParameterType[ParameterNumber] = colDataType.val;

      /**************************************************/
      /* Parameter output sizes                         */
      /**************************************************/

      /* ColumnSize is only relevant for CHARACTER, DECIMAL and NUMERIC */
      ColumnSize = colLength.val; /* maximum length / maximum decimal precision */
      /* DecimalDigits is only relevant for DECIMAL and NUMERIC */
      DecimalDigits = colScale.val; /* scale */

      /**************************************************/
      /* Bind the SP parameter                          */
      /**************************************************/

      sqlrc = SQLBindParameter( hstmt1,
                                ParameterNumber + 1, /* NOTE that here the 1st is 1 */
                                InputOutputType[ParameterNumber],
                                ValueType,
                                ParameterType[ParameterNumber],
                                ColumnSize,
                                DecimalDigits,
                                ParameterValuePtr,
                                BufferLength,
                                &(StrLen_or_IndPtr[ParameterNumber])
                              );
      HANDLE_CHECK( SQL_HANDLE_STMT, hstmt1, sqlrc, &henv, &hdbc ) ;

      /**************************************************/
      /* Get the next SP parameter                      */
      /**************************************************/

      ParameterNumber++;
      sqlrc = SQLFetch( hstmt );
      HANDLE_CHECK( SQL_HANDLE_STMT, hstmt, sqlrc, &henv, &hdbc ) ;
    } /* while */

    /*************************************************************/
    /* Final processing on SP's parameters                       */
    /*************************************************************/

    /* Finished getting SP's parameters, so must 
       free the associated stmt handle. */
    sqlrc = SQLFreeHandle( SQL_HANDLE_STMT, hstmt ) ;
    HANDLE_CHECK( SQL_HANDLE_STMT, hstmt, sqlrc, &henv, &hdbc ) ;

    /* If there's a mismatch between cmd-line parameters
       supplies as SP input parameters, and the actual
       number of input parameters required by SP, then
       signal the error. */
    if (input_args != nparams) 
    { /* too many args in the command line */
      printf ("\nWARNING: Wrong number of input parameters (expected %d)...\n\n", input_args);
    }

    /*************************************************************/
    /* Invoke the SP                                             */
    /*************************************************************/

    /* Generate the closing bracket (overwrite the last ',') */
    sqlstmt[strlen((const char *)sqlstmt) - 1] = ')';

    /* Prepare the call statement */
    sqlrc = SQLPrepare(hstmt1, sqlstmt, SQL_NTS);
    HANDLE_CHECK( SQL_HANDLE_STMT, hstmt1, sqlrc, &henv, &hdbc ) ;

    /* Execute the call */
    sqlrc = SQLExecute( hstmt1 ) ;
    HANDLE_CHECK( SQL_HANDLE_STMT, hstmt1, sqlrc, &henv, &hdbc ) ;

    /*************************************************************/
    /* Print SP's output parameters, if any.                     */
    /*************************************************************/

    if( has_out_parms )
    {
      /* printf ("\n----- OUTPUT PARAMETERS -----"); */
      for(i = 0; i < ParameterNumber; i++)
      {
        if(InputOutputType[i] != SQL_PARAM_INPUT)
        {
          printf("\n%s: ", ParameterName[i]);
          switch(StrLen_or_IndPtr[i])
          {
            case SQL_NULL_DATA:
            {
              printf ("NULL");
              break;
            }
            case SQL_NO_TOTAL:
            {
              printf ("UNKNOWN LENGTH");
              break;
            }
            default:
            {
              switch(ParameterType[i])
              {
                case SQL_CHAR:
                case SQL_VARCHAR:
                case SQL_LONGVARCHAR:
                case SQL_INTEGER:
                case SQL_SMALLINT:
                case SQL_BIGINT:
                case SQL_FLOAT:
                case SQL_DOUBLE:
                case SQL_REAL:
                case SQL_TYPE_DATE:
                case SQL_TYPE_TIME:
                case SQL_TYPE_TIMESTAMP:
                case SQL_DECIMAL:
                case SQL_NUMERIC:
                  printf ("%*.*s", StrLen_or_IndPtr[i], StrLen_or_IndPtr[i], bufs[i]);
                  break;
                default:
                  printf( "\nERROR: Unknown type code %d for OUTPUT paramenter %d. Rolling back ...\n",
                          ParameterType[i], i );
                  MAIN_PANIC;
              } /* switch */
              break;
            } /* default */
          } /* switch */
        } /* if */
      }/* for */
      printf ("\n\n");
    } /* if */

    /*************************************************************/
    /* Print SP's result sets, if any.                           */
    /*************************************************************/

    do    
    {   
      rc = StmtResultPrint( hstmt1);
    }
    while( SQLMoreResults( hstmt1) == SQL_SUCCESS);   

    /*************************************************************/
    /* Cleanup                                                   */
    /*************************************************************/

    /* Commit the transaction */
    sqlrc = SQLEndTran( SQL_HANDLE_DBC, hdbc, SQL_COMMIT ) ;
    HANDLE_CHECK( SQL_HANDLE_DBC, hdbc, sqlrc, &henv, &hdbc ) ;     
    
    /* Finished calling SP, so uncallocate handle. */
    sqlrc = SQLFreeHandle( SQL_HANDLE_STMT, hstmt1 ) ;
    HANDLE_CHECK( SQL_HANDLE_STMT, hstmt1, sqlrc, &henv, &hdbc ) ;

    /* Disconnect from the db */
    sqlrc = SQLDisconnect( hdbc ) ;
    HANDLE_CHECK( SQL_HANDLE_DBC, hdbc, sqlrc, &henv, &hdbc ) ;   

    /* free connection handle */
    sqlrc = SQLFreeHandle( SQL_HANDLE_DBC, hdbc ) ;
    HANDLE_CHECK( SQL_HANDLE_DBC, hdbc, sqlrc, &henv, &hdbc ) ;   

    /* free env handle */
    sqlrc = SQLFreeHandle( SQL_HANDLE_ENV,  henv ) ;
    HANDLE_CHECK( SQL_HANDLE_ENV, henv, sqlrc, &henv, &hdbc ) ;

    return 0;
} /* end main */