There is a problem retrieving data from cursor with two or more result sets. Let's assume we have following Transact-SQL procedure:
create procedure SampleProc
as
begin
select 1 as col1, 2 as col2
union
select 11,12
select 3 as col3, "4" as col4, 5 as col5
union
select 31,"41",51
end
and corresponding python code to get first result set:
import Sybase
db=Sybase.connect('Sybase','user','pass','')
cur = db.cursor()
cur.execute('exec SampleProc')
rs = cur.fetchall()
print rs
I expect output like "[(1, 2), (11, 12)]", but it is a bit longer: "[(1, 2), (11, 12), (3, '4', 5), (31, '41', 51)]".
I.e. cursor has returned both results instead of first one.
Patch makes some changes in class Cursor to fix it.
1. Method _start(): add private variable _empty_set to test if end of result set has been reached.
def _start(self):
self._result_list = []
self._rownum = -1
self.rowcount = -1
self.description = None
self._empty_set = False
status = self._cmd.ct_send()
if status != CS_SUCCEED:
self._raise_error(Error('ct_send'))
self._fetching = True
return self._mainloop()
2. Method _row_result(): delete call of _mainloop() and check end of result set. _mainloop() is the cause of getting all result sets instead of one.
def _row_result(self):
logical_result = []
if self._empty_set:
count = 0
else:
count = self._fetch_rows(self._bufs, logical_result)
self._rownum += count
self._result_list += logical_result
self._empty_set = count == 0
3. Method fetchmany(): check if there is more rows in current result set.
def fetchmany(self, num = -1):
'''DB-API Cursor.fetchmany()
'''
self._lock()
try:
if self._rownum == -1:
self._raise_error(Error('No result set'))
if num < 0:
num = self.arraysize
while num > self._rownum and self._fetching and not self._empty_set:
self._row_result()
res = self._result_list[0:num]
del self._result_list[0:num]
self._rownum -= num
return res
finally:
self._unlock()
4. Method fetchall(): the very same modification.
def fetchall(self):
'''DB-API Cursor.fetchall()
'''
self._lock()
try:
if self._rownum == -1:
self._raise_error(Error('No result set'))
while self._fetching and not self._empty_set:
self._row_result()
res = self._result_list
self._result_list = []
self._rownum = 0
return res
finally:
self._unlock()
5. Method nextset(): reset _empty_set and return True if _mainloop get empty set. Query may return empty result set, in that case variable _result_list would contain empty list. Though result set should be returned to client.
def nextset(self):
'''DB-API Cursor.nextset()
'''
self._lock()
try:
if not self._fetching:
return None
status = self._cmd.ct_cancel(CS_CANCEL_CURRENT)
self._empty_set = False
self._result_list = []
self._rownum = -1
self.rowcount = -1
self._mainloop()
if self._result_list or self._empty_set:
return True
return None
finally:
self._unlock()
With this changes we would get all result sets and it's descriptions separately. For example, following code:
import Sybase
db=Sybase.connect('Sybase','user','pass','')
cur = db.cursor()
cur.execute('exec SampleProc')
rs = cur.fetchall()
print rs
while cur.nextset():
rs = cur.fetchall()
print rs
will return:
[(1, 2), (11, 12)]
[(3, '4', 5), (31, '41', 51)]
Also i've added property status_result to store processed return status. It can be accessed after fetching all of result sets.