/**********************************************************************
* FILENAME :    CallSPOutputParameterStreaming
*
* DESCRIPTION :
*               ODBC 3.8 introduces a new way to retrieve procedure
*               output parameters in parts. An application can now call
*               SQLGetData with a small buffer multiple times to retrieve
*               a large parameter value. This approach reduces the
*               application's memory footprint.
*
*               This is is a SQL Server specific example. To run it, you need:
*
*               - Version 1.7.12+ of the SQL Server ODBC driver
*
*               - Create a SQL Server ODBC driver data source that connects to a database in
*                 which you have created the following procedure and table:
*
*                 use mydb
*
*                 CREATE PROCEDURE [dbo].[SP_OutputParameterStreaming] @Param1 VARBINARY(max) OUTPUT
*
*                 AS
*
*                 BEGIN
*
*                 -- SET NOCOUNT ON added to prevent extra result sets from
*
*                 -- interfering with SELECT statements.
*
*                 SET NOCOUNT ON;
*
*                 SELECT @Param1 = [Document]  FROM [mydb].[dbo].[TableImage] where [TableImage].[id]  = 1
*
*                 END
*
*                 CREATE TABLE TableImage(id integer identity, Document varbinary(max))
*                 INSERT INTO TableImage(Document)  SELECT * FROM OPENROWSET(BULK N'photo.jpg', SINGLE_BLOB) AS I
*
*               - Compile the example against the unixODBC Driver Manager included with the
*                 SQL Server ODBC driver rather than one included with your system:
*
*                 $ LANG=C cc -I/usr/local/easysoft/unixODBC/include -L/usr/local/easysoft/unixODBC/lib \
*                     -lodbc CallSPOutputParameterStreaming.c -o CallSPOutputParameterStreaming
*
*/


#include <stdio.h>
#include <stdlib.h>
#include <sql.h>
#include <sqlext.h>
#include <string.h>

#include "util.c"

int main () {
    SQLHENV   henv  = SQL_NULL_HENV;
    SQLHDBC   hdbc  = SQL_NULL_HDBC;
    SQLHSTMT  hstmt = SQL_NULL_HSTMT;
    SQLRETURN retcode, retcode2;
    SQLLEN    lengthofpicture;          // The actual length of the picture
    BYTE      smallbuffer[100];         // A very small buffer
    CHAR      filename[14] = "photo.jpg";
    FILE      *pfile;

    // Allocate an environment handle
    retcode=SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
    CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_ENV)",
                henv, SQL_HANDLE_ENV);

    // Notify ODBC that this is an ODBC 3.8 app
    retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
                                    (SQLPOINTER) SQL_OV_ODBC3_80, 0);
    CHECK_ERROR(retcode, "SQLSetEnvAttr(SQL_HANDLE_ENV)",
                henv, SQL_HANDLE_ENV);

    // Allocate ODBC connection handle and connect
    retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
    CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_DBC)",
                hdbc, SQL_HANDLE_DBC);

    // Connect to data source
    retcode = SQLDriverConnect(hdbc, NULL, "DSN=DATASOURCE;", SQL_NTS,
                             NULL, 0, NULL, SQL_DRIVER_NOPROMPT);
    CHECK_ERROR(retcode, "SQLDriverConnect(DATASOURCE)",
                hdbc, SQL_HANDLE_DBC);

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

   // Bind the streamed output parameter
   retcode = SQLBindParameter(
         hstmt,
         1,
         SQL_PARAM_OUTPUT_STREAM,   // A streamed output parameter
         SQL_C_BINARY,
         SQL_VARBINARY,
         0,                         // ColumnSize: The maximum size of varbinary(max)
         0,                         // DecimalDigits is ignored for binary type
         (SQLPOINTER)1,             // ParameterValuePtr: An application-defined
                                    // token (this will be returned from SQLParamData)
                                    // In this example, we used the ordinal
                                    // of the parameter
         0,                         // BufferLength is ignored for streamed output parameters
         &lengthofpicture);         // StrLen_or_IndPtr: The status variable returned

   CHECK_ERROR(retcode, "SQLBindParameter()",
                hstmt, SQL_HANDLE_STMT);

   retcode = SQLPrepare(hstmt, "{call SP_OutputParameterStreaming(?)}", SQL_NTS);

   retcode = SQLExecute(hstmt);

   pfile = fopen(filename, "w+b");
   if ( !pfile ) {
    printf( "failed to open '%s' for w+b\n", filename );
    exit( -1 );
   }

   // Assume that the retrieved picture exists. Use SQLBindCol or SQLGetData to retrieve the result-set.
   // Process the result set and move to the streamed output parameters
   if ( retcode != SQL_PARAM_DATA_AVAILABLE )
      retcode = SQLMoreResults( hstmt );

   // SQLGetData retrieves and displays the picture in parts.
   // The streamed output parameter is available.
   while (retcode == SQL_PARAM_DATA_AVAILABLE) {
      SQLPOINTER token;   // Output by SQLParamData
      SQLLEN cbleft;      // #bytes remained
      retcode = SQLParamData(hstmt, &token);   // Returned token is 2 (according to the binding)
      if ( retcode == SQL_PARAM_DATA_AVAILABLE ) {
         // A do-while loop retrieves the picture in parts
         do {
            retcode2 = SQLGetData(
               hstmt,
               1,          // The value of the token is the ordinal
               SQL_C_BINARY,
               smallbuffer,            // A small buffer
               sizeof(smallbuffer),
	       &cbleft);               // How much data we can get

	    if (cbleft > sizeof(smallbuffer)) // Full buffer
             {
               fwrite(smallbuffer, sizeof(char), sizeof(smallbuffer), pfile);
             }
	     else // Partial buffer on last SQLGetData
             {
               fwrite(smallbuffer, sizeof(char), cbleft, pfile);
             }
         }
         while ( retcode2 == SQL_SUCCESS_WITH_INFO );
      }
   }

   // Close the file
   fflush(pfile);
   fclose(pfile);

exit:

    printf ("\nComplete.\n");

    // Statement
    if (hstmt != SQL_NULL_HSTMT)
        SQLFreeHandle(SQL_HANDLE_STMT, hstmt);

    // Connection
    if (hdbc != SQL_NULL_HDBC) {
        SQLDisconnect(hdbc);
        SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
    }

    // Environment
    if (henv != SQL_NULL_HENV)
        SQLFreeHandle(SQL_HANDLE_ENV, henv);

    return 0;
}

See Also