I have snipped ALL previous discussion on this as I am finding it almost
impossible to see the wood from the trees now. If I understand you correctly
you have the following procedure:

create procedure roger
 @p1 int = 0
as
 if (@p1 >= 0) select * from systypes;
 return(@p1)

You call it using $dbh->prepare("{call roger(?)}"), bind the parameter with
$sth->bind_param(-1) and then execute it. Afterwards you test
$sth->{NUM_OF_FIELDS} and get 20 when you think you should get 0.

The basic problems with this are:

[1] your procedure has a variable result i.e. if p1 >= 0 you produce a
    result-set and if p1 < 0 you do not. This is a bad idea.

[2] because you are using a bound parameter and the driver does not know what
    the value of the bound parameter is until execute is called the driver
    cannot possibly know how to respond to SQLNumResultCols/SQLDescribeCol
    properly i.e. the answer to SQLNumResultCols (before execute is called)
    is either 0 or 20 and SQLNumResultCols can only return 1 number.

When you add this to the way the SQL Server ODBC driver works you are stuck.
The reason is demonstrated by the C code below which can be built in one of 3
ways:

[1] no defines on cc line calls 
    SQLFreeStmt(SQL_RESET_PARAMS)
    SQLBindParam(-1)
    SQLExecDirect("{call fred(?)}")
    SQLNumResultCols 
      returns the correct answer - 0.

[2] add -DPREPARE_EXECUTE to the cc line and the code calls
    SQLPrepare("{call fred(?)}")
    SQLFreeStmt(SQL_RESET_PARAMS)
    SQLBindParam(-1)
    SQLExecute
    SQLNumResultCols
      returns the correct answer - 0

[3] add -DNUMRESULTCOLS and -DPREPARE_EXECUTE to the cc line and the code calls
    SQLPrepare("{call fred(?)}")
    SQLFreeStmt(SQL_RESET_PARAMS)
    SQLBindParam(-1)
    SQLNumResultCols
      returns the wrong answer - 20
    SQLExecute
    SQLNumResultCols
      returns the wrong answer - 20

3 could never work because ODBC says the bound parameter is NOT valid until
execute is called. As a result, when you ask the ODBC driver for the number of
resultant columns before executing, the answer (in your case) is 0 or 20.
Arguably, the SQL Server ODBC driver is broken here since because it knows it
cannot use your parameter yet it does a "set fmtonly on exec roger set fmtonly
off" and because your procedure defaults to p1=0 if p1 is not passed, this
generates an empty result-set with 20 columns.

Why this is happening if you are running DBD::ODBC 0.45_18 on Windows I don't
know because DBD::ODBC 0.45_18 does not appear to call SQLNumResultCols between
the prepare and execute - I cannot test what you are doing on Windows as I don't
have up to date DBD::ODBC for Windows (are you sure you are using DBD::ODBC
0.45_18?).

Change your procedure so it does not have a variable result and this problem
will go away. I still see no problem actually in DBD::ODBC.

On some of your other comments:

On 16-Oct-2002 Roger Perttu wrote:

> I recall that it's not possible to get output parameters and returncode 
> until all data have been fetched (for SQL Server).

Because SQL Server runs the procedure SQL in batches but "set nocount on" can
affect this (stops the done proc msgs).

> Finally, I noticed that dbdimp.c uses a mix of space and tab for 
> indentation. It would be nice if this could be cleaned up unless it 
> messes up Jeff's source control system.

What is "unclean" about it? does your editor not handle tabs and spaces
properly?

Martin

Following code a quick hack compiled on Linux, with unixODBC driver
manager and Easysoft ODBC-ODBC Bridge to SQL Server and on Windows linked with
the MS driver manager to SQL Server. Build with PREPARE_EXECUTE defined or not
to see it working and and with PREPARE_EXECUTE and NUMRESULTCOLS defined to see
the problem.

#ifdef WIN32
#include <windows.h>
#endif
#include <stdio.h>
#include <string.h>
#include <sql.h>
#include <sqlext.h>

SQLHENV        henv = SQL_NULL_HENV;
SQLHDBC        hdbc = SQL_NULL_HDBC;
SQLHSTMT        hstmt = SQL_NULL_HSTMT;

static void extract_error(
    char *fn,
    SQLHANDLE handle,
    SQLSMALLINT type);

static void extract_error(
    char *fn,
    SQLHANDLE handle,
    SQLSMALLINT type)
{
    SQLINTEGER i = 0, native;
    char state[ 7 ];
    SQLCHAR text[256];
    SQLSMALLINT len;
    int ret;

    fprintf(stderr,
            "\n"
            "The driver reported the following diagnostics whilst running "
            "%s\n\n",
            fn);
    
    do {
        ret = SQLGetDiagRec(type, handle, ++i, state, &native, text,
                            sizeof(text), &len );
        if (SQL_SUCCEEDED(ret))
            printf( "%s:%ld:%ld:%s\n", state, i, native, text );
    }
    while( ret == SQL_SUCCESS );
}

int main()
{
    SQLRETURN retcode;
    SQLINTEGER sparm1=1, sparm2=-1;
    SQLINTEGER cbparm1=sizeof(SQLINTEGER);
    SQLSMALLINT columns;
    
    retcode = SQLAllocHandle (SQL_HANDLE_ENV, NULL, &henv);
    if (!SQL_SUCCEEDED(retcode)) {
        fprintf(stderr, "Failed to allocate env handle\n");
        exit(1);
    }
    
    retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
                            (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_INTEGER);
        
    retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
    if (!SQL_SUCCEEDED(retcode)) {
        extract_error("SQLAllochandle for dbc", henv, SQL_HANDLE_ENV);
        exit(1);
    }
    
    retcode = SQLConnect(hdbc, "test", SQL_NTS,
                         "user", SQL_NTS,
                         "pass", SQL_NTS);
    if (!SQL_SUCCEEDED(retcode)) {
        extract_error("SQLConnect", hdbc, SQL_HANDLE_DBC);
        exit(1);
    }
    retcode = SQLSetConnectOption(hdbc, SQL_ATTR_AUTOCOMMIT, 0);
    if (!SQL_SUCCEEDED(retcode)) {
        extract_error("SQLSetConnectOption", hdbc, SQL_HANDLE_DBC);
        exit(1);
    }
    
    retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
    if (!SQL_SUCCEEDED(retcode)) {
        extract_error("SQLAllocHandle for stmt", hdbc, SQL_HANDLE_DBC);
        exit(1);
    }
        
#ifdef PREPARE_EXECUTE
    retcode = SQLPrepare(hstmt, "{call roger (?)}", SQL_NTS);
    /*retcode = SQLPrepare(hstmt, "exec roger -1", SQL_NTS);*/
    
    if (!SQL_SUCCEEDED(retcode)) {
        extract_error("SQLPrepare/SQLExecute", hstmt, SQL_HANDLE_STMT);
        exit(1);
    }
#endif
# ifdef DESCRIBEPARAM
    {
        SQLSMALLINT dt;
        SQLUINTEGER ps;
        SQLSMALLINT dd;
        SQLSMALLINT n;
        
        SQLDescribeParam(hstmt, 1, &dt, &ps, &dd, &n);
    }
# endif
    SQLFreeStmt(hstmt, SQL_RESET_PARAMS);
    retcode = SQLBindParameter(hstmt,1,SQL_PARAM_INPUT,SQL_C_DEFAULT,
                               SQL_INTEGER,0,0,&sparm1,
                               sizeof(sparm1) ,&cbparm1);
    if (!SQL_SUCCEEDED(retcode)) {
        extract_error("SQLBindParameter", hstmt, SQL_HANDLE_STMT);
        exit(1);
    }
    sparm1=-1;

# ifdef NUMRESULTCOLS
    SQLNumResultCols(hstmt, &columns);
    printf("%d resultant columns\n", columns);
# endif
    retcode = SQLExecute(hstmt);
#ifndef PREPARE_EXECUTE
    retcode = SQLExecDirect(hstmt, "{call roger (?)}", SQL_NTS);
#endif
    if (!SQL_SUCCEEDED(retcode)) {
        extract_error("SQLPrepare/SQLExecute", hstmt, SQL_HANDLE_STMT);
        exit(1);
    }
    
    SQLNumResultCols(hstmt, &columns);
    printf("%d resultant columns\n", columns);
    
    SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
    SQLDisconnect(hdbc);
    SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
    SQLFreeHandle(SQL_HANDLE_ENV, henv);
    return(0);
        
}

--
Martin J. Evans
Easysoft Ltd, UK
Development

Reply via email to