[python-sybase] Re: Your test program (sp_test.py) output

Dave Cole djc at object-craft.com.au
Fri, 18 Mar 2005 17:37:38 +1100


I keep forgetting to send to the mailing list :-(.

Bradley Feldman wrote:
> Dave:
> 
> If I run your test program (slightly modded for my environment) on
> FreeTDS/MS SQL, here's what I get as output:
> 
> {'@num': 12345, '@result': <DataBufType object at 0x2a95596770>}
> [54321, <DataBufType object at 0x2a95596940>]
> 
> Attached is the debug trace.  Let me know what you discover.

Extremely odd.

The parts of the log to do with calling the stored procedure are:

Cursor.callproc
ct_cmd_alloc(conn0, &cmd) -> CS_SUCCEED, cmd3

Allocated a command structure to send a command to the server.

ct_command(cmd3, CS_RPC_CMD, "sp_test_output", CS_NULLTERM, CS_UNUSED)
-> CS_SUCCEED

Start constructing the command; use an RPC command telling the server to
  execute a stored procedure.

ct_param(cmd3, &databuf0->fmt=[name:"@result" type:CS_INT_TYPE
status:CS_RETURN format:CS_FMT_UNUSED count:1 maxlength:4 scale:0
precision:0], databuf0->buff, 4, 0) -> CS_SUCCEED

Tell the command that we want to bind a buffer to receive a return
parameter (status == CS_RETURN) called @result.

ct_param(cmd3, &databuf1->fmt=[name:"@num" type:CS_INT_TYPE
status:CS_INPUTVALUE format:CS_FMT_UNUSED count:1 maxlength:4 scale:0
precision:0], databuf1->buff, 4, 0) -> CS_SUCCEED

Tell the command that we have an input parameter called '@num'.  The
value is already in the associated buffer.

ct_send(cmd3) -> CS_SUCCEED

Send the command to the server.

ct_results(cmd3, &result) -> CS_SUCCEED, CS_STATUS_RESULT

Server says that there is some status returned.  We fetch this and
discard it.

ct_res_info(cmd3, CS_NUMDATA, &value, CS_UNUSED, NULL) -> CS_SUCCEED, 1
ct_describe(cmd3, 1, &fmt) -> CS_SUCCEED, datafmt0=[name:""
type:CS_INT_TYPE status:CS_FALSE format:CS_FMT_UNUSED count:1
maxlength:4 scale:0 precision:0]
ct_bind(cmd3, 1, &datafmt0->fmt=[name:"" type:CS_INT_TYPE
status:CS_FALSE format:CS_FMT_UNUSED count:1 maxlength:4 scale:0
precision:0], databuf2->buff,
databuf2->copied, databuf2->indicator) -> CS_SUCCEED, databuf2
_fetch_rows
ct_fetch(cmd3, CS_UNUSED, CS_UNUSED, CS_UNUSED, &rows_read) -> CS_SUCCEED, 1
_fetch_rows
ct_fetch(cmd3, CS_UNUSED, CS_UNUSED, CS_UNUSED, &rows_read) ->
CS_END_DATA, 0

Status result has been fetched.  The next logs are where we get the
parameter results.

ct_results(cmd3, &result) -> CS_SUCCEED, CS_PARAM_RESULT

Server tells us to begin fetching the parameter result set.

ct_res_info(cmd3, CS_NUMDATA, &value, CS_UNUSED, NULL) -> CS_SUCCEED, 1

There is one return parameter.

ct_describe(cmd3, 1, &fmt) -> CS_SUCCEED, datafmt1=[name:"@result"
type:CS_INT_TYPE status:CS_FALSE format:CS_FMT_UNUSED count:1
maxlength:4 scale:0 precision:0]

It is called '@result' (which is what we expected).  The *really*
strange thing here is that the status is not CS_RETURN!!!  When I run it
here the status *is* CS_RETURN.  The upshot of this is that the result
is ignored by the code in Sybase.py

I think that a fix for this that should work all of the time is to
change _FetchNowParams._param_result():

     def _param_result(self):
         bufs = _row_bind(self._cmd, 1)
         while 1:
             status, rows_read = self._cmd.ct_fetch()
             if status == CS_SUCCEED:
                 pass
             elif status == CS_END_DATA:
                 break
             elif status in (CS_ROW_FAIL, CS_FAIL, CS_CANCELED):
                 self._raise_error(Error, 'ct_fetch')
             pos = -1
             for buf in bufs:
                 if buf.status & CS_RETURN:
                     if type(self._params) is type({}):
                         self._params[buf.name] = _column_value(buf[0])
                     else:
                         while 1:
                             pos += 1
                             param = self._params[pos]
                             if (type(param) is DataBufType
                                 and param.status & CS_RETURN):
                                 break
                         self._params[pos] = _column_value(buf[0])

If you just remove the test for buf.status & CS_RETURN it should do the
right thing.

     def _param_result(self):
         bufs = _row_bind(self._cmd, 1)
         while 1:
             status, rows_read = self._cmd.ct_fetch()
             if status == CS_SUCCEED:
                 pass
             elif status == CS_END_DATA:
                 break
             elif status in (CS_ROW_FAIL, CS_FAIL, CS_CANCELED):
                 self._raise_error(Error, 'ct_fetch')
             pos = -1
             for buf in bufs:
                 if type(self._params) is type({}):
                     self._params[buf.name] = _column_value(buf[0])
                 else:
                     while 1:
                         pos += 1
                         param = self._params[pos]
                         if (type(param) is DataBufType
                             and param.status & CS_RETURN):
                             break
                     self._params[pos] = _column_value(buf[0])

Let me know how it goes.

- Dave

-- 
http://www.object-craft.com.au