[python-sybase] Re: Question about Sybase Module 0.36 Output Parameter Handling

Dave Cole djc at object-craft.com.au
Thu, 17 Mar 2005 12:15:44 +1100


Ooops - forgot to respond to the list as well...

Bradley Feldman wrote:

 > Hi Dave:
 >
 > Thanks so much for providing your great Sybase module for Python!  I've
 > installed it and it works great so far.


Thanks for the response.

 > I have a question about the handling of Output Parms which is a new 
feature
 > of 0.36.  I understand how to pass the output parm syntax to the database
 > within the callproc as given in the release notes, but how to I see the
 > value passed back?  This seems simple, but there is no example of 
evaluating
 > the resulting parameter and I can't seem to figure it out.


The problem is due to a shortcoming in the Sybase CT library.  There is 
no way to ask the library whether parameters to a query are input or 
output.  If you get it wrong then the query fails.  So in the absence of 
a way to automatically determine the direction of an argument, the user 
must tell the module.

The OUTPUT() function uses the passed value to construct a buffer that 
will accept a value of the same type from the Sybase CT library in 
response to a query.

 > For example, you give:
 >
 > C.callproc ('testproc', {'@parm1': 'value1', '@outputparm':
 > Sybase.OUTPUT(1)})
 >
 > If I print the callproc object after it executes I get back something 
like:
 > {'@parm1':'value1','@outputparm': <DataBufType object at 0x2a394999>}


This is showing you the parameters that will be passed to the CT 
library.  The results will not be available until you try to fetch 
results from the cursor.

 > So how to I examine the contents of the DataBufType object, using 
python db
 > api syntax?


The result should come back as a row via the fetchone() method on the 
cursor.

 > Finally, what does the "1" in Sybase.OUTPUT(1) mean?   A buffer size of 1
 > byte?  A value of 1 contained in one byte?  Can I simply pass a variable
 > reference instead, such as Sybase.OUTPUT(thisVar)?


This is not explained in the documentation.  It probably should be I 
suppose.

Communication with the Sybase server is via the CT library.  The CT 
library uses two objects for passing values to, and accepting values 
from the server; a CS_DATAFMT structure, and a data buffer.  The 
CS_DATAFMT structure describes (among other things) the type, size, and 
direction of a related data buffer.  The data buffer is simply a pointer 
to an area of memory that matches the description in the CS_DATAFMT.

For passing values to Sybase all you need to do is place the value in a 
data buffer and describe it using a CS_DATAFMT.  You can see this in 
operation in the Cursor.callproc() method.  It takes plain values and 
places them in a data buffer via DataBuf() constructor.

      buf = DataBuf(value)

The DataBuf object contains an embedded CS_DATAFMT structure that is 
initialised using the type and size of the Python object that was passed 
to the constructor.  It also contains an allocated area of memory into 
which the value passed to the constructor is copied.

      buf.name = name

After constructing The Cursor.callproc() method then assigns the 
specified parameter name to the .name member of the DataBuf.  This is 
sufficient to create the CS_DATAFMT and data buffer to pass a parameter 
to Sybase.

      cmd.ct_param(buf)


As mentioned above, Sybase does not provide any mechanism to query the 
CT Library about whether a parameter to a stored procedure is for input 
or output.  If you get it wrong then the query will fail.  To get around 
this, the OUTPUT() function establishes a DataBuf object in the same way 
as the Cursor.callproc() method, but then in addition, sets the .status 
attribute of the embedded CS_DATAFMT to CS_RETURN.  This tells Sybase 
that the buffer is intended to receive a return value from the query 
rather than pass a value to the query.


For column values returned by plain queries the Sybase module asks the 
Sybase CT Library the format of each column and then constructs a 
DataBuf to receive that value.  The _row_bind() function in the Sybase 
module shows this:

     cmd.ct_res_info(CS_NUMDATA)

Asks CT how many columns are in the query result.

     cmd.ct_describe(i + 1)

Asks CT to describe one of the columns.  The second return value is a 
CS_DATAFMT object.

     cmd.ct_bind(i + 1, fmt)

This constructs a DataBuf from the CS_DATAFMT and tells CT to bind the 
specified column to the area of memory in the DataBuf.  The second 
return value is the constructed DataBuf object.


So after all of that, if you want to be a bit more direct in 
constructing output parameter buffers to stored procedures you do use 
something like this:

fmt = Sybase.CS_DATAFMT()
fmt.datatype = Sybase.CS_VARCHAR_TYPE
fmt.maxlength = 1024
fmt.count = 1
buf = Sybase.DataBuf(fmt)
buf.status = Sybase.CS_RETURN

The buf can then be passed like this:

  C.callproc ('testproc', {'@parm1': 'value1', '@outputparm': buf})

As explained above, the return parameter should be available as a normal 
row returned using one of the Cursor.fetch*() methods.

Note that when you fiddle with DataBuf objects you must hold a reference 
to that buffer until the CT library has finished using the memory in the 
buffer.  This is transparently done for you by the code in Sybase.py.

If you start programming directly to the sybasect extension module then 
you will need to manage your own DataBuf references.

 > Thanks so much for your help, in advance.


No problem.  Is a distraction from the work I am supposed to be doing :-) .

- Dave

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