테이블 반환 매개 변수 사용(ODBC)

적용 대상: SQL Server Azure SQL DatabaseAzure SQL Managed InstanceAzure Synapse Analytics AnalyticsPlatform System(PDW)

이 예제에서는 테이블 반환 매개 변수를 사용하여 여러 열과 함께 서버에 대한 한 번의 호출로 여러 행을 삽입하는 방법을 보여 줍니다.

테이블 반환 매개 변수에 대한 자세한 내용은 테이블 반환 매개 변수(ODBC)를 참조하세요. 테이블 반환 매개 변수를 사용하는 예제에 대한 자세한 내용은 ODBC Table-Valued Parameter Programming Examples를 참조하십시오.

TVPDemo라는 ODBC 데이터 원본이 필요합니다. 컴퓨터의 모든 테스트 데이터베이스가 TVPDemo의 기본 데이터베이스가 될 수 있습니다. 이 데이터 원본은 SQL Server Native Client ODBC 드라이버를 기반으로 해야 합니다.

이 예제를 64비트 운영 체제에서 32비트 애플리케이션으로 작성하여 실행하려는 경우 %windir%\SysWOW64\odbcad32.exe에서 ODBC 관리자를 사용하여 ODBC 데이터 원본을 만들어야 합니다.

이 샘플은 컴퓨터의 기본 SQL Server 인스턴스에 연결합니다. 명명된 인스턴스에 연결하려면 ODBC 데이터 원본의 정의를 변경하여 server\namedinstance 형식으로 인스턴스를 지정합니다. 기본적으로 SQL Server Express는 명명된 인스턴스에 설치됩니다.

odbc32.lib 및 user32.lib를 사용하여 C++ 코드 목록을 컴파일합니다. INCLUDE 환경 변수에 sqlncli.h가 들어 있는 디렉터리를 포함해야 합니다.

// compile with: odbc32.lib user32.lib  
#pragma once  
#define WIN32_LEAN_AND_MEAN   // Exclude rarely-used stuff from Windows headers  
#include <stdio.h>  
#include <stdlib.h>  
#include <tchar.h>  
#include <windows.h>  
#include "sql.h"  
#include "sqlext.h"  
#include "sqlncli.h"  
  
// cardinality of order item related array variables  
#define ITEM_ARRAY_SIZE 20  
  
// struct to pass order entry data  
typedef struct OrdEntry_struct {  
   SQLINTEGER OrdNo;  
   SQLTCHAR OrdDate[24];  
   SQLTCHAR CustCode[6];  
   SQLUINTEGER ItemCount;  
   SQLINTEGER ProdCode[ITEM_ARRAY_SIZE];  
   SQLINTEGER Qty[ITEM_ARRAY_SIZE];  
} OrdEntryData;  
  
SQLHANDLE henv, hdbc, hstmt;  
  
void ODBCError(SQLHANDLE henv, SQLHANDLE hdbc, SQLHANDLE hstmt, SQLHANDLE hdesc, bool ShowError) {  
   SQLRETURN r = 0;  
   SQLTCHAR szSqlState[6] = {0};  
   SQLINTEGER fNativeError = 0;  
   SQLTCHAR szErrorMsg[256] = {0};  
   SQLSMALLINT cbErrorMsgMax = sizeof(szErrorMsg) - 1;  
   SQLSMALLINT cbErrorMsg = 0;  
   TCHAR text[1024] = {0}, title[256] = {0};  
  
   if (hdesc != NULL)  
      r = SQLGetDiagRec(SQL_HANDLE_DESC, hdesc, 1, szSqlState, &fNativeError, szErrorMsg, cbErrorMsgMax, &cbErrorMsg);  
   else {  
      if (hstmt != NULL)  
         r = SQLGetDiagRec(SQL_HANDLE_STMT, hstmt, 1, szSqlState, &fNativeError, szErrorMsg, cbErrorMsgMax, &cbErrorMsg);  
      else {  
         if (hdbc != NULL)  
            r = SQLGetDiagRec(SQL_HANDLE_DBC, hdbc, 1, szSqlState, &fNativeError, szErrorMsg, cbErrorMsgMax, &cbErrorMsg);  
         else  
            r = SQLGetDiagRec(SQL_HANDLE_ENV, henv, 1, szSqlState, &fNativeError, szErrorMsg, cbErrorMsgMax, &cbErrorMsg);  
      }  
   }  
  
   if (ShowError) {  
      _sntprintf_s(title, _countof(title), _TRUNCATE, _T("ODBC Error %i"), fNativeError);  
      _sntprintf_s(text, _countof(text), _TRUNCATE, _T("[%s] - %s"), szSqlState, szErrorMsg);  
  
      MessageBox(NULL, (LPCTSTR) text, (LPCTSTR) _T("ODBC Error"), MB_OK);  
   }  
}  
  
void connect() {  
   SQLRETURN r;  
  
   r = SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv);  
  
   // This is an ODBC v3 application  
   r = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, 0);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, NULL, NULL, NULL, true);  
      exit(-1);  
   }  
  
   r = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);  
  
   // Run in ANSI/implicit transaction mode  
   r = SQLSetConnectAttr(hdbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_OFF, SQL_IS_INTEGER);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, NULL, NULL, NULL, true);  
      exit(-1);  
   }  
  
   TCHAR szConnStrIn[256] = _T("DSN=TVPDemo");  
  
   r = SQLDriverConnect(hdbc, NULL, (SQLTCHAR *) szConnStrIn, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, NULL, NULL, true);  
      exit(-1);  
   }  
}  
  
void setup_ODBC_basics() {  
   SQLRETURN r;  
  
   r = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);  
      exit(-1);  
   }  
}  
  
void setup_TVP_demo () {  
   SQLRETURN r;  
   // Drop prior versions of table and procedure  
   r = SQLExecDirect(hstmt, (SQLTCHAR *) _T("drop procedure TVPOrderEntry"), SQL_NTS);  
   r = SQLExecDirect(hstmt, (SQLTCHAR *) _T("drop procedure TVPOrderInsert"), SQL_NTS);  
   r = SQLExecDirect(hstmt, (SQLTCHAR *) _T("drop procedure TVPItemInsert"), SQL_NTS);  
   r = SQLExecDirect(hstmt, (SQLTCHAR *) _T("drop type TVPParam"), SQL_NTS);  
   r = SQLExecDirect(hstmt, (SQLTCHAR *) _T("drop table TVPOrd"), SQL_NTS);  
   r = SQLExecDirect(hstmt, (SQLTCHAR *) _T("drop table TVPItem"), SQL_NTS);  
  
   // Create tables  
   r = SQLExecDirect(hstmt,   
      (SQLTCHAR *) _T("create table TVPOrd( OrdNo integer identity(1,1), OrdDate datetime, CustCode varchar(5))"), SQL_NTS);  
  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);  
      exit(-1);  
   }  
  
   r = SQLExecDirect(hstmt,   
      (SQLTCHAR *) _T("create table TVPItem( OrdNo integer, ItemNo integer identity(1,1), ProdCode integer, Qty integer)"), SQL_NTS);  
  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Create TABLE type for use as a TVP  
   r = SQLExecDirect(hstmt,   
      (SQLTCHAR *) _T("create type TVPParam as table(ProdCode integer, Qty integer)"), SQL_NTS);  
  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Create procedure for TVPOrd insert   
   r = SQLExecDirect(hstmt, (SQLTCHAR *)   
      _T("create procedure TVPOrderInsert(@CustCode varchar(5), \  
         @OrdNo integer output, @OrdDate datetime output)\  
         as \  
         set @OrdDate = GETDATE();\  
         insert into TVPOrd (OrdDate, CustCode) values (@OrdDate, @CustCode); \  
         select @OrdNo = SCOPE_IDENTITY()"), SQL_NTS);  
  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Create procedure for TVPItem insert   
   r = SQLExecDirect(hstmt,   
      (SQLTCHAR *) _T("create procedure TVPItemInsert(@OrdNo integer, \  
                      @ProdCode integer, @Qty integer)\  
                      as \  
                      insert into TVPItem (OrdNo, ProdCode, Qty) \  
                      values (@OrdNo, @ProdCode, @Qty)"), SQL_NTS);  
  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Create procedure with TVP parameters   
   r = SQLExecDirect(hstmt,   
      (SQLTCHAR *) _T("create procedure TVPOrderEntry(@CustCode varchar(5), @Items TVPParam READONLY, \  
                      @OrdNo integer output, @OrdDate datetime output)\  
                      as \  
                      set @OrdDate = GETDATE();\  
                      insert into TVPOrd (OrdDate, CustCode) values (@OrdDate, @CustCode); \  
                      select @OrdNo = SCOPE_IDENTITY(); \  
                      insert into TVPItem (OrdNo, ProdCode, Qty) \  
                      select @OrdNo, ProdCode, Qty from @Items"), SQL_NTS);  
  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   r = SQLEndTran(SQL_HANDLE_DBC, hdbc, SQL_COMMIT);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
}  
  
void OrdEntry_Simple (OrdEntryData& order) {  
   // Simple order entry  
   SQLRETURN r;  
  
   SQLINTEGER ProdCode, Qty;  
  
   // Bind parameters for the Order  
   // 1 - Custcode input  
   r = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT,SQL_C_TCHAR, SQL_VARCHAR, 5, 0, &order.CustCode, sizeof(order.CustCode), NULL);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // 2 - OrdNo output  
   r = SQLBindParameter(hstmt, 2, SQL_PARAM_OUTPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &order.OrdNo, sizeof(SQLINTEGER), NULL);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // 3- OrdDate output  
   r = SQLBindParameter(hstmt, 3, SQL_PARAM_OUTPUT, SQL_C_TCHAR, SQL_TYPE_TIMESTAMP, 23, 3, order.OrdDate, sizeof(order.OrdDate), NULL);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Insert the order  
   r = SQLExecDirect(hstmt, (SQLTCHAR *) _T("{call TVPOrderInsert(?, ?, ?)}"),SQL_NTS);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Flush results & reset hstmt  
   r = SQLMoreResults(hstmt);  
   if (r != SQL_NO_DATA) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   r = SQLFreeStmt(hstmt, SQL_RESET_PARAMS);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Bind parameters for the Items  
   // 1 - OrdNo   
   r = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 10, 0, &order.OrdNo, sizeof(SQLINTEGER), NULL);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // 2 - ProdCode  
   r = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 10, 0, &ProdCode, sizeof(SQLINTEGER), NULL);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // 3 - Qty  
   r = SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 10, 0, &Qty, sizeof(SQLINTEGER), NULL);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Insert items one at a time  
   r = SQLPrepare(hstmt, (SQLTCHAR *) _T("{call TVPItemInsert(?, ?, ?)}"),SQL_NTS);  
  
   for (unsigned int i = 0; i < order.ItemCount; i++) {  
      ProdCode = order.ProdCode[i];  
      Qty = order.Qty[i];  
      r = SQLExecute(hstmt);  
      if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
         ODBCError(henv, hdbc, hstmt, NULL, true);   
         exit(-1);  
      }  
   }  
  
   // Flush results & reset hstmt  
   r = SQLMoreResults(hstmt);  
   if (r != SQL_NO_DATA) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   r = SQLFreeStmt(hstmt, SQL_RESET_PARAMS);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Commit the transaction  
   r = SQLEndTran(SQL_HANDLE_DBC, hdbc, SQL_COMMIT);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
}  
  
void OrdEntry_PA (OrdEntryData& order){  
   // best practice (not using TVPs) using a parameter array  
   SQLRETURN r;  
  
   // Array if OrdNo for use with array insert of Items  
   SQLINTEGER OrdNo[ITEM_ARRAY_SIZE];  
  
   // Bind parameters for the Order  
   // 1 - Custcode input  
   r = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT,SQL_C_TCHAR, SQL_VARCHAR, 5, 0, &order.CustCode, sizeof(order.CustCode), NULL);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // 2 - OrdNo output  
   r = SQLBindParameter(hstmt, 2, SQL_PARAM_OUTPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &order.OrdNo, sizeof(SQLINTEGER), NULL);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // 3- OrdDate output  
   r = SQLBindParameter(hstmt, 3, SQL_PARAM_OUTPUT, SQL_C_TCHAR, SQL_TYPE_TIMESTAMP, 23, 3, order.OrdDate, sizeof(order.OrdDate), NULL);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);  
      exit(-1);  
   }  
  
   // Insert the order  
   r = SQLExecDirect(hstmt, (SQLTCHAR *) _T("{call TVPOrderInsert(?, ?, ?)}"),SQL_NTS);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Flush results & reset hstmt  
   r = SQLMoreResults(hstmt);  
   if (r != SQL_NO_DATA) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   r = SQLFreeStmt(hstmt, SQL_RESET_PARAMS);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Bind parameters for the Items  
   // 1 - OrdNo   
   r = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, OrdNo, sizeof(SQLINTEGER), NULL);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // 2 - ProdCode  
   r = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, order.ProdCode, sizeof(SQLINTEGER), NULL);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // 3 - Qty  
   r = SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, order.Qty, sizeof(SQLINTEGER), NULL);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Set param array size for items  
   SQLULEN arraySize;  
   arraySize = order.ItemCount;  
   r = SQLSetStmtAttr(hstmt, SQL_ATTR_PARAMSET_SIZE, reinterpret_cast<SQLPOINTER> (arraySize), SQL_IS_UINTEGER);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Initialize OrdNo array   
   for (int i = 0; i < ITEM_ARRAY_SIZE; i++)  
      OrdNo[i] = order.OrdNo;  
  
   // Insert the Items  
   r = SQLExecDirect(hstmt, (SQLTCHAR *) _T("{call TVPItemInsert(?, ?, ?)}"),SQL_NTS);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Flush results & reset hstmt  
   r = SQLMoreResults(hstmt);  
   if (r != SQL_NO_DATA) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   r = SQLFreeStmt(hstmt, SQL_RESET_PARAMS);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   r = SQLSetStmtAttr(hstmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) 1, SQL_IS_UINTEGER);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Commit the transaction  
   r = SQLEndTran(SQL_HANDLE_DBC, hdbc, SQL_COMMIT);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
}  
  
void OrdEntry_TVP (OrdEntryData& order){  
   // Order entry using a TVP  
   SQLRETURN r;  
  
   // Variable for TVP row count  
   SQLLEN cbTVP;  
  
   // Bind parameters for call to TVPOrderEntryDirect  
   // 1 - Custcode input  
   r = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT,SQL_C_TCHAR, SQL_VARCHAR, 5, 0, &order.CustCode, sizeof(order.CustCode), NULL);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // 2 - Items TVP  
   r = SQLBindParameter(hstmt,   
      2,// ParameterNumber  
      SQL_PARAM_INPUT,// InputOutputType  
      SQL_C_DEFAULT,// ValueType   
      SQL_SS_TABLE,// Parametertype  
      ITEM_ARRAY_SIZE,// ColumnSize - for a TVP this the row array size  
      0,// DecimalDigits - DecimalDigits: For a table-valued parameter this must always be 0.
      NULL,// ParameterValuePtr - for a TVP this is the type name of the TVP  
                        // (not needed with stored proc)  
      NULL,// BufferLength - for a TVP this is the length of the type name or SQL_NTS  
                        // (not needed with stored proc)  
      &cbTVP);// StrLen_or_IndPtr - for a TVP this is the number of rows available  
  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // 3 - OrdNo output  
   r = SQLBindParameter(hstmt, 3, SQL_PARAM_OUTPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &order.OrdNo, sizeof(SQLINTEGER), NULL);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // 4- OrdDate output  
   r = SQLBindParameter(hstmt, 4, SQL_PARAM_OUTPUT, SQL_C_TCHAR, SQL_TYPE_TIMESTAMP, 23, 3, order.OrdDate, sizeof(order.OrdDate), NULL);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Bind columns for the TVP (param 2)  
   // First set focus on param 2  
   r = SQLSetStmtAttr(hstmt, SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 2, SQL_IS_INTEGER);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Col 1 - ProdCode  
   r = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, order.ProdCode, sizeof(SQLINTEGER), NULL);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Col 2 - Qty  
   r = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, order.Qty, sizeof(SQLINTEGER), NULL);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Reset param focus  
   r = SQLSetStmtAttr(hstmt, SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 0, SQL_IS_INTEGER);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Initialize TVP row count  
   cbTVP = order.ItemCount; // Number of rows available for input  
  
   // Call one procedure which inserts both the order and items  
   r = SQLExecDirect(hstmt, (SQLTCHAR *) _T("{call TVPOrderEntry(?, ?, ?, ?)}"),SQL_NTS);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Flush results & reset hstmt  
   r = SQLMoreResults(hstmt);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r!= SQL_NO_DATA) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   r = SQLFreeStmt(hstmt, SQL_RESET_PARAMS);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Commit the transaction  
   r = SQLEndTran(SQL_HANDLE_DBC, hdbc, SQL_COMMIT);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
}  
  
void demo_fixed_TVP_binding (SQLHANDLE hstmt){  
   // Bind a TVP using program arrays  
   SQLRETURN r;  
  
   // Variables for SQL parameters  
   SQLTCHAR CustCode[6];  
   SQLWCHAR *TVP = (SQLWCHAR *) L"TVPParam";  
   SQLINTEGER ProdCode[ITEM_ARRAY_SIZE], Qty[ITEM_ARRAY_SIZE];  
   SQLINTEGER OrdNo;  
   SQLTCHAR OrdDate[24];  
  
   // Variables for indicator/length variables associated with parameters  
   SQLLEN cbCustCode, cbTVP, cbProdCode[ITEM_ARRAY_SIZE], cbQty[ITEM_ARRAY_SIZE], cbOrdNo, cbOrdDate;  
  
   // Bind parameters for call to TVPOrderEntryDirect  
   // 1 - Custcode input  
   r = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_TCHAR, SQL_VARCHAR, 5, 0, CustCode, sizeof(CustCode), &cbCustCode);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // 2 - Items TVP  
   r = SQLBindParameter(hstmt,   
      2,// ParameterNumber  
      SQL_PARAM_INPUT,// InputOutputType  
      SQL_C_DEFAULT,// ValueType   
      SQL_SS_TABLE,// Parametertype  
      ITEM_ARRAY_SIZE,// ColumnSize - for a TVP this the row array size  
      0,// DecimalDigits - for a TVP this is the number of columns in the TVP   
      TVP,// ParameterValuePtr - for a TVP this is the type name of the TVP  
                        // and also a token returned by SQLParamData  
      SQL_NTS,// BufferLength - for a TVP this is the length of the type name or SQL_NTS  
      &cbTVP);// StrLen_or_IndPtr - for a TVP this is the number of rows input and output  
  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // 3 - OrdNo output  
   r = SQLBindParameter(hstmt, 3, SQL_PARAM_OUTPUT,SQL_C_LONG, SQL_INTEGER, 0, 0, &OrdNo, sizeof(SQLINTEGER), &cbOrdNo);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // 4- OrdDate output  
   r = SQLBindParameter(hstmt, 4, SQL_PARAM_OUTPUT,SQL_C_TCHAR, SQL_TYPE_TIMESTAMP, 23, 3, OrdDate, sizeof(OrdDate), &cbOrdDate);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Bind columns for the TVP (param 2)  
   //First set focus on param 2  
   r = SQLSetStmtAttr(hstmt, SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 2, SQL_IS_INTEGER);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Col 1 - ProdCode  
   r = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT,SQL_C_LONG, SQL_INTEGER, 0, 0, ProdCode, sizeof(SQLINTEGER), cbProdCode);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Col 2 - Qty  
   r = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT,SQL_C_LONG, SQL_INTEGER, 0, 0, Qty, sizeof(SQLINTEGER), cbQty);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Reset param focus  
   r = SQLSetStmtAttr(hstmt, SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 0, SQL_IS_INTEGER);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Populate parameters  
   cbTVP = 0;   // Number of rows available for input  
   strcpy_s((char *) CustCode, sizeof(CustCode), "CUST1"); cbCustCode = SQL_NTS;  
  
   ProdCode[cbTVP] = 1215;  
   cbProdCode[cbTVP] = sizeof(SQLINTEGER);   
   Qty[cbTVP] = 5;  
   cbQty[cbTVP] = sizeof(SQLINTEGER);   
   cbTVP++;   // Number of rows available for input  
  
   ProdCode[cbTVP] = 1017;  
   cbProdCode[cbTVP] = sizeof(SQLINTEGER);   
   Qty[cbTVP] = 2;  
   cbQty[cbTVP] = sizeof(SQLINTEGER);   
   cbTVP++;   // Number of rows available for input  
  
   // Call the procedure  
   r = SQLExecDirect(hstmt, (SQLTCHAR *) _T("{call TVPOrderEntry(?, ?, ?, ?)}"),SQL_NTS);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Flush rowcounts  
   do {  
      r = SQLMoreResults(hstmt);  
      if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r != SQL_NO_DATA) {  
         ODBCError(henv, hdbc, hstmt, NULL, true);   
         exit(-1);  
      }  
  
   } while (r != SQL_NO_DATA);  
  
   // Commit the transaction  
   r = SQLEndTran(SQL_HANDLE_DBC, hdbc, SQL_COMMIT);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
}  
  
void demo_variable_TVP_binding (SQLHANDLE hstmt) {  
   // Bind a TVP using program data at exec and output row streaming  
   SQLRETURN r;  
  
   // Variables for SQL parameters  
   SQLCHAR CustCode[6];  
   SQLWCHAR *TVP = (SQLWCHAR *) L"TVPParam";  
   SQLINTEGER ProdCode, Qty;  
   SQLINTEGER OrdNo;  
   char *OrdDate[23];  
  
   // Variables for indicator/length variables associated with parameters  
   SQLLEN cbCustCode, cbTVP, cbProdCode, cbQty, cbOrdNo, cbOrdDate;  
  
   // Token returned by SQLParamData to indicate which param data is needed for  
   SQLPOINTER ParamId;  
  
   // Bind parameters for call to TVPOrderEntry  
   r = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_TCHAR, SQL_VARCHAR, 5, 0, CustCode, sizeof(CustCode), &cbCustCode);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // 2 - Items TVP  
   r = SQLBindParameter(hstmt,   
      2,// ParameterNumber  
      SQL_PARAM_INPUT,// InputOutputType  
      SQL_C_DEFAULT,// ValueType   
      SQL_SS_TABLE,// Parametertype  
      ITEM_ARRAY_SIZE,// ColumnSize - for a TVP this the row array size  
      0,// DecimalDigits - for a TVP this is the number of columns in the TVP   
      TVP,// ParameterValuePtr - for a TVP this is the type name of the TVP  
                        // and also a token returned by SQLParamData  
      SQL_NTS,// BufferLength - for a TVP this is the length of the type name or SQL_NTS  
      &cbTVP);// StrLen_or_IndPtr - for a TVP this is the number of rows input and output  
  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // 3 - OrdNo output  
   r = SQLBindParameter(hstmt, 3, SQL_PARAM_OUTPUT,SQL_C_LONG, SQL_INTEGER, 0, 0, &OrdNo, sizeof(SQLINTEGER), &cbOrdNo);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // 4 - OrdDate output  
   r = SQLBindParameter(hstmt, 4, SQL_PARAM_OUTPUT,SQL_C_CHAR, SQL_TYPE_TIMESTAMP, 23, 3, &OrdDate, sizeof(OrdDate), &cbOrdDate);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Bind the TVP columns  
   // First set focus on param 2  
   r = SQLSetStmtAttr(hstmt, SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 2, SQL_IS_INTEGER);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // ProdCode  
   r = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT,SQL_C_LONG, SQL_INTEGER, 0, 0, &ProdCode, sizeof(SQLINTEGER), &cbProdCode);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Qty  
   r = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT,SQL_C_LONG, SQL_INTEGER, 0, 0, &Qty, sizeof(SQLINTEGER), &cbQty);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Reset param focus  
   r = SQLSetStmtAttr(hstmt, SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 0, SQL_IS_INTEGER);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   // Initialze the TVP for row streaming  
   cbTVP = SQL_DATA_AT_EXEC;  
  
   // Initialize output parameters for normal binding  
   cbOrdNo = sizeof(SQLINTEGER);  
   cbOrdDate = sizeof(OrdDate);  
  
   // Populate non-data-at-exec parameters  
   strcpy_s((char *) CustCode , sizeof(CustCode), "CUST1");  
   cbCustCode = SQL_NTS;  
  
   // Call the procedure  
   r = SQLExecDirect(hstmt, (SQLTCHAR *) _T("{call TVPOrderEntry(?, ?, ?, ?)}"), SQL_NTS);  
  
   // Check if para data needed and get 1st param id token  
   if (r == SQL_NEED_DATA)  
      r = SQLParamData(hstmt, &ParamId);  
  
   // Supply param row data  
   int rowNum = 0;  
   while (r == SQL_NEED_DATA) {  
      if (ParamId == TVP) {  
         switch (rowNum) {  
                case 0:   // Supply data for 1st row  
                   // Populate input TVP row constituent columns  
                   ProdCode = 1215;  
                   cbProdCode = sizeof(SQLINTEGER);   
                   Qty = 5;  
                   cbQty = sizeof(SQLINTEGER);  
  
                   // Returning 1 for Str_Len_or_Ind indicates a row is available  
                   r = SQLPutData(hstmt, SQLPOINTER(1), 1);  
                   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
                      ODBCError(henv, hdbc, hstmt, NULL, true);   
                      exit(-1);  
                   }  
  
                   rowNum++;  
                   break;  
  
                case 1:   // Supply data for 2nd row  
                   // Populate another TVP row as above  
                   ProdCode = 1017;  
                   cbProdCode = sizeof(SQLINTEGER);   
  
                   // This time supply Qty via SQLPutData ...  
                   Qty = 0;  
                   cbQty = SQL_DATA_AT_EXEC;  
  
                   r = SQLPutData(hstmt, SQLPOINTER(1), 1);  
                   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
                      ODBCError(henv, hdbc, hstmt, NULL, true);   
                      exit(-1);  
                   }  
  
                   rowNum++;  
                   break;  
  
                default:  
                   // Set StrLenOrIndPtr and DataPtr to 0, and send to indicate no more TVP rows are available
                   r = SQLPutData(hstmt, 0, 0);  
                   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
                      ODBCError(henv, hdbc, hstmt, NULL, true);   
                      exit(-1);  
                   }  
  
                   break;  
         }  
      }  
      else {  
         if (ParamId == &Qty) {  
            Qty = 2;  
            // For a character or binary parameter, SQLPutData can be called multiple times to pass the value in pieces  
            r = SQLPutData(hstmt, &Qty, sizeof(SQLINTEGER));  
            if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
               ODBCError(henv, hdbc, hstmt, NULL, true);   
               exit(-1);  
            }  
         }  
      }  
  
      // Signal that param data is available, get token for next param  
      r = SQLParamData(hstmt, &ParamId);  
   }  
  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      _tprintf_s(_T("Error streaming input rows\n"));  
      exit(-1);  
   }  
  
   // Commit the transaction  
   r = SQLEndTran(SQL_HANDLE_DBC, hdbc, SQL_COMMIT);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
}  
  
// Getting metadata for columns of a table type used for TVPs  
// is just the same as getting column metadata for a regular table  
void demo_metadata_for_table_type_columns(SQLTCHAR *TableTypeName) {  
   SQLHANDLE chstmt;  
   SQLRETURN r;  
  
   r = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &chstmt);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   r = SQLSetStmtAttr(chstmt, SQL_SOPT_SS_NAME_SCOPE, (SQLPOINTER) SQL_SS_NAME_SCOPE_TABLE_TYPE, SQL_IS_UINTEGER);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   r = SQLColumns(chstmt, NULL, 0, NULL, 0, TableTypeName, SQL_NTS, NULL, 0);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   if (r == SQL_SUCCESS) {  
      int colNo = 0;   
      SQLTCHAR columnType[256];  
      for ( ; ; ) {  
         r = SQLFetch(chstmt);  
         if (r != SQL_SUCCESS)   
            break;  
         SQLGetData(chstmt, 6, SQL_C_TCHAR, columnType, sizeof(columnType), NULL);  
         _tprintf(_T("\tColumn %i has type %s\n"), ++colNo, columnType);  
      }  
   }  
  
   r = SQLCloseCursor(chstmt);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   r = SQLFreeHandle(SQL_HANDLE_STMT, chstmt);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
}  
  
// To get metadata for a TVP from a prepared statement, an application calls  
// SQLDescribeParam. SQLGetDescField can be used with SQL_DESC_TYPE_NAME in   
// the IPD to get the underlying table type name and its catalog and schema.  
// For a TVP, DecimalDigits returns the number of columns in the TVP.  
void demo_metadata_from_prepared_statement(SQLHANDLE hstmt) {  
   SQLRETURN r;  
   SQLUSMALLINT paramNo=1;  
   SQLSMALLINT DataType;  
   SQLULEN ParameterSize;  
   SQLSMALLINT DecimalDigits, Nullable, NumParams;  
   SQLHANDLE IPD;  
   SQLINTEGER StringLength;  
   SQLTCHAR parameterTypeName[256];  
  
   r = SQLPrepare(hstmt, (SQLTCHAR *) _T("{call TVPOrderEntry(?, ?, ?, ?)}"), SQL_NTS);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   r = SQLNumParams(hstmt, &NumParams);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   r = SQLGetStmtAttr(hstmt, SQL_ATTR_IMP_PARAM_DESC, &IPD, SQL_IS_POINTER, &StringLength);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   for (paramNo = 1; paramNo <= NumParams; paramNo++) {  
      r = SQLDescribeParam(hstmt, paramNo, &DataType, &ParameterSize, &DecimalDigits, &Nullable);  
      if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
         ODBCError(henv, hdbc, hstmt, NULL, true);   
         exit(-1);  
      }  
  
      if (r == SQL_SUCCESS) {  
         r = SQLGetDescField(IPD, paramNo, SQL_DESC_TYPE_NAME, parameterTypeName, sizeof(parameterTypeName), &StringLength);  
         if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
            ODBCError(henv, hdbc, hstmt, NULL, true);   
            exit(-1);  
         }  
  
         _tprintf(_T("Parameter %i has type %s\n"), paramNo, parameterTypeName);  
      }  
  
      if (DataType == SQL_SS_TABLE) {  
         r = SQLCancel(hstmt);  
         demo_metadata_for_table_type_columns(parameterTypeName);  
      }  
   }  
}  
  
// An application uses SQLProcedureColumns in the usual way to get parameter  
// information for procedures which use TVPs. However, this does not return  
// column metadata for TVPs. Instead an application uses the data type name for   
// the TVP with SQLColumns to get column metadata  
void demo_metadata_from_catalog_APIs(SQLTCHAR *procName) {  
   SQLHANDLE chstmt;  
   SQLRETURN r;  
  
   r = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &chstmt);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, NULL, NULL, true);   
      exit(-1);  
   }  
  
   r = SQLProcedureColumns(chstmt, NULL, 0, NULL, 0, procName, SQL_NTS, NULL, 0);  
  
   if (r == SQL_SUCCESS) {  
      SQLSMALLINT paramType;  
      SQLLEN colNameInd;  
      SQLTCHAR colName[256];  
      SQLTCHAR colTypeName[256];  
      SQLSMALLINT colDataType;  
      SQLINTEGER colOrdinal;  
      int pNum = 0;  
      TCHAR *preamble;  
  
      for ( ; ; ) {  
         r = SQLFetch(chstmt);  
         pNum++;  
         if (r != SQL_SUCCESS)   
            break;  
         r = SQLGetData(chstmt, 4, SQL_C_TCHAR, colName, sizeof(colTypeName), &colNameInd);  
         if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
            ODBCError(henv, hdbc, hstmt, NULL, true);   
            exit(-1);  
         }  
  
         if (colNameInd < 0)  
            colName[0] = 0;  
  
         r = SQLGetData(chstmt, 5, SQL_C_SHORT, &paramType, sizeof(SQL_C_SHORT), NULL);  
         if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
            ODBCError(henv, hdbc, hstmt, NULL, true);   
            exit(-1);  
         }  
  
         switch(paramType) {  
                case SQL_PARAM_INPUT:  
                   preamble = _T("(Input)        Parameter");   
                   break;  
  
                case SQL_PARAM_INPUT_OUTPUT:  
                   preamble = _T("(Input/Output) Parameter");   
                   break;  
  
                case SQL_PARAM_OUTPUT:  
                   preamble = _T("(Output)       Parameter");   
                   break;  
  
                case SQL_RETURN_VALUE:  
                   preamble = _T("(Return)       Parameter");   
                   break;  
  
                case SQL_RESULT_COL:  
                   preamble = _T("Result Column");   
                   break;  
  
                case SQL_PARAM_TYPE_UNKNOWN:  
  
                default:  
                   preamble = _T("(Unknown) Parameter");   
                   break;  
         }  
  
         r = SQLGetData(chstmt, 6, SQL_C_SHORT, &colDataType, sizeof(SQL_C_SHORT), NULL);  
         if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
            ODBCError(henv, hdbc, chstmt, NULL, true);   
            exit(-1);  
         }  
  
         r = SQLGetData(chstmt, 7, SQL_C_TCHAR, colTypeName, sizeof(colTypeName), NULL);  
         if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
            ODBCError(henv, hdbc, chstmt, NULL, true);   
            exit(-1);  
         }  
  
         r = SQLGetData(chstmt, 18, SQL_C_LONG, &colOrdinal, sizeof(SQL_C_LONG), NULL);  
         if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
            ODBCError(henv, hdbc, chstmt, NULL, true);  
            exit(-1);  
         }  
  
         _tprintf(_T("%s %i has type %s\n"), preamble, colOrdinal, colTypeName);  
  
         if (colDataType == SQL_SS_TABLE) {  
            r = SQLCancel(chstmt);  
            if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
               ODBCError(henv, hdbc, chstmt, NULL, true);  
               exit(-1);  
            }  
  
            demo_metadata_for_table_type_columns(colTypeName);  
  
            r = SQLProcedureColumns(chstmt, NULL, 0, NULL, 0, procName, SQL_NTS, NULL, 0);  
            if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
               ODBCError(henv, hdbc, chstmt, NULL, true);   
               exit(-1);  
            }  
  
            for (int x = 0; x < pNum; x++)  
               r = SQLFetch(chstmt);  
  
         }  
      }  
   }  
  
   r = SQLCloseCursor(chstmt);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   r = SQLFreeHandle(SQL_HANDLE_STMT, chstmt);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
}  
  
void printOrder(SQLINTEGER OrdNo) {  
   SQLRETURN r;  
   SQLTCHAR OrdDate[24], CustCode[6];  
   SQLINTEGER ProdCode, Qty;  
   SQLLEN cbOrdNo, cbCustCode, cbOrdDate, cbProdCode, cbQty;   
  
   r = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER,10, 0, &OrdNo, sizeof(SQLINTEGER), &cbOrdNo);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   cbOrdNo = 0;  
  
   r = SQLExecDirect(hstmt, (SQLTCHAR *) _T("select CustCode, OrdDate from TVPOrd where OrdNo=?"), SQL_NTS);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   r = SQLFetch(hstmt);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);  
      exit(-1);  
   }  
  
   r = SQLGetData(hstmt,1, SQL_C_TCHAR, CustCode, sizeof(CustCode), &cbCustCode);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);  
      exit(-1);  
   }  
  
   r = SQLGetData(hstmt,2, SQL_C_TCHAR, OrdDate, sizeof(OrdDate), &cbOrdDate);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);  
      exit(-1);  
   }  
  
   _tprintf(_T("OrderNo %i - Date %s - Cust %s\n"), OrdNo, OrdDate, CustCode);  
  
   r = SQLCloseCursor(hstmt);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);  
      exit(-1);  
   }  
  
   r = SQLExecDirect(hstmt, (SQLTCHAR *) _T("select ProdCode, Qty from TVPItem where OrdNo=?"), SQL_NTS);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);  
      exit(-1);  
   }  
  
   r = SQLFetch(hstmt);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r != SQL_NO_DATA) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   while (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {   
      r = SQLGetData(hstmt,1, SQL_C_LONG, &ProdCode, sizeof(ProdCode), &cbProdCode);  
      if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
         ODBCError(henv, hdbc, hstmt, NULL, true);  
         exit(-1);  
      }  
  
      r = SQLGetData(hstmt,2, SQL_C_LONG, &Qty, sizeof(Qty), &cbQty);  
      if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
         ODBCError(henv, hdbc, hstmt, NULL, true);  
         exit(-1);  
      }  
  
      _tprintf(_T("        Product %i - Quantity %i\n"), ProdCode, Qty);  
  
      r = SQLFetch(hstmt);  
      if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r != SQL_NO_DATA) {  
         ODBCError(henv, hdbc, hstmt, NULL, true);  
         exit(-1);  
      }  
   }  
  
   r = SQLCloseCursor(hstmt);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
  
   r = SQLFreeStmt(hstmt, SQL_RESET_PARAMS);  
   if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {  
      ODBCError(henv, hdbc, hstmt, NULL, true);   
      exit(-1);  
   }  
}  
  
void testSimpleOrderEntry() {  
  
   OrdEntryData order;  
  
   order.OrdNo = 0;  
   order.OrdDate[0] = _T('\0');  
   _tcscpy_s((TCHAR *) order.CustCode, _countof(order.CustCode), _T("CUST1"));  
   order.ProdCode[0] = 10;  
   order.Qty[0] = 1;  
   order.ProdCode[1] = 20;  
   order.Qty[1] = 2;  
   order.ProdCode[2] = 30;  
   order.Qty[2] = 3;  
   order.ProdCode[3] = 40;  
   order.Qty[3] = 4;  
   order.ItemCount = 4;  
  
   OrdEntry_Simple(order);  
   printOrder(order.OrdNo);  
}  
  
void testPAOrderEntry() {  
  
   OrdEntryData order;  
  
   order.OrdNo = 0;  
   order.OrdDate[0] = _T('\0');  
   _tcscpy_s ((TCHAR *) order.CustCode, _countof(order.CustCode), _T("CUST2"));  
   order.ProdCode[0] = 100;  
   order.Qty[0] = 10;  
   order.ProdCode[1] = 200;  
   order.Qty[1] = 20;  
   order.ProdCode[2] = 300;  
   order.Qty[2] = 30;  
   order.ProdCode[3] = 400;  
   order.Qty[3] = 40;  
   order.ItemCount = 4;  
  
   OrdEntry_PA(order);  
   printOrder(order.OrdNo);  
}  
  
void testTVPOrderEntry() {  
   OrdEntryData order;  
  
   order.OrdNo = 0;  
   order.OrdDate[0] = _T('\0');  
   _tcscpy_s((TCHAR *) order.CustCode, _countof(order.CustCode), _T("CUST3"));  
   order.ProdCode[0] = 1000;  
   order.Qty[0] = 100;  
   order.ProdCode[1] = 2000;  
   order.Qty[1] = 200;  
   order.ProdCode[2] = 3000;  
   order.Qty[2] = 300;  
   order.ProdCode[3] = 4000;  
   order.Qty[3] = 400;  
   order.ItemCount = 4;  
  
   OrdEntry_TVP(order);  
  
   printOrder(order.OrdNo);  
}  
  
int _tmain() {  
   connect();  
   setup_ODBC_basics();  
   setup_TVP_demo();  
  
   testSimpleOrderEntry();  
   testPAOrderEntry ();  
   testTVPOrderEntry();  
   demo_metadata_from_catalog_APIs((SQLTCHAR *)_T("TVPOrderEntry"));  
}