RE: how to get return value of update/delete

"Nidositko, James" <[email protected]> Wed, 26 Oct 2005 11:39:32 -0400
Newsgroups gmane.comp.python.sybase
Message-ID <D511E431A087CE46983D5B54404CCCBD17BB3B45@gsnmp20es.firmwide.corp.gs.com>
 
Mike, 
I had some discussions earlier this year with Dave about fixing the rowcount
stuff in the recent versions of the Sybase module (0.36 and 0.37).  I was
under the impression that these fixes had been incorporated and released,
but I did not check that before responding to your question.  Sorry for the
confusion.
 
The last official version that I know of where rowcount() worked was
0.36-pre2.  There were extensive changes to the structure of the code
between that version and the actual 0.36 release.  The rowcount problems
were introduced by some of those changes and carried forward into 0.37.
 
I'm assuming that you're using 0.37.
 
The attached copy of Sybase.py fixes the rowcount issues.  It is based on
the version released with 0.37.
 
Dave, any chance of getting an official release with rowcount() working?
 
-Jim
 


  _____  

From: mike [mailto:[email protected]] 
Sent: Wednesday, October 26, 2005 10:56 AM
To: Nidositko, James
Cc: [email protected]
Subject: Re: [python-sybase] how to get return value of update/delete


hi thanks for the reply.
I went to try the codes out

def exec_updtstmt(Svr,Login,Pass,Db,SQL):
        ''' Connects to database specified, exec the SQL and returns
value'''
        try:
                db = Sybase.connect(Svr, Login,Pass ,Db)
                c = db.cursor()               
                try:
                        c.execute(SQL)
                        print "Cursor rowcount after update (no rows) is:
%d" % c.rowcount
                        db.commit()
                        db.close()                    
                except Sybase.DatabaseError:
                        return 9
        except:
                return 2

stmt = '''
update table
set col_1 =  1
where login = "user"
'''

exec_updtstmt(Svr,Login,Pass,DbInt,stmt)

what i got is "Cursor rowcount after update (no rows) is: -1" and when i
checked the table
it is updated. Although the update works, i would wish to get that return to
indicate to the user whether success or failure
.
thanks for any further help





On 10/26/05, Nidositko, James <[email protected]
<mailto:[email protected]> > wrote: 


Mike,
There is no inherent concept of a "return value" from an executed SQL
statement.  You can produce such a result by encapsulating your SQL
statements within stored procedures by detecting the rowcount and setting 
the return value in response, but there isn't really any need for you to do
that.

1) It sounds like the error case that you're trying to describe below should
be detected by getting a rowcount of zero from the update. 

2)  Rowcount does work.  Here is some sample code that will show rowcount
working in various situations.

  c=dbc.cursor()
  print "Cursor rowcount is initialized to: %d" % c.rowcount
  c.execute ("create table __test_rowcount (col1 varchar(10) NULL)")
  print "Cursor rowcount after create table is: %d" % c.rowcount

  c.execute("insert __test_rowcount (col1) values ('asdf')") 
  print "Cursor rowcount after insert 1 is: %d" % c.rowcount

  c.execute("insert __test_rowcount (col1) values ('asdf')")
  print "Cursor rowcount after insert 2 is: %d" % c.rowcount 

  c.execute("update __test_rowcount set col1 = 'qwer' where col1 = 'asdf'")
  print "Cursor rowcount after update is: %d" % c.rowcount

  c.execute("update __test_rowcount set col1 = 'qwer' where col1 = 'not 
there'")
  print "Cursor rowcount after update (no rows) is: %d" % c.rowcount

  c.execute("select count(*) from  __test_rowcount")
  print "Cursor rowcount after execute for select is: %d" % c.rowcount
  c.fetchall()
  print "Cursor rowcount after fetch for select is: %d" % c.rowcount

  c.execute("delete __test_rowcount where col1 = 'qwer'")
  print "Cursor rowcount after delete is: %d" % c.rowcount

The only unusual case is select.  Then, the rowcount is not set until the
fetch is performed.

-Jim

-----Original Message-----
From: [email protected]
<mailto:[email protected]> 
[mailto:[email protected]
<mailto:[email protected]> ] On Behalf Of
[email protected]
<mailto:[email protected]> 
Sent: Tuesday, October 25, 2005 10:00 PM
To: [email protected]
<mailto:[email protected]> 
Subject: Python-sybase Digest, Vol 5, Issue 3 


Today's Topics:

   1. how to get return value of update/delete statement (michael lee)


----------------------------------------------------------------------

Message: 1
Date: Tue, 25 Oct 2005 18:30:07 +0800 
From: michael lee <[email protected] <mailto:[email protected]> >
Subject: [python-sybase] how to get return value of update/delete
        statement
To: Python Sybase <  <mailto:[email protected]>
[email protected]>
Message-ID:
        <[email protected]
<mailto:[email protected]> >
Content-Type: text/plain; charset="iso-8859-1"

hi
how can i successfully get the return of an update or delete statement using
the Sybase module?
i am doing a CGI script whereby if a user keys in the wrong name, i would 
want to catch that error  eg if key in wrong username, my CGI script will
call Sybase to execute < update table set this = a-value where name =
'wronguser' > and then return a value indicating "not found" 
but i am not able to figure out how to catch this return value. Any advise?
....
cur.execute(update_statement)
....
my table is being updated though when i tried a valid user. I tried
rowcount but also not working. 
thanks
-------------- next part --------------
An HTML attachment was scrubbed...
URL:
http://www.object-craft.com.au/pipermail/python-sybase/attachments/20051025/
<http://www.object-craft.com.au/pipermail/python-sybase/attachments/20051025
/> 
0c35109c/attachment-0001.html

------------------------------

_______________________________________________
Python-sybase mailing list
[email protected]
<mailto:[email protected]> 
https://www.object-craft.com.au/cgi-bin/mailman/listinfo/python-sybase
<https://www.object-craft.com.au/cgi-bin/mailman/listinfo/python-sybase> 


End of Python-sybase Digest, Vol 5, Issue 3
*******************************************

_______________________________________________
Python-sybase mailing list
[email protected]
https://www.object-craft.com.au/cgi-bin/mailman/listinfo/python-sybase
Sybase.py (application/octet-stream, 31 KB)
#
# Copyright 2001 by Object Craft P/L, Melbourne, Australia.
#
# LICENCE - see LICENCE file distributed with this software for details.
#
try:
    import DateTime
    use_datetime = 1
except ImportError:
    try:
        import mx.DateTime
        DateTime = mx.DateTime
        use_datetime = 1
    except ImportError:
        use_datetime = 0
import sys
import time
import string
import threading
from sybasect import *
from sybasect import __have_freetds__


set_debug(sys.stderr)


__version__ = '0.37'


# DB-API values
apilevel = '2.0'                        # DB API level supported


threadsafety = 2                        # Threads may share the module
                                        # and connections.


paramstyle = 'named'                    # Named style, 
                                        # e.g. '...WHERE name=@name'

# DB-API exceptions
#
# StandardError
# |__Warning
# |__Error
#    |__InterfaceError
#    |__DatabaseError
#       |__DataError
#       |__OperationalError
#       |__IntegrityError
#       |__InternalError
#       |__ProgrammingError
#       |__NotSupportedError

class Warning(StandardError):
    pass


class Error(StandardError):

    def append(self, other):
        self.args = (self.args[0] + other.args[0],)


class InterfaceError(Error):
    pass


class DatabaseError(Error):
    pass

class DataError(DatabaseError):
    pass


class OperationalError(DatabaseError):
    pass


class IntegrityError(DatabaseError):
    pass


class InternalError(DatabaseError):
    pass


class ProgrammingError(DatabaseError):
    pass


class NotSupportedError(DatabaseError):
    pass


class DBAPITypeObject:

    def __init__(self, *values):
	self.values = values

    def __cmp__(self, other):
	if other in self.values:
	    return 0
	if other < self.values:
	    return 1
	else:
	    return -1


STRING = DBAPITypeObject(CS_LONGCHAR_TYPE, CS_VARCHAR_TYPE,
                         CS_TEXT_TYPE, CS_CHAR_TYPE)
BINARY = DBAPITypeObject(CS_IMAGE_TYPE, CS_LONGBINARY_TYPE,
                         CS_VARBINARY_TYPE, CS_BINARY_TYPE)
NUMBER = DBAPITypeObject(CS_BIT_TYPE, CS_TINYINT_TYPE,
                         CS_SMALLINT_TYPE, CS_INT_TYPE,
                         CS_MONEY_TYPE, CS_REAL_TYPE, CS_FLOAT_TYPE,
                         CS_DECIMAL_TYPE, CS_NUMERIC_TYPE)
DATETIME = DBAPITypeObject(CS_DATETIME4_TYPE, CS_DATETIME_TYPE)
ROWID = DBAPITypeObject(CS_DECIMAL_TYPE, CS_NUMERIC_TYPE)


def OUTPUT(value):
    buf = DataBuf(value)
    buf.status = CS_RETURN
    return buf


def Date(year, month, day):
    return datetime('%s-%s-%s' % (year, month, day))


def Time(hour, minute, second):
    return datetime('%d:%d:%d' % (hour, minute, second))


def Timestamp(year, month, day, hour, minute, second):
    return datetime('%s-%s-%s %d:%d:%d' % (year, month, day,
                                           hour, minute, second))


def DateFromTicks(ticks):
    return apply(Date, time.localtime(ticks)[:3])


def TimeFromTicks(ticks):
    return apply(Time, time.localtime(ticks)[3:6])


def TimestampFromTicks(ticks):
    return apply(Timestamp, time.localtime(ticks)[:6])


def Binary(str):
    return str


_output_hooks = {}


def _fmt_server(msg):
    parts = []
    for label, name in (('Msg', 'msgnumber'),
                        ('Level', 'severity'),
                        ('State', 'state'),
                        ('Procedure', 'proc'),
                        ('Line', 'line')):
        value = getattr(msg, name)
        if value:
            parts.append('%s %s' % (label, value))
    text = '%s\n%s' % (string.join(parts, ', '), msg.text)
    _ctx.debug_msg(text)
    return text


def _fmt_client(msg):
    text = 'Layer: %s, Origin: %s\n' \
           '%s' % (CS_LAYER(msg.msgnumber), CS_ORIGIN(msg.msgnumber),
                   msg.msgstring)
    _ctx.debug_msg(text)
    return text


def _cslib_cb(ctx, msg):
    raise Error(_fmt_client(msg))


def _clientmsg_cb(ctx, conn, msg):
    raise DatabaseError(_fmt_client(msg))


def _servermsg_cb(ctx, conn, msg):
    mn = msg.msgnumber
    if mn in (0, 5701, 5703, 5704) or ((mn >= 6200) and (mn < 6300)):
        # Non-errors:
        #    0      PRINT
        # 5701      Changed db context
        # 5703      Changed language
        # 5704      Changed character set (Sybase)
        # 6200-6299 SHOWPLAN output (Sybase)
        hook = _output_hooks.get(conn)
        if hook:
            hook(conn, msg)
    else:
        raise DatabaseError(_fmt_server(msg))


def _row_bind(cmd, count=1):
    '''Bind buffers for count rows of column data.
    '''
    status, num_cols = cmd.ct_res_info(CS_NUMDATA)
    if status != CS_SUCCEED:
        raise Error('ct_res_info')
    bufs = []
    for i in range(num_cols):
        status, fmt = cmd.ct_describe(i + 1)
        if status != CS_SUCCEED:
            raise Error('ct_describe')
        fmt.count = count
        if fmt.datatype == CS_VARBINARY_TYPE:
            fmt.datatype = CS_BINARY_TYPE
        if fmt.maxlength > 65536:
            fmt.maxlength = 65536
        status, buf = cmd.ct_bind(i + 1, fmt)
        if status != CS_SUCCEED:
            raise Error('ct_bind')
        bufs.append(buf)
    return bufs


def _column_value(val):
    if use_datetime and type(val) is DateTimeType:
        return DateTime.DateTime(val.year, val.month + 1, val.day,
                                 val.hour, val.minute,
                                 val.second + val.msecond / 1000.0)
    else:
        return val


def _extract_row(bufs, n):
    '''Extract a row tuple from buffers.
    '''
    row = [None] * len(bufs)
    col = 0
    for buf in bufs:
        row[col] = _column_value(buf[n])
        col = col + 1
    return tuple(row)


def _fetch_rows(cmd, bufs, rows):
    '''Fetch rows into bufs.

    When bound to buffers for a single row, return a row tuple.
    When bound to multiple row buffers, return a list of row
    tuples.
    '''
    _ctx.debug_msg('_fetch_rows\n')
    status, rows_read = cmd.ct_fetch()
    if status == CS_SUCCEED:
        pass
    elif status == CS_END_DATA:
        return 0
    elif status in (CS_ROW_FAIL, CS_FAIL, CS_CANCELED):
        raise Error('ct_fetch')
    if bufs[0].count > 1:
        for i in xrange(rows_read):
            rows.append(_extract_row(bufs, i))
        return rows_read
    else:
        rows.append(_extract_row(bufs, 0))
        return 1


def _bufs_description(bufs):
    desc = []
    for buf in bufs:
        desc.append((buf.name, buf.datatype, 0,
                     buf.maxlength, buf.precision, buf.scale,
                     buf.status & CS_CANBENULL))
    return desc


# Setup global library context
status, _ctx = cs_ctx_alloc()
if status != CS_SUCCEED:
    raise InternalError('cs_ctx_alloc failed')
set_global_ctx(_ctx)
if _ctx.ct_init() != CS_SUCCEED:
    raise Error('ct_init')
_ctx.cs_config(CS_SET, CS_MESSAGE_CB, _cslib_cb)
_ctx.ct_callback(CS_SET, CS_CLIENTMSG_CB, _clientmsg_cb)
_ctx.ct_callback(CS_SET, CS_SERVERMSG_CB, _servermsg_cb)
if _ctx.ct_config(CS_SET, CS_NETIO, CS_SYNC_IO) != CS_SUCCEED:
    raise Error('ct_config')


class _FetchNow:

    def __init__(self, owner):
        self._owner = owner
        self._conn = owner._conn
        self._result_list = []
        self._description_list = []
        self._rownum = 0
        status, self._cmd = self._conn.ct_cmd_alloc()
        if status != CS_SUCCEED:
            self._raise_error(Error, 'ct_cmd_alloc')

    def start(self, arraysize):
        self._arraysize = arraysize
        status = self._cmd.ct_send()
        if status != CS_SUCCEED:
            self._raise_error(Error, 'ct_send')
        while 1:
            try:
                status, result = self._cmd.ct_results()
            except Exception, e:
                self._conn.ct_cancel(CS_CANCEL_ALL)
                raise e
            if status == CS_END_RESULTS:
                if self._description_list:
                    return self._description_list[0]
                return None
            elif status != CS_SUCCEED:
                self._raise_error(Error, 'ct_results')
            if result == CS_ROW_RESULT:
                self._row_result()
            elif result == CS_STATUS_RESULT:
                self._status_result()
            elif result == CS_PARAM_RESULT:
                self._param_result()
            elif result == CS_COMPUTE_RESULT:
                self._compute_result()
            elif result in (CS_CMD_DONE, CS_CMD_SUCCEED):
                status, self.rowcount = self._cmd.ct_res_info(CS_ROW_COUNT)
                if status != CS_SUCCEED:
                    self._raise_error(Error, 'ct_res_info')
            elif result not in (CS_CMD_DONE, CS_CMD_SUCCEED):
                self._raise_error(Error, 'ct_results')

    def _is_idle(self):
        return 1

    def _raise_error(self, exc, text):
        self._conn.ct_cancel(CS_CANCEL_ALL)
        raise exc(text)

    def _read_result(self):
        bufs = _row_bind(self._cmd, self._arraysize)
        self._description_list.append(_bufs_description(bufs))
        logical_result = []
        while _fetch_rows(self._cmd, bufs, logical_result):
            pass
        self._result_list.append(logical_result)

    _row_result = _read_result
    _status_result = _read_result
    _param_result = _read_result
    _compute_result = _read_result

    def result_list(self):
        return self._result_list

    def fetchone(self):
        rownum = self._rownum
        if self._result_list and rownum < len(self._result_list[0]):
            self._rownum = self._rownum + 1
            return self._result_list[0][rownum]

    def fetchmany(self, num):
        if self._result_list:
            rownum = self._rownum
            rows = self._result_list[0][rownum: rownum + num]
            self._rownum = rownum + num
            return rows
        return []

    def fetchall(self):
        if self._result_list:
            rows = self._result_list[0][self._rownum:]
            self._rownum = len(self._result_list[0])
            return rows
        return []

    def nextset(self):
        if self._result_list:
            del self._result_list[0]
            del self._description_list[0]
            self._rownum = 0
        if self._result_list:
            return self._description_list[0]
        return None


class _FetchNowParams(_FetchNow):

    def start(self, arraysize, params):
        self._params = params
        return _FetchNow.start(self, arraysize)

    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])

    def _status_result(self):
        bufs = _row_bind(self._cmd, 1)
        status_result = []
        while _fetch_rows(self._cmd, bufs, status_result):
            pass
        if len(status_result) == 1:
            row = status_result[0]
            if len(row) == 1:
                self.return_status = row[0]


_LAZY_IDLE = 0                          # prepared command
_LAZY_FETCHING = 1                      # fetching rows
_LAZY_END_RESULT = 2                    # fetching rows
_LAZY_CLOSED = 3                        # cursor closed
_state_names = { _LAZY_IDLE: '_LAZY_IDLE',
                 _LAZY_FETCHING: '_LAZY_FETCHING',
                 _LAZY_END_RESULT: '_LAZY_END_RESULT',
                 _LAZY_CLOSED: '_LAZY_CLOSED' }


class _FetchLazy:

    def __init__(self, owner):
        self._owner = owner
        self._conn = owner._conn
        self._cmd = None
        self._lock_count = 0
        self._state = _LAZY_IDLE
        self.rowcount = -1
        self._open()

    def _set_state(self, state):
        _ctx.debug_msg('_set_state: %s\n' % _state_names[state])
        self._state = state

    def _lock(self):
        self._lock_count = self._lock_count + 1
        _ctx.debug_msg('_lock: count -> %d\n' % self._lock_count)
        self._owner._lock()

    def _unlock(self):
        self._lock_count = self._lock_count - 1
        _ctx.debug_msg('_unlock: count -> %d\n' % self._lock_count)
        self._owner._unlock()

    def _open(self):
        self._lock()
        try:
            status, self._cmd = self._owner._conn.ct_cmd_alloc()
            if status != CS_SUCCEED:
                self._raise_error(Error, 'ct_cmd_alloc')
            self._lock()
            self._set_state(_LAZY_IDLE)
        finally:
            self._unlock()

    def _close(self):
        if self._state == _LAZY_CLOSED:
            return
        self._lock()
        try:
            if self._state != _LAZY_IDLE:
                status = self._cmd.ct_cancel(CS_CANCEL_ALL)
                if status == CS_SUCCEED:
                    self._unlock()
            self._cmd = None
            self._set_state(_LAZY_CLOSED)
        finally:
            self._unlock()

    def __del__(self):
        if self._state not in (_LAZY_IDLE, _LAZY_CLOSED):
            if self._owner._is_connected:
                self._owner._conn.ct_cancel(CS_CANCEL_ALL)
        if self._lock_count:
            # By the time we get called the threading module might
            # have killed the thread the lock was created in ---
            # oops.
            count, owner = self._owner._connlock._release_save()
            self._owner._connlock._acquire_restore((count, threading.currentThread()))
            while self._lock_count:
                self._unlock()

    def _raise_error(self, exc, text):
        if self._state not in (_LAZY_IDLE, _LAZY_CLOSED):
            if self._owner._conn.ct_cancel(CS_CANCEL_ALL) == CS_SUCCEED:
                self._set_state(_LAZY_IDLE)
                self._unlock()
        raise exc(text)

    def _is_idle(self):
        return self._state == _LAZY_IDLE

    def start(self, arraysize):
        self._arraysize = arraysize
        self._set_state(_LAZY_FETCHING)
        status = self._cmd.ct_send()
        if status != CS_SUCCEED:
            self._raise_error(Error, 'ct_send')
        return self._start_results()

    def fetchone(self):
        self._lock()
        try:
            if self._state == _LAZY_IDLE:
                self._raise_error(ProgrammingError, 'no result set pending')
            if self._state == _LAZY_CLOSED:
                self._raise_error(ProgrammingError, 'cursor is closed')
            if self._state == _LAZY_FETCHING:
                if self._array_pos >= len(self._array):
                    try:
                        self._array_pos = 0
                        self._array = []
                        _fetch_rows(self._cmd, self._bufs, self._array)
                    except Error:
                        status = self._cmd.ct_cancel(CS_CANCEL_ALL)
                        if status == CS_SUCCEED:
                            self._set_state(_LAZY_IDLE)
                            self._unlock()
                        raise
                if self._array_pos < len(self._array):
                    row = self._array[self._array_pos]
                    self._array_pos = self._array_pos + 1
                    return row
                self._fetch_rowcount()
            self._set_state(_LAZY_END_RESULT)
        finally:
            self._unlock()

    def fetchmany(self, num):
        self._lock()
        try:
            if self._state == _LAZY_IDLE:
                self._raise_error(ProgrammingError, 'no result set pending')
            if self._state == _LAZY_CLOSED:
                self._raise_error(ProgrammingError, 'cursor is closed')
            if self._state == _LAZY_FETCHING:
                if num == -1:
                    num = self._arraysize
                if num != self._bufs[0].count:
                    rows = []
                    for i in xrange(num):
                        row = self.fetchone()
                        if not row:
                            break
                        rows.append(row)
                    return rows
                elif self._array and self._array_pos < len(self._array):
                    rows = self._array[self._array_pos:]
                else:
                    try:
                        rows = []
                        _fetch_rows(self._cmd, self._bufs, rows)
                    except Error:
                        status = self._cmd.ct_cancel(CS_CANCEL_ALL)
                        if status == CS_SUCCEED:
                            self._set_state(_LAZY_IDLE)
                            self._unlock()
                        raise
                self._array = []
                self._array_pos = 0
                if rows:
                    return rows
                self._fetch_rowcount()
            self._set_state(_LAZY_END_RESULT)
            return []
        finally:
            self._unlock()

    def fetchall(self):
        self._lock()
        try:
            rows = []
            while 1:
                row = self.fetchone()
                if not row:
                    break
                rows.append(row)
            return rows
        finally:
            self._unlock()

    def nextset(self):
        self.rowcount = 0
        self._lock()
        try:
            if self._state == _LAZY_CLOSED:
                self._raise_error(ProgrammingError, 'cursor is closed')
            if self._state == _LAZY_IDLE:
                return []
            if self._state == _LAZY_FETCHING:
                status = self._cmd.ct_cancel(CS_CANCEL_CURRENT)
                if status != CS_SUCCEED:
                    self._raise_error(Error, 'ct_cancel')
            return self._start_results()
        finally:
            self._unlock()

    def _start_results(self):
        _ctx.debug_msg('_start_results\n')
        self._array = []
        self._array_pos = 0
        while 1:
            status, result = self._cmd.ct_results()
            if status == CS_END_RESULTS:
                if self._state != _LAZY_END_RESULT:
                    self._unlock()
                self._set_state(_LAZY_IDLE)
                return None
            elif status != CS_SUCCEED:
                self._raise_error(Error, 'ct_results')
            if result in (CS_COMPUTE_RESULT, CS_CURSOR_RESULT,
                          CS_PARAM_RESULT, CS_ROW_RESULT, CS_STATUS_RESULT):
                if self._arraysize > 0:
                    bufs = self._bufs = _row_bind(self._cmd, self._arraysize)
                else:
                    bufs = self._bufs = _row_bind(self._cmd)
                self._set_state(_LAZY_FETCHING)
                return _bufs_description(bufs)
            elif result in (CS_CMD_DONE, CS_CMD_SUCCEED):
                status, self.rowcount = self._cmd.ct_res_info(CS_ROW_COUNT)
                if status != CS_SUCCEED:
                    self._raise_error(Error, 'ct_res_info')
            elif result == CS_CMD_FAIL:
                status, self.rowcount = self._cmd.ct_res_info(CS_ROW_COUNT)
                self._raise_error(Error, 'ct_results')
            else:
                self._raise_error(Error, 'ct_results')

    def _fetch_rowcount(self):
        _ctx.debug_msg('_fetch_rowcount\n')
        while 1:
            status, result = self._cmd.ct_results()
            if status == CS_END_RESULTS:
                self._set_state(_LAZY_IDLE)
                self._unlock()
                return
            elif status != CS_SUCCEED:
                self._raise_error(Error, 'ct_results')
            if result == CS_PARAM_RESULT:
                bufs = _row_bind(self._cmd)
                while 1:
                    rows = []
                    _fetch_rows(self._cmd, bufs, rows)
                    if not rows:
                        break
            elif result in (CS_CMD_DONE, CS_CMD_SUCCEED):
                status, self.rowcount = self._cmd.ct_res_info(CS_ROW_COUNT)
                if status != CS_SUCCEED:
                    self._raise_error(Error, 'ct_res_info')
                return
            else:
                self._raise_error(Error, 'ct_results')


class Cursor:

    def __init__(self, owner):
        '''Implements DB-API Cursor object
        '''
        self.description = None         # DB-API
        self.rowcount = -1              # DB-API
        self.arraysize = 1              # DB-API
        self._owner = owner
        self._fetcher = None
        self._closed = 0

    def _lock(self):
        self._owner._lock()

    def _unlock(self):
        self._owner._unlock()

    def callproc(self, name, params=()):
        '''DB-API Cursor.callproc()
        '''
        _ctx.debug_msg('Cursor.callproc\n')
        if self._closed:
            raise ProgrammingError('cursor is closed')
        self._lock()
        try:
            # Discard any previous results
            self._fetcher = None
            self.return_status = None

            # Prepare to retrieve new results.
            fetcher = self._fetcher = _FetchNowParams(self._owner)
            cmd = fetcher._cmd
            status = cmd.ct_command(CS_RPC_CMD, name)
            if status != CS_SUCCEED:
                fetcher._raise_error(Error, 'ct_command')
            # Send parameters.
            if type(params) is type({}):
                out_params = {}
                for name, value in params.items():
                    out_params[name] = value
                    if isinstance(value, DataBufType):
                        buf = value
                    else:
                        buf = DataBuf(value)
                    buf.name = name
                    status = cmd.ct_param(buf)
                    if status != CS_SUCCEED:
                        fetcher._raise_error(Error, 'ct_param')
            else:
                out_params = []
                for value in params:
                    out_params.append(value)
                    if isinstance(value, DataBufType):
                        buf = value
                    else:
                        buf = DataBuf(value)
                    status = cmd.ct_param(buf)
                    if status != CS_SUCCEED:
                        fetcher._raise_error(Error, 'ct_param')
            # Start retreiving results.
            self.description = fetcher.start(self.arraysize, out_params)
            self.rowcount = fetcher.rowcount
            self.return_status = fetcher.return_status
            return out_params
        finally:
            self._unlock()

    def close(self):
        '''DB-API Cursor.close()
        '''
        if self._closed:
            raise ProgrammingError('cursor is closed')
        self._fetcher = None
        self._closed = 1

    def execute(self, sql, params={}):
        '''DB-API Cursor.execute()
        '''
        _ctx.debug_msg('Cursor.execute\n')
        if self._closed:
            raise ProgrammingError('cursor is closed')
        self._lock()
        try:
            # Discard any previous results
            self._fetcher = None

            # Prepare to retrieve new results.
            fetcher = self._fetcher = _FetchLazy(self._owner)
            cmd = fetcher._cmd
            cmd.ct_command(CS_LANG_CMD, sql)
            for name, value in params.items():
                buf = DataBuf(value)
                buf.name = name
                status = cmd.ct_param(buf)
                if status != CS_SUCCEED:
                    fetcher._raise_error(Error, 'ct_param')
            self.description = fetcher.start(self.arraysize)
            self.rowcount = fetcher.rowcount
        finally:
            self._unlock()

    def executemany(self, sql, params_seq=[]):
        '''DB-API Cursor.executemany()
        '''
        _ctx.debug_msg('Cursor.executemany\n')
        if self._closed:
            raise ProgrammingError('cursor is closed')
        self._lock()
        try:
            for params in params_seq:
                self.execute(sql, params)
                if not self._fetcher._is_idle():
                    self._fetcher._raise_error(ProgrammingError, 'fetchable results on cursor')
        finally:
            self._unlock()

    def fetchone(self):
        '''DB-API Cursor.fetchone()
        '''
        if self._closed:
            raise ProgrammingError('cursor is closed')
        if not self._fetcher:
            raise ProgrammingError('query has not been executed')
        res = self._fetcher.fetchone()
        self.rowcount = self._fetcher.rowcount
        return res

    def fetchmany(self, num=-1):
        '''DB-API Cursor.fetchmany()
        '''
        if self._closed:
            raise ProgrammingError('cursor is closed')
        if not self._fetcher:
            raise ProgrammingError('query has not been executed')
        if num < 0:
            num = self.arraysize
        res = self._fetcher.fetchmany(num)
        self.rowcount = self._fetcher.rowcount
        return res

    def fetchall(self):
        '''DB-API Cursor.fetchall()
        '''
        if self._closed:
            raise ProgrammingError('cursor is closed')
        if not self._fetcher:
            raise ProgrammingError('query has not been executed')
        res = self._fetcher.fetchall()
        self.rowcount = self._fetcher.rowcount
        return res

    def nextset(self):
        '''DB-API Cursor.nextset()
        '''
        if self._closed:
            raise ProgrammingError('cursor is closed')
        if not self._fetcher:
            raise ProgrammingError('query has not been executed')
        desc = self._fetcher.nextset()
        self.rowcount = self._fetcher.rowcount
        if desc:
            self.description = desc
            return 1
        return 0

    def setinputsizes(self, *sizes):
        '''DB-API Cursor.setinputsizes()
        '''
        pass

    def setoutputsize(self, size, column=None):
        '''DB-API Cursor.setoutputsize()
        '''
        pass


class Connection:

    def __init__(self, dsn, user, passwd, database=None,
                 strip=0, auto_commit=0, delay_connect=0, locking=1):
        '''DB-API Sybase.Connect()
        '''
        self._conn = self._cmd = None
        self.dsn = dsn
        self.user = user
        self.passwd = passwd
        self.database = database
        self.auto_commit = auto_commit
        self._do_locking = locking
        self._is_connected = 0
        self.arraysize = 32
        if locking:
            self._connlock = threading.RLock()

        # Do not lock in sybasect - we take care if locking in Python.
        status, conn = _ctx.ct_con_alloc(0)
        if status != CS_SUCCEED:
            raise Error('ct_con_alloc')
        self._conn = conn
        conn.strip = strip
        status = conn.ct_con_props(CS_SET, CS_USERNAME, user)
        if status != CS_SUCCEED:
            self._raise_error(Error, 'ct_con_props')
        status = conn.ct_con_props(CS_SET, CS_PASSWORD, passwd)
        if status != CS_SUCCEED:
            self._raise_error(Error, 'ct_con_props')
        if not delay_connect:
            self.connect()

    def _lock(self):
        if self._do_locking:
            self._connlock.acquire()

    def _unlock(self):
        if self._do_locking:
            self._connlock.release()

    def _raise_error(self, exc, text):
        if self._is_connected:
            self._conn.ct_cancel(CS_CANCEL_ALL)
        raise exc(text)

    def connect(self):
        conn = self._conn
        self._lock()
        try:
            status = conn.ct_connect(self.dsn)
            if status != CS_SUCCEED:
                self._raise_error(Error, 'ct_connect')
            self._is_connected = 1
            status = conn.ct_options(CS_SET, CS_OPT_CHAINXACTS, not self.auto_commit)
            if status != CS_SUCCEED:
                self._raise_error(Error, 'ct_options')
        finally:
            self._unlock()
        if self.database:
            self.execute('use %s' % self.database)
        self._dyn_num = 0

    def get_property(self, prop):
        conn = self._conn
        self._lock()
        try:
            status, value = conn.ct_con_props(CS_GET, prop)
            if status != CS_SUCCEED:
                self._raise_error(Error, 'ct_con_props')
        finally:
            self._unlock()
        return value

    def set_property(self, prop, value):
        conn = self._conn
        self._lock()
        try:
            status = conn.ct_con_props(CS_SET, prop, value)
            if status != CS_SUCCEED:
                self._raise_error(Error, 'ct_con_props')
        finally:
            self._unlock()

    def set_output_hook(self, hook):
        if hook is None:
            if _output_hooks.has_key(self._conn):
                del _output_hooks[self._conn]
        else:
            _output_hooks[self._conn] = hook

    def get_output_hook(self):
        return _output_hooks.get(self._conn)

    def __del__(self):
        if self._conn:
            try:
                self.close()
            except:
                pass

    def close(self):
        '''DBI-API Connection.close()
        '''
        conn = self._conn
        self._lock()
        try:
            status, result = conn.ct_con_props(CS_GET, CS_CON_STATUS)
            if status != CS_SUCCEED:
                self._raise_error(Error, 'ct_con_props')
            if not result & CS_CONSTAT_CONNECTED:
                self._raise_error(ProgrammingError, 'Connection is already closed')
            if self._cmd:
                self._cmd = None
            status = conn.ct_close(CS_FORCE_CLOSE)
            if status != CS_SUCCEED:
                self._raise_error(Error, 'ct_close')
            self._is_connected = 0
        finally:
            self._unlock()

    def begin(self, name=None):
        '''Not in DB-API, but useful for Sybase
        '''
        if name:
            self.execute('begin transaction %s' % name)
        else:
            self.execute('begin transaction')

    def commit(self, name=None):
        '''DB-API Connection.commit()
        '''
        if name:
            self.execute('commit transaction %s' % name)
        else:
            self.execute('commit transaction')

    def rollback(self, name=None):
        '''DB-API Connection.rollback()
        '''
        if name:
            self.execute('rollback transaction %s' % name)
        else:
            self.execute('rollback transaction')

    def cursor(self):
        '''DB-API Connection.cursor()
        '''
        return Cursor(self)

    def execute(self, sql):
        '''Backwards compatibility
        '''
        self._lock()
        try:
            fetcher = _FetchNow(self)
            cmd = fetcher._cmd
            status = cmd.ct_command(CS_LANG_CMD, sql)
            if status != CS_SUCCEED:
                self._raise_error(Error, 'ct_command')
            fetcher.start(self.arraysize)
            return fetcher.result_list()
        finally:
            self._unlock()


def connect(dsn, user, passwd, database=None,
            strip=0, auto_commit=0, delay_connect=0, locking=1):
    return Connection(dsn, user, passwd, database,
                      strip, auto_commit, delay_connect, locking)