[python-sybase] An Enhancement Proposal against the "another command structure has results pending" problem
Peter Hansack
hansack.p at online.de
Fri, 26 Dec 2003 13:17:07 +0100
--------------------
import Sybase
db = Sybase.connect('SYBASE','sa','',database='data',auto_commit=1)
try:
db.execute('drop table test')
except Sybase.Error:
pass
db.execute('''create table test (
key1 varchar(10) not null,
value1 int null
)
''')
--------------------
This seemingly trivial piece of code does not work as expected. It creates
Traceback (most recent call last):
File "a.py", line 10, in ?
db.execute('''create table test (
File "/usr/lib/python2.3/site-packages/Sybase.py", line 906, in
execute
fetcher.start(self.arraysize)
File "/usr/lib/python2.3/site-packages/Sybase.py", line 260, in start
status = self._cmd.ct_send()
File "/usr/lib/python2.3/site-packages/Sybase.py", line 157, in
_clientmsg_cb
raise DatabaseError(_fmt_client(msg))
Sybase.DatabaseError: Layer: 1, Origin: 1
ct_send(): user api layer: external error:
This routine cannot be called because another command structure has
results
pending. Layer: 1, Origin: 1
ct_cmd_drop(): user api layer: external error: This routine can be
called
only if the command structure is idle.
Fatal Python error: unexpected exception during garbage collection
Aborted
if the table 'test' does not exist already.
A look through the mail archive shows, that several others have been baffled
by this
unexpected "results pending" problem and the workaround - closing and
re-opening the
connection in the exception handler - is obviously a crutch.
A somewhat deeper look into the code of Sybase.py shows, that the exception
handler
catches an (irrelevant) informational message from the callback function -
thus
breaking the code of _FetchNow.start(), which would else call
_cmd.ct_results().
We can change our code to
--------------------
import Sybase
def my_clientmsg_cb(ctx, conn, msg):
print "+++ %s" % Sybase._fmt_client(msg)
def my_servermsg_cb(ctx, conn, msg):
if msg.msgnumber not in (5701, 5703):
print "+++ %s" % Sybase._fmt_server(msg)
Sybase._ctx.ct_callback(Sybase.CS_SET, Sybase.CS_CLIENTMSG_CB,
my_clientmsg_cb)
Sybase._ctx.ct_callback(Sybase.CS_SET, Sybase.CS_SERVERMSG_CB,
my_servermsg_cb)
db = Sybase.connect('SYBASE','sa','',database='data',auto_commit=1)
try:
db.execute('drop table test')
except Sybase.Error:
pass
db.execute('''create table test (
key1 varchar(10) not null,
value1 int null
)
''')
--------------------
Now the code works. I the table 'test' does not exist, we get the message
+++ Msg 3701, Level 11, State 1, Line 1
Cannot drop the table 'test', because it doesn't exist in the system
catalogs.
and then the table is created.
But violating the privacy of _ctx is not good style. The next refactoring
of Sybase.py may break the code.
Therefore my proposal:
The callback functions should be settable by an official method.
At first I considered additional parameters in connect(). But the callback
functions
should be changable without a reconnect, so something in the style of
set_property()
would be preferable.
Of course default handlers should be installed if the callback functions are
not set
explicitly. I would prefer to an exception an informational message as
default;
to react sensibly to an unexpected exception is rather difficult.
Any opinions?
- Peter