Ok, I've downloaded the latest DBI and DBD::ODBC for Windows (from
ftp://ftp.esoftmatic.com/outgoing/DBI) and installed that. I now see the problem
you are seeing. If my test C code in previous email is changed to add a call to
SQLMoreResults() after the SQLExecute and then SQLNumResultCols is called you
get 20 back (like when you call SQLNumResultCols after SQLPrepare and before
SQLExecute).
However, I then spotted perl -e "use DBD::ODBC;print $DBD::ODBC::VERSION;"
outputs 0.45_17 and I believe from previous posts Jeff made some change related
to this in _18.
I notice the main difference between 17 and 18 is:
> if (rc == SQL_SUCCESS_WITH_INFO) {
> AllODBCErrors(imp_sth->henv, imp_sth->hdbc, imp_sth->hstmt, DBIc_DEBUG
IV(imp_sth) >= 8, DBIc_LOGPIO(imp_dbh));
1883a1887,1891
> /* flag that we've done the describe to avoid a problem
> * where calling describe after execute returned no rows
> * caused SQLServer to provide a description of a query
> * that didn't quite apply. */
> imp_sth->done_desc = 1;
where did you get a ppm which includes this code from (i.e version _18)?
Martin
On 16-Oct-2002 Roger Perttu wrote:
> [EMAIL PROTECTED] wrote:
>
>>I have snipped ALL previous discussion on this as I am finding it almost
>>impossible to see the wood from the trees now.
>>
> Thanks, this makes it much more understandable.
>
>>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)
>>
> Yes, but it's just a test case to reproduce the bug
>
>>
>>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.
>>
>>
> Actually it's not necessary to use $sth->{NUM_OF_FIELDS}. the bug shows
> it self when you execute.
>
>>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.
>>
> Might be bad, but it's valid code and it works in JScript + ODBC
>
>>
>>[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.
>>
> It's a very bad idea to call SQLNumResultCols before execute and I don't
> do anything to trigger this. (I also don't think it's happening because
> I can see in Profiler that my procedure is called first with my
> parameter and then a second time with the wrong one. In Jeff's code
> SQLNumResultCols is called after SQLExecute but he is also doing some
> other stuff that might trigger the use of "SET FMTONLY ON".
>
> This is run on the server:
> set implicit_transactions on
> testPrc -1
> SET FMTONLY ON EXEC testPrc 0 SET FMTONLY OFF
> IF @@TRANCOUNT > 0 ROLLBACK TRAN
>
> Notice that in this situation it's totally stupid (must be a MS bug
> then) to re execute the procedure. Using "SET FMTONLY ON" is like using
> "where 1=2" in a select statement. It won't return more information than
> the "true" call thats just been executed.
>
>>
>>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
>>
>>
> This [2] is exactly what I would expect DBD-ODBC to use to execute my code.
>
>>[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.
>>
> It's worse. It doesn't use the default parameter (I've checked this) but
> a hardcoded zero.
>
>>
>>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?).
>>
> Yes, I'm using the latest ones from Jeff's site
> ftp.esoftmatic.com/outgoing/DBI And I'm doing this because I've found
> another bug in an earlier release of 0.45
>
>>
>>Change your procedure so it does not have a variable result and this problem
>>will go away.
>>
> Can't do that. It's part of a object-relational mapping system. One
> procedure handle one object and one parameter indicates if it's a
> create, update or get.
>
>>I still see no problem actually in DBD::ODBC.
>>
> I do :-)
> 0.45 is still beta or something. I know I found a very special condition
> but it would be nice if it was fixed. Now that I know more I'm not
> saying it's a true bug in DBI, but rather a lack of a workaround for a
> problem in MS code.
>
>>
>>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?
>>
> It sure does but the source contains a mixture of tabs and spaces. In
> some places it's indented using space and in others tab. My editor is
> set to a tab-length of four or three. Just trying to help.
>
> Once again, thanks Martin.
> I wish I had your knowledge of ODBC, a compiler and lots of spare time.
> Then I might be able to debug and patch Jeff's code.
>
>>
>>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
>>
>>
>>
>>
--
Martin J. Evans
Easysoft Ltd, UK
Development