/**********************************************************************
* FILENAME :    CallSPUtil.c
*
* DESCRIPTION :
*               Utility functions for Stored Procedure exmples:
*               	DropProcedure () 	- Drop strored procedure
*               	CreateProcedure ()	- Create stored proecdure
*               	ListProcedure()		- Lookup and list stored procudure
*
*               Procedure referenced are:
*
*                   {? = CALL Insert_Record (?, ?, ?, ?, ?, ?)}
*                   {CALL SelectRecords()}
*                   {CALL Find_Record(?)}
*                   {CALL Multi_Result_Sets()}
*                   {? = CALL InOutRet_Params (?, ?)}
*                   {? = CALL Find_ByID (?, ?)}
*                   {? = CALL Find_ByName (?, ?)}
*                   {? = CALL Edit_Record (?, ?, ?, ?, ?, ?)}
*
*               See below for individual details.
*
* ODBC USAGE :
*           	DropProcedure()   - Calls SQLExecDirect() with statement that
*           			    checks procedure exist before dropping it.
*           	CreateProcedure() - Calls SQLExecDirect() to create a stored
*           			    procedure.
*           			    Insert_Record for use in these tests.
*           	List Procedure()  - Used to show one of the above procedures
*           			    is defined by lookup using SQLProcedures()
*           			    and SQLBindCol() for the columns returned.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sql.h>
#include <sqlext.h>
#include <string.h>

#define BUFF_SIZE 255
#define FIRSTNAME_LEN 256
#define LASTNAME_LEN 256
#define ADDRESS_LEN 256
#define CITY_LEN 256

//
// Create Stored procedure
// Procedure is called either Insert_Record or Select_Records
//
SQLRETURN CreateProcedure (SQLHDBC hdbc, char *ProcName) {

    SQLHSTMT hstmt = SQL_NULL_HSTMT;  	// Statement handle
    SQLRETURN retcode = SQL_SUCCESS;	// Return status

	SQLCHAR strCreateSP [1024];

	if (strcmp(ProcName, "Insert_Record") == 0) {
		// Stored Procedure: Insert_Record
		//
		// Called by: CallSPINsertRec.c
		//
		// Usage {? = CALL Insert_Record (?, ?, ?, ?, ?, ?)}
		//
		// Has 7 Parameters - 1   - Proc return value (Prov RETURN value)
		//		      2-5 - Table columns (INPUT)
		//                    6   - Matching records count (OUTPUT)
		//		      7   - Total records Count (OUTPUT)
		//
		// Creates Stored Proc 'Insert_Record'
		// Inserts record into TestTBL1 using params 2-5
		// Generates result set of entire table
		// Generates result set of records matching FirstName just inserted
		// Returns number of records in table
		// Returns number of records matching FirstName just inserted
		// Function returns 100
		//
		strcpy(strCreateSP, "CREATE PROCEDURE Insert_Record ("
		    "@pFirstName nvarchar(256), @pLastName varchar(256), "
		    "@pAddress varchar(256), @pCity varchar(256), "
		    "@MatchingRecs int OUTPUT, @TotalRecs int OUTPUT) AS "
		    "INSERT INTO TestTBL1 (FirstName, LastName, Address, City) "
		           "VALUES (@pFirstName, @pLastName, @pAddress, @pCity); "
		    "SELECT * FROM TestTBL1 ORDER BY PersonID; "
		    "SELECT * FROM TestTBL1 WHERE FirstName=@pFirstName; "
		    "SELECT @MatchingRecs=count(*) FROM TestTBL1 WHERE "
					"FirstName=@pFirstName; "
		    "SELECT @TotalRecs=count(*) FROM TestTBL1; "
		    "RETURN 100;");

	} else if (strcmp(ProcName, "Select_Records") == 0) {
		// Stored Procedure : 'Select_Records' (no param example)
		// Called By : CallSP.c
		//
		// Usage {CALL SelectRecords()}
		//
		// Has no parameters
		//
		// Creates Stored Procedure 'Select_Records'
		// Generates result set of entire table
		//
		strcpy (strCreateSP, "CREATE PROCEDURE Select_Records AS "
			"SELECT PersonID, FirstName, LastName, Address, City "
			"FROM TestTBL1");

	} else if (strcmp(ProcName, "Find_Record") == 0) {
		// Stored Procedure : 'Find_Record'
		// Called By : CallSPWithParam.c
		//
		// Usage {CALL Find_Record(?)}
		//
		// Has 1 Input Parameter - pPersonID (INPUT)
		//
		// Creates stored procedure 'Find_Record'
		// Generates result set of records matching pPersonID
		//
		strcpy (strCreateSP, "CREATE PROCEDURE Find_Record (@pPersonID INT) "
			"AS SELECT PersonID, FirstName, LastName, Address, City "
			"FROM TestTBL1 WHERE PersonID=@pPersonID");

	} else if (strcmp(ProcName, "Multi_Result_Sets") == 0) {
			// Stored Procedure : 'Multi_Result_Sets'
			// Called By : CallSPMultiResultSet.c
			//
			// Usage {CALL Multi_Result_Sets()}
			//
			// Has no parameters
			//
			// Creates stored procedure Multi_Result_Sets
			// Generates results set for all records in TestTBL1
			// Generates results set for all records in TestTBLCopy
			//
			strcpy (strCreateSP, "CREATE PROCEDURE Multi_Result_Sets AS "
				"SELECT PersonID, FirstName, LastName, Address, City "
				"FROM TestTBL1; "
				"SELECT PersonID, FirstName, LastName, Address, City "
				"FROM TestTBL1Copy");

	} else if (strcmp(ProcName, "InOutRet_Params") == 0) {
		// Stored Procedure : 'InOutRet_Params'
		// Called By : CallSPWithInOutParams.c
		//
		// Usage {? = CALL InOutRet_Params (?, ?)}
		//
		//  Has 3 Parameters - 1 - Proc return value (Proc RETURN value)
		//		       2 - table field PersonID (INPUT)
		//                     3 - matching record count (OUTPUT)
		//
		// Creates stored procedure 'InOutRet_Params'
		// Generates result set for records matching the input parameter
		// Returns number of records in result set
		// Function returns 100
		//
		strcpy(strCreateSP, "CREATE PROCEDURE InOutRet_Params ("
		    "@pPersonID int, @MatchingRecs int OUTPUT) AS "
			"SELECT PersonID, FirstName, LastName, Address, City "
			"FROM TestTBL1 WHERE PersonID=@pPersonID; "
		    "SELECT @MatchingRecs=count(*) FROM TestTBL1 WHERE "
					"PersonID=@pPersonID; "
		    "RETURN 100;");

	} else if (strcmp(ProcName, "Find_ByID") == 0) {
		// Stored Procedure : Find_ByID
		// Called By : CallSPFindID.c
		//
		// Usage {? = CALL Find_ByID (?, ?)}
		//
		// Uses 3 Parameters - 1 - return value
		//		       2 - pPersonIDs parameter array element (INPUT)
		//		       3 - pPersonIXs parameter array element (INPUT)
		//
		// Creates stored procedure Find_ByID
		// Generates a result set that matches the pPersonID parameter
		// Returns the pPersonIX. This can be used to tie the result set
		// to the pPersonID that generated it
		strcpy (strCreateSP, "CREATE PROCEDURE Find_ByID "
			"(@pPersonID int, @pPersonIX int) AS SELECT PersonID, FirstName, "
			"LastName, Address, City FROM TestTBL1 WHERE PersonID=@pPersonID; "
			"RETURN @pPersonIX");

	} else if (strcmp(ProcName, "Find_ByName") == 0) {
		// Stored Procedure : Find_ByName
		// Called By : CalLSPFindName.c
		//
		// Usage {? = CALL Find_ByName (?, ?)}
		//
		// Uses 3 Parameters - 1 - return value
		//		       2 - pFirstName parameter array element (INPUT)
		//		       3 - pPersonIXs parameter array element (INPUT)
		//
		// Creates stored procedure Find_ByName
		// Generates a result set that matches the pFirstName parameter
		// Returns the pPersonIX. This can be used to tie the result set
		// to the pFirstName that generated it
		strcpy (strCreateSP, "CREATE PROCEDURE Find_ByName ("
			"@pFirstName varchar(2), @pPersonIX int) "
			"AS SELECT PersonID, FirstName, LastName, Address, City "
			"FROM TestTBL1 WHERE FirstName LIKE @pFirstName+'%'; "
			"RETURN @pPersonIX");

	} else if (strcmp(ProcName, "Edit_Record") == 0) {
		// Stored Procedure : Edit_Record
		// Called By : CallSPEditRecord.c
		//
		// Usage {? = CALL Edit_Record (?, ?, ?, ?, ?, ?)}
		//
	        // Uses 7 Parameters - 1   - Proc return value   - RETURNED
	        //                     2   - Action - See below  - INPUT
	        //                     3   - RecordID            - INPUT
	        //                     4-7 - if action is SELECT
	        //                         - old column values   - OUTPUT
	        //                     4-7 - if action is UPDATE or INSERT
	        //                         - new column values   - INPUT
	        //		       4-7 - if action is DELETE
	        //			   - unused, delete is via RecordID
	        //
		// Action values are SELECT RECORD 0
		// 					 UPDATE 	   1
		//					 INSERT        2
		//					 DELETE        3
		//					 SELECT ALL    4
		//

		strcpy (strCreateSP, "CREATE PROCEDURE Edit_Record ("
			"@action int, @pPersonID int OUTPUT, "
			"@pFirstName nvarchar(256), @pLastName nvarchar(256), "
			"@pAddress nvarchar(256), @pCity nvarchar(256)) "
			"AS "
			"IF (@action=0) BEGIN "
					"SELECT PersonID, FirstName, LastName, Address, City "
					"FROM TestTBL1 WHERE PersonID=@pPersonID; "
				"END "
			"ELSE "
				"IF (@action=1) BEGIN "
					"UPDATE TestTBL1 "
					"SET FirstName=@pFirstName, LastName=@pLastName, "
					"Address=@pAddress, City=@pCity "
					"WHERE PersonID=@pPersonID; "
				"END "
			"ELSE "
				"IF (@action=2) BEGIN "
					"INSERT INTO TestTBL1 "
					"(FirstName, LastName, Address, City) "
					"VALUES (@pFirstName, @pLastName, @pAddress, @pCity);"
					"SET @pPersonID=@@IDENTITY; "
				"END "
			"ELSE "
				"IF (@action=3) BEGIN "
					"DELETE FROM TestTBL1 WHERE PersonID=@pPersonID; "
				"END "
			"ELSE "
				"IF (@action=4) BEGIN "
					"SELECT PersonID, FirstName, LastName, Address, City "
					"FROM TestTBL1 ORDER BY PersonID; "
				"END "
			"RETURN @action");
	}

    // Allocate a statement handle
    retcode=SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
    CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_STMT)",
                hstmt, SQL_HANDLE_STMT);

    // Execute Create Procedure
    retcode = SQLExecDirect (hstmt, strCreateSP, SQL_NTS);
    CHECK_ERROR(retcode, "SQLExecDirect(CREATESP)",
                hstmt, SQL_HANDLE_STMT);

exit:

    // Free statement handle
    if (hstmt != SQL_NULL_HSTMT)
        SQLFreeHandle(SQL_HANDLE_STMT, hstmt);

    return retcode;
}

//
// Function to drop existing stored proc. Either Insert_Record or
// Select_Records, which are the two we are using in these examples.
//
SQLRETURN DropProcedure (SQLHDBC  hdbc, char *ProcName) {

    SQLHSTMT hstmt = SQL_NULL_HSTMT;  	// Statement handle
    SQLRETURN retcode = SQL_SUCCESS;	// Return status

    SQLCHAR   strDropSP[1024];

    sprintf (strDropSP, "IF EXISTS (SELECT * FROM sys.objects WHERE "
    		        "type='P' AND name='%s') DROP PROCEDURE %s", ProcName, ProcName);

    // Allocate a statement handle
    retcode=SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
    CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_STMT)", hstmt,
                SQL_HANDLE_STMT);

    // Execute Drop Procedure
    retcode = SQLExecDirect (hstmt, strDropSP, SQL_NTS);
    CHECK_ERROR(retcode, "SQLExecDirect(SQL_HANDLE_STMT)", hstmt,
                SQL_HANDLE_STMT);

exit:

    // Free statement handle
    if (hstmt != SQL_NULL_HSTMT)
        SQLFreeHandle(SQL_HANDLE_STMT, hstmt);

    return retcode;
}

//
// Function to list the stored proc we are using in this example.
// Uses SQLProcedures () to find stored proc Insert_Record or Select_Records.
//
SQLRETURN ListProcedure (SQLHDBC hdbc, char *ProcName) {

    // Columns for binding to SQLProcedures() results set
    SQLCHAR strProcedureCat[BUFF_SIZE];
    SQLCHAR strProcedureSchema[BUFF_SIZE];
    SQLCHAR strProcedureName[BUFF_SIZE];
    SQLSMALLINT ProcedureType;

    SQLLEN  lenProcedureCat, lenProcedureSchema;
    SQLLEN  lenProcedureName, lenProcedureType;

    int header=0;
    SQLHSTMT hstmt = SQL_NULL_HSTMT;  	// Statement handle
    SQLRETURN retcode = SQL_SUCCESS;	// Return status

    // Allocate a statement handle
    retcode=SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
    CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_STMT)",
                hstmt, SQL_HANDLE_STMT);

    retcode = SQLProcedures (hstmt,
                             NULL, 0,
                             NULL, 0,
                             ProcName, strlen(ProcName));

    if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
        // Bind columns in result set to buffers
        // column 1 is the catalogue
        SQLBindCol(hstmt, 1, SQL_C_CHAR, strProcedureCat,
                   sizeof(strProcedureCat), &lenProcedureCat);

        // column 2 is the schema
        SQLBindCol(hstmt, 2, SQL_C_CHAR, strProcedureSchema,
                   sizeof(strProcedureSchema), &lenProcedureSchema);

        // column 3 is the procedure name
        SQLBindCol(hstmt, 3, SQL_C_CHAR, strProcedureName,
                   sizeof(strProcedureName), &lenProcedureName);

        // columns 4 to 7 are skipped
        // column 8 is the procedure type
        SQLBindCol(hstmt, 8, SQL_C_SHORT,&ProcedureType,
                   sizeof(ProcedureType), &lenProcedureType);

        // fetch results (only expecting one row)
        while (SQL_SUCCESS == retcode) {
            retcode = SQLFetch(hstmt);

            if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO) {
                extract_error("SQLFetch Error : ", hstmt, SQL_HANDLE_STMT);
            }

            if (header++==0) {
                printf ("\nDSN : SQLSRV");
            }

            if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
                printf ("\nProcedure Cat    : %s\n", strProcedureCat);
                printf ("Procedure Schema : %s\n", strProcedureSchema);
                printf ("Procedure Name   : %s\n", strProcedureName);
                printf ("Procedure Type   : ");
                switch (ProcedureType) {
                    case SQL_PT_PROCEDURE:
                        printf ("%s\n", "SQL_PT_PROCEDURE");
                        break;
                    case SQL_PT_FUNCTION:
                        printf ("%s\n", "SQL_PT_FUNCTION");
                        break;
                    case SQL_PT_UNKNOWN:
                    default:
                        printf ("%s\n", "SQL_PT_UNKNOWN");
                        break;
                }
            }

            if (retcode==SQL_NO_DATA && header==1) {
                printf ("(NO DATA)\n");
            }
        }
    }

exit:

    // Free statement handle
    if (hstmt != SQL_NULL_HSTMT)
        SQLFreeHandle(SQL_HANDLE_STMT, hstmt);

    // if last status is no data it is ok
    if (retcode==SQL_NO_DATA) {
    	retcode=SQL_SUCCESS;
    }

    return retcode;
}