home *** CD-ROM | disk | FTP | other *** search
/ io Programmo 32 / IOPROG_32.ISO / SOFT / SqlEval7 / devtools / samples / ODBC / compute / compute.cpp next >
Encoding:
C/C++ Source or Header  |  1997-12-12  |  19.6 KB  |  619 lines

  1. // Compute.cpp -- Use ODBC to execute a Transact-SQL SELECT statement
  2. // containing a COMPUTE clause.
  3. //
  4. // Illustrates fast retrieve on a default ODBC statement handle and
  5. // determining the properties of a result set using SQL Server ODBC
  6. // driver-specific column attributes.
  7. //
  8. // This file is part of Microsoft SQL Server online documentation.
  9. // Copyright (C) 1992-1997 Microsoft Corporation. All rights reserved.
  10. //
  11. // This source code is an intended supplement to the Microsoft SQL
  12. // Server online references and related electronic documentation.
  13. #include <windows.h>
  14. #include <stdio.h>
  15. #include <tchar.h>
  16.  
  17. #include "sql.h"
  18. #include "sqlext.h"
  19. #include "odbcss.h"
  20.  
  21. // Macros
  22. #define NUMROWS_CHUNK               20
  23. #define CBCOLNAME_MAX               64
  24.  
  25. // Types
  26. typedef struct tagDRIVERBYLIST
  27.     {
  28.     SQLSMALLINT     nBys;
  29.     SQLSMALLINT     aByList[1];
  30.     } DRIVERBYLIST;
  31. typedef DRIVERBYLIST* PDRIVERBYLIST;
  32.  
  33. typedef struct tagLOCALBYLIST
  34.     {
  35.     SQLSMALLINT     nBys;
  36.     SQLSMALLINT*    pBys;
  37.     } LOCALBYLIST;
  38. typedef LOCALBYLIST* PLOCALBYLIST;
  39.  
  40. typedef struct tagODBCCOLINFO
  41.     {
  42.     SQLTCHAR        szColName[CBCOLNAME_MAX + 1];
  43.     SQLSMALLINT     cbColName;
  44.     SQLSMALLINT     fSQLType;
  45.     SQLUINTEGER     cbColData;
  46.     SQLSMALLINT     cbScale;
  47.     SQLSMALLINT     fNullable;
  48.     SQLSMALLINT     fBindType;
  49.     UINT            obValue;
  50.     UINT            obIndicator;
  51.     } ODBCCOLINFO;
  52. typedef ODBCCOLINFO*    PODBCCOLINFO;
  53.  
  54. typedef struct tagODBCSETINFO
  55.     {
  56.     SQLUSMALLINT    nCols;
  57.     SQLINTEGER      nRows;
  58.     SQLINTEGER      cbResultSet;
  59.     PODBCCOLINFO    pODBCColInfo;
  60.     PBYTE           pRows;
  61.     PLOCALBYLIST    pByList;
  62.     } ODBCSETINFO;
  63. typedef ODBCSETINFO*    PODBCSETINFO;
  64.  
  65. // Function prototypes
  66. SQLRETURN GetColumnsInfo(SQLHSTMT hstmt, SWORD nCols, ODBCCOLINFO** ppODBCColInfo);
  67. SQLRETURN BindCols(SQLHSTMT hstmt, SQLUSMALLINT nCols, PODBCCOLINFO pODBCColInfo,
  68.                    PBYTE pRows);
  69. void CreateDBBindings(PODBCSETINFO pODBCSetInfo);
  70. void GetData(SQLHSTMT hstmt);
  71.  
  72. void DumpError(PTSTR pErrorText);
  73. void DumpError(SQLSMALLINT eHandleType, SQLHANDLE hodbc);
  74.  
  75. // AdjustLen supports binding on four-byte boundaries.
  76. _inline SQLUINTEGER AdjustLen(SQLUINTEGER cb)
  77.     {
  78.     return ((cb + 3) & ~3);
  79.     }
  80.  
  81. int main()
  82.     {
  83.     // ODBC handles
  84.     SQLHENV     henv = NULL;
  85.     SQLHDBC     hdbc = NULL;
  86.     SQLHSTMT    hstmt = NULL;
  87.  
  88.     PTSTR       szDataSource = _T("MyDatasource");
  89.     PTSTR       szUID = _T("MyUID");
  90.     PTSTR       szPWD = _T("MyPWD");
  91.  
  92.     PTSTR       szSQLSelect = 
  93.         _T("SELECT")
  94.         _T(" O.EmployeeID, O.OrderID,")
  95.         _T(" FullPrice = (UnitPrice * Quantity), Discount,")
  96.         _T(" Discounted = UnitPrice * (1 - Discount) * Quantity")
  97.         _T(" FROM Orders O, [Order Details] OD")
  98.         _T(" WHERE O.OrderID = OD.OrderID")
  99.         _T(" ORDER BY EmployeeID, O.OrderID")
  100.         _T(" COMPUTE")
  101.         _T(" SUM(UnitPrice * Quantity),")
  102.         _T(" SUM(UnitPrice * (1 - Discount) * Quantity)")
  103.         _T(" BY EmployeeID, O.OrderID")
  104.         _T(" COMPUTE")
  105.         _T(" SUM(UnitPrice * Quantity),")
  106.         _T(" SUM(UnitPrice * (1 - Discount) * Quantity)")
  107.         _T(" BY EmployeeID")
  108.         _T(" COMPUTE")
  109.         _T(" SUM(UnitPrice * Quantity),")
  110.         _T(" SUM(UnitPrice * (1 - Discount) * Quantity)");
  111.  
  112.     // Initialize the ODBC environment.
  113.     if (SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv) == SQL_ERROR)
  114.         goto EXIT;
  115.     SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*) SQL_OV_ODBC3,
  116.         SQL_IS_INTEGER);
  117.  
  118.     // Allocate a connection handle and connect to the data source.
  119.     if (SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc) == SQL_ERROR)
  120.         {
  121.         DumpError(_T("AllocHandle on DBC failed."));
  122.         goto EXIT;
  123.         }
  124.     if (SQLConnect(hdbc, (SQLTCHAR*) szDataSource, SQL_NTS,
  125.         (SQLTCHAR*) szUID, SQL_NTS, (SQLTCHAR*) szPWD, SQL_NTS) == SQL_ERROR)
  126.         {
  127.         DumpError(SQL_HANDLE_DBC, hdbc);
  128.         goto EXIT;
  129.         }
  130.  
  131.     // Get a statement handle and execute a Transact-SQL SELECT statement
  132.     //  containing a COMPUTE clause.
  133.     if (SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt) == SQL_ERROR)
  134.         {
  135.         DumpError(SQL_HANDLE_DBC, hdbc);
  136.         goto EXIT;
  137.         }
  138.     if (SQLExecDirect(hstmt, (SQLTCHAR*) szSQLSelect, SQL_NTS) == SQL_ERROR)
  139.         {
  140.         DumpError(SQL_HANDLE_STMT, hstmt);
  141.         goto EXIT;
  142.         }
  143.  
  144.     // Retrieve data from multiple result sets.
  145.     GetData(hstmt);
  146.  
  147. EXIT:
  148.     if (hstmt != NULL)
  149.         {
  150.         SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
  151.         }
  152.  
  153.     if (hdbc != NULL)
  154.         {
  155.         SQLDisconnect(hdbc);
  156.         SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
  157.         }
  158.  
  159.     if (henv != NULL)
  160.         {
  161.         SQLFreeHandle(SQL_HANDLE_ENV, henv);
  162.         }
  163.  
  164.     return (0);
  165.     }
  166.  
  167. // GetData(...) -- retrieve data for all result sets.
  168. void GetData
  169.     (
  170.     SQLHSTMT hstmt
  171.     ) 
  172.     {
  173.     SQLUSMALLINT    nCols;
  174.     SQLUSMALLINT    nCol;
  175.     PODBCSETINFO    pODBCSetInfo = NULL;
  176.     SQLRETURN       sRet;
  177.     SQLINTEGER      nRowsFetched = 0;
  178.     UINT            nRow;
  179.     SQLINTEGER      nComputes = 0;
  180.     SQLINTEGER      nSets = 1;
  181.     SQLINTEGER      nSet;
  182.     BYTE*           pValue;
  183.     SQLINTEGER*     pIndicator;
  184.  
  185.     // If SQLNumResultCols failed, then some error occured in statement
  186.     //  execution. Exit.
  187.     if (!SQL_SUCCEEDED(sRet = SQLNumResultCols(hstmt, (SQLSMALLINT*) &nCols)))
  188.         {
  189.         DumpError(SQL_HANDLE_STMT, hstmt);
  190.         goto EXIT;
  191.         }
  192.  
  193.     // If SQLNumResultCols succeeded, but no columns are indicated, then
  194.     //  the statement did not return a results set.
  195.     if (nCols == 0)
  196.         {
  197.         DumpError(_T("Invalid statement."));
  198.         goto EXIT;
  199.         }
  200.  
  201.     // Determine the presence of COMPUTE clause result sets. The SQL Server
  202.     //  driver uses column attributes to report multiple sets.
  203.     SQLColAttribute(hstmt, 1, SQL_CA_SS_NUM_COMPUTES,
  204.         NULL, 0, NULL, (SQLPOINTER) &nComputes);
  205.  
  206.     // The number of result sets is 1 (for the normal rows) + nComputes.
  207.     nSets += nComputes;
  208.  
  209.     // Create a column info structure pointer array, one element for each
  210.     //  result set.
  211.     pODBCSetInfo = new ODBCSETINFO[1 + nComputes];
  212.     for (nSet = 0; nSet < 1 + nComputes; nSet++)
  213.         {
  214.         pODBCSetInfo[nSet].nCols = 0;
  215.         pODBCSetInfo[nSet].nRows = 1;
  216.         pODBCSetInfo[nSet].cbResultSet = 0;
  217.         pODBCSetInfo[nSet].pODBCColInfo = NULL;
  218.         pODBCSetInfo[nSet].pRows = NULL;
  219.         pODBCSetInfo[nSet].pByList = NULL;
  220.         }
  221.  
  222.     // Set up info structure for normal result set. The normal result set
  223.     //  can contain multiple rows. All COMPUTE clause sets will have only
  224.     //  a single row. We can optimize retrieval of rows for the SQL Server
  225.     //  driver by using row array binding for the normal set (0).
  226.     pODBCSetInfo[0].nCols = nCols;
  227.     pODBCSetInfo[0].nRows = NUMROWS_CHUNK;
  228.     nSet = 0;
  229.  
  230.     while (TRUE)
  231.         {
  232.         // If required, get the column information for the result set.
  233.         if (pODBCSetInfo[nSet].pODBCColInfo == NULL)
  234.             {
  235.             if (pODBCSetInfo[nSet].nCols == 0)
  236.                 {
  237.                 SQLNumResultCols(hstmt, (SQLSMALLINT*) &nCols);
  238.                 pODBCSetInfo[nSet].nCols = nCols;
  239.                 }
  240.  
  241.             if (GetColumnsInfo(hstmt, pODBCSetInfo[nSet].nCols,
  242.                 &(pODBCSetInfo[nSet].pODBCColInfo)) == SQL_ERROR)
  243.                 {
  244.                 goto EXIT;
  245.                 }
  246.             }
  247.  
  248.         // If this is a COMPUTE clause result sets, get the ordering columns
  249.         //  (if any) for the set and display them.
  250.         if (nSet > 0)
  251.             {
  252.             SQLSMALLINT     nBy;
  253.             SQLSMALLINT     nBys;
  254.             PLOCALBYLIST    pLocalByList;
  255.  
  256.             if (pODBCSetInfo[nSet].pByList == NULL)
  257.                 {
  258.                 PDRIVERBYLIST  pDriverByList;
  259.                 pLocalByList = new LOCALBYLIST;
  260.  
  261.                 SQLColAttribute(hstmt, 1, SQL_CA_SS_COMPUTE_BYLIST, NULL,
  262.                     0, NULL, (SQLPOINTER) &pDriverByList);
  263.  
  264.                 if (pDriverByList)
  265.                     {
  266.                     pLocalByList->nBys = pDriverByList->nBys;
  267.                     nBys = pLocalByList->nBys;
  268.                     pLocalByList->pBys = new SQLSMALLINT[nBys];
  269.                     for (nBy = 0; nBy < nBys; nBy++)
  270.                         {
  271.                         pLocalByList->pBys[nBy] = 
  272.                             pDriverByList->aByList[nBy]; 
  273.                         }
  274.                     }
  275.                 else
  276.                     {
  277.                     nBys = pLocalByList->nBys = 0;
  278.                     pLocalByList->pBys = NULL;
  279.                     }
  280.  
  281.                 pODBCSetInfo[nSet].pByList = pLocalByList;
  282.                 }
  283.             else
  284.                 {
  285.                 pLocalByList = pODBCSetInfo[nSet].pByList;
  286.                 nBys = pLocalByList->nBys;
  287.                 }
  288.  
  289.             for (nBy = 0; nBy < nBys; nBy++)
  290.                 {
  291.                 _tprintf(_T("This compute clause ordered by columns: "));
  292.                 for (nBy = 0; nBy < pLocalByList->nBys; )
  293.                     {
  294.                     _tprintf(_T("%u"), pLocalByList->pBys[nBy]);
  295.                     nBy++;
  296.  
  297.                     if (nBy == pLocalByList->nBys)
  298.                         {
  299.                         _tprintf(_T("\n"));
  300.                         }
  301.                     else
  302.                         {
  303.                         _tprintf(_T(", "));
  304.                         }
  305.                     }
  306.                 }
  307.             }
  308.             
  309.  
  310.         // Create a string for bound return values if required.
  311.         if (pODBCSetInfo[nSet].pRows == NULL)
  312.             {
  313.             CreateDBBindings(&(pODBCSetInfo[nSet]));
  314.             }
  315.  
  316.         // Binding must be done on each result set.
  317.         BindCols(hstmt, pODBCSetInfo[nSet].nCols,
  318.             pODBCSetInfo[nSet].pODBCColInfo, pODBCSetInfo[nSet].pRows);
  319.  
  320.         // Set for ODBC row array retrieval. Fast retrieve for all sets.
  321.         SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_BIND_TYPE,
  322.             (void*) pODBCSetInfo[nSet].cbResultSet, SQL_IS_UINTEGER);
  323.         SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE,
  324.             (void*) pODBCSetInfo[nSet].nRows, SQL_IS_UINTEGER);
  325.         SQLSetStmtAttr(hstmt, SQL_ATTR_ROWS_FETCHED_PTR, (void*) &nRowsFetched,
  326.             sizeof(SQLINTEGER));
  327.  
  328.         while (TRUE)
  329.             {
  330.             // In ODBC 3.x, SQLFetch supports arrays of bound rows or columns.
  331.             //  SQLFetchScroll (or ODBC 2.x SQLExtendedFetch) is not necessary
  332.             //  to support fastest retrieval of our data rows.
  333.             sRet = SQLFetch(hstmt);
  334.             if (sRet == SQL_ERROR)
  335.                 {
  336.                 DumpError(SQL_HANDLE_STMT, hstmt);
  337.                 goto EXIT;
  338.                 }
  339.             else if (sRet == SQL_NO_DATA)
  340.                 {
  341.                 break;
  342.                 }
  343.             else if (sRet == SQL_SUCCESS_WITH_INFO)
  344.                 {
  345.                 DumpError(SQL_HANDLE_STMT, hstmt);
  346.                 }
  347.  
  348.             // Display the data. On each column, check the indicator and
  349.             //  print the string "<null>" if no data available.
  350.             for (nRow = 0; nRow < (UINT) nRowsFetched; nRow++)
  351.                 {
  352.                 for (nCol = 0; nCol < pODBCSetInfo[nSet].nCols; nCol++)
  353.                     {
  354.                     _tprintf(_T("%s%s:"), 
  355.                         pODBCSetInfo[nSet].pODBCColInfo[nCol].szColName,
  356.                         (pODBCSetInfo[nSet].pODBCColInfo[nCol].cbColName > 7 ? 
  357.                         _T("\t") : _T("\t\t")));
  358.  
  359.                     pIndicator = (SQLINTEGER*) (pODBCSetInfo[nSet].pRows + 
  360.                         (nRow * pODBCSetInfo[nSet].cbResultSet) + 
  361.                         pODBCSetInfo[nSet].pODBCColInfo[nCol].obIndicator);
  362.                     if (*pIndicator == SQL_NULL_DATA)
  363.                         {
  364.                         _tprintf(_T("\t<null>\n"));
  365.                         }
  366.                     else
  367.                         {
  368.                         pValue = pODBCSetInfo[nSet].pRows + 
  369.                             ((nRow * pODBCSetInfo[nSet].cbResultSet) + 
  370.                             pODBCSetInfo[nSet].pODBCColInfo[nCol].obValue);
  371.                         switch (pODBCSetInfo[nSet].pODBCColInfo[nCol].fBindType)
  372.                             {
  373.                             case SQL_C_CHAR:
  374.                                 {
  375.                                 printf("\t%s\n", (char*) pValue);
  376.                                 break;
  377.                                 }
  378.  
  379.                             case SQL_C_SLONG:
  380.                                 {
  381.                                 printf("\t%lu\n", *(ULONG*) pValue);
  382.                                 break;
  383.                                 }
  384.  
  385.                             case SQL_C_DOUBLE:
  386.                                 {
  387.                                 printf("\t%f\n", *(double*) pValue);
  388.                                 break;
  389.                                 }
  390.  
  391.                             default:
  392.                                 {
  393.                                 _tprintf(_T("\tUnsupported conversion.\n"));
  394.                                 break;
  395.                                 }
  396.                             }
  397.                         }
  398.                     }
  399.                 _tprintf(_T("\n"));
  400.                 }
  401.             }
  402.  
  403.         // If another result set exists, then get the next set.
  404.         if (SQLMoreResults(hstmt) == SQL_SUCCESS)
  405.             {
  406.             // Determine the result set indicator (0 for normal result set,
  407.             //  number of COMPUTE clause set).
  408.             sRet = SQLColAttribute(hstmt, 1, SQL_CA_SS_COMPUTE_ID,
  409.                 NULL, 0, NULL, (SQLPOINTER) &nSet);
  410.             }
  411.         else
  412.             {
  413.             break;
  414.             }
  415.         }
  416.  
  417. EXIT:
  418.     // Cleanup and go home.
  419.     if (pODBCSetInfo != NULL)
  420.         {
  421.         for (nSet = 0; nSet < nComputes + 1; nSet++)
  422.             {
  423.             if (pODBCSetInfo[nSet].pODBCColInfo != NULL)
  424.                 delete [] pODBCSetInfo[nSet].pODBCColInfo;
  425.             if (pODBCSetInfo[nSet].pRows != NULL)
  426.                 delete [] pODBCSetInfo[nSet].pRows;
  427.             if (pODBCSetInfo[nSet].pByList != NULL)
  428.                 {
  429.                 if (pODBCSetInfo[nSet].pByList->pBys != NULL)
  430.                     delete [] pODBCSetInfo[nSet].pByList->pBys;
  431.  
  432.                 delete [] pODBCSetInfo[nSet].pByList;
  433.                 }
  434.             }
  435.  
  436.         delete [] pODBCSetInfo;
  437.         }
  438.  
  439.     return;
  440.     }
  441.  
  442. // GetColumnsInfo(...) -- Query the result set to determine the properties
  443. //  of result set columns.
  444. SQLRETURN GetColumnsInfo
  445.     (
  446.     SQLHSTMT hstmt,
  447.     SWORD    nCols,
  448.     ODBCCOLINFO** ppODBCColInfo
  449.     )
  450.     {
  451.     ODBCCOLINFO*    pODBCColInfo;
  452.     SWORD           nCol;
  453.     SQLRETURN       sRet;
  454.     SQLINTEGER      nComputeCol;
  455.  
  456.     pODBCColInfo = new ODBCCOLINFO[nCols];
  457.     if (pODBCColInfo == NULL)
  458.         {
  459.         DumpError(_T("Out of memory"));
  460.         return (SQL_ERROR);
  461.         }
  462.  
  463.     for (nCol = 0; nCol < nCols; nCol++)
  464.         {
  465.         sRet = SQLDescribeCol(hstmt, nCol+1,
  466.             pODBCColInfo[nCol].szColName,
  467.             CBCOLNAME_MAX + 1,
  468.             &pODBCColInfo[nCol].cbColName,
  469.             &pODBCColInfo[nCol].fSQLType,
  470.             &pODBCColInfo[nCol].cbColData,
  471.             &pODBCColInfo[nCol].cbScale,
  472.             &pODBCColInfo[nCol].fNullable
  473.             );
  474.  
  475.         if (sRet == SQL_ERROR)
  476.             {
  477.             DumpError(SQL_HANDLE_STMT, hstmt);
  478.             break;
  479.             }
  480.  
  481.         SQLColAttribute(hstmt, nCol+1, SQL_CA_SS_COLUMN_ID,
  482.             NULL, 0, NULL, (SQLPOINTER) &nComputeCol);
  483.  
  484.         switch (pODBCColInfo[nCol].fSQLType)
  485.             {
  486.             case SQL_CHAR:
  487.             case SQL_VARCHAR:
  488.             case SQL_NUMERIC:
  489.             case SQL_DECIMAL:
  490.                 {
  491.                 pODBCColInfo[nCol].fBindType = SQL_C_CHAR;
  492.                 pODBCColInfo[nCol].cbColData = 
  493.                     AdjustLen(pODBCColInfo[nCol].cbColData + 1);
  494.                 break;
  495.                 }
  496.  
  497.             case SQL_REAL:
  498.             case SQL_FLOAT:
  499.             case SQL_DOUBLE:
  500.                 {
  501.                 pODBCColInfo[nCol].fBindType = SQL_C_DOUBLE;
  502.                 pODBCColInfo[nCol].cbColData = sizeof(double);
  503.                 break;
  504.                 }
  505.  
  506.             case SQL_INTEGER:
  507.             case SQL_SMALLINT:
  508.             case SQL_TINYINT:
  509.             case SQL_BIT:
  510.                 {
  511.                 pODBCColInfo[nCol].fBindType = SQL_C_SLONG;
  512.                 pODBCColInfo[nCol].cbColData = sizeof(long);
  513.                 break;
  514.                 }
  515.  
  516.             default:
  517.                 {
  518.                 SQLColAttribute(hstmt, nCol+1, SQL_DESC_DISPLAY_SIZE,
  519.                     NULL, 0, NULL, &pODBCColInfo[nCol].cbColData);
  520.                 pODBCColInfo[nCol].fBindType = SQL_C_CHAR;
  521.                 pODBCColInfo[nCol].cbColData = 
  522.                     AdjustLen(pODBCColInfo[nCol].cbColData + 1);
  523.                 break;
  524.                 }
  525.             }
  526.         }
  527.  
  528.     *ppODBCColInfo = pODBCColInfo;
  529.  
  530.     return (sRet);
  531.     }
  532.  
  533. // CreateDBBindings(...) -- Determine the row length for bound values.
  534. void CreateDBBindings
  535.     (
  536.     PODBCSETINFO pODBCSetInfo
  537.     )
  538.     {
  539.     SQLUSMALLINT    nCol;
  540.     UDWORD          cbRow = 0;
  541.  
  542.     for (nCol = 0; nCol < pODBCSetInfo->nCols; nCol++)
  543.         {
  544.         // Set the data buffer pointer and then add the width of the 
  545.         //  bound buffer.
  546.         pODBCSetInfo->pODBCColInfo[nCol].obValue = cbRow;
  547.         cbRow += pODBCSetInfo->pODBCColInfo[nCol].cbColData;
  548.  
  549.         // Indicators bound beyond data.
  550.         pODBCSetInfo->pODBCColInfo[nCol].obIndicator = cbRow;
  551.         cbRow += sizeof(SQLINTEGER);
  552.         }
  553.  
  554.     pODBCSetInfo->pRows = new BYTE[cbRow * pODBCSetInfo->nRows];
  555.     pODBCSetInfo->cbResultSet = (SQLUINTEGER) cbRow;
  556.  
  557.     return;
  558.     }
  559.  
  560. // BindCols(...) -- Bind the columns in the structure.
  561. SQLRETURN BindCols
  562.     (
  563.     SQLHSTMT hstmt,
  564.     SQLUSMALLINT nCols,
  565.     PODBCCOLINFO pODBCColInfo,
  566.     PBYTE pRows
  567.     )
  568.     {
  569.     SQLUSMALLINT    nCol;
  570.  
  571.     for (nCol = 0; nCol < nCols; nCol++)
  572.         {
  573.         if (SQLBindCol(hstmt, nCol + 1, pODBCColInfo[nCol].fBindType,
  574.             (SQLPOINTER) (pRows + pODBCColInfo[nCol].obValue),
  575.             pODBCColInfo[nCol].cbColData, 
  576.             (SQLINTEGER*) (pRows + pODBCColInfo[nCol].obIndicator)) == 
  577.             SQL_ERROR)
  578.             {
  579.             return (SQL_ERROR);
  580.             }
  581.         }
  582.  
  583.     return (SQL_SUCCESS);
  584.     }
  585.  
  586. // DumpError(PTSTR) -- printf the string to the console.
  587. void DumpError
  588.     (
  589.     PTSTR pErrorText
  590.     )
  591.     {
  592.     _tprintf(_T("%s\n"), pErrorText);
  593.     }
  594.  
  595. // DumpError(SQLSMALLINT, SQLHANDLE) -- printf the diagnostic records for the
  596. //  handle received.
  597. void DumpError
  598.     (
  599.     SQLSMALLINT eHandleType,
  600.     SQLHANDLE hodbc
  601.     )
  602.     {
  603.     SQLTCHAR    szState[SQL_SQLSTATE_SIZE + 1];
  604.     SQLTCHAR    szMessage[SQL_MAX_MESSAGE_LENGTH + 1];
  605.     SQLINTEGER  nServerError;
  606.     SQLSMALLINT cbMessage;
  607.     UINT        nRec = 1;
  608.  
  609.     while (SQL_SUCCEEDED(SQLGetDiagRec(eHandleType, hodbc, nRec, szState,
  610.         &nServerError, szMessage, SQL_MAX_MESSAGE_LENGTH + 1, &cbMessage)))
  611.         {
  612.         _tprintf(_T("SQLSTATE: %s\nNative error: %ld\nMessage: %s\n"),
  613.             (PTSTR) szState, nServerError, (PTSTR) szMessage);
  614.         nRec++;
  615.         }
  616.  
  617.     return;
  618.     }
  619.