dbapi.py
branchstable
changeset 5425 7c84e3f370de
parent 5423 e15abfdcce38
child 5426 0d4853a6e5ee
equal deleted inserted replaced
5421:8167de96c523 5425:7c84e3f370de
    25 __docformat__ = "restructuredtext en"
    25 __docformat__ = "restructuredtext en"
    26 
    26 
    27 from logging import getLogger
    27 from logging import getLogger
    28 from time import time, clock
    28 from time import time, clock
    29 from itertools import count
    29 from itertools import count
       
    30 from warnings import warn
    30 
    31 
    31 from logilab.common.logging_ext import set_log_methods
    32 from logilab.common.logging_ext import set_log_methods
    32 from logilab.common.decorators import monkeypatch
    33 from logilab.common.decorators import monkeypatch
    33 from logilab.common.deprecation import deprecated
    34 from logilab.common.deprecation import deprecated
    34 
    35 
    35 from cubicweb import ETYPE_NAME_MAP, ConnectionError, cwvreg, cwconfig
    36 from cubicweb import ETYPE_NAME_MAP, ConnectionError, AuthenticationError,\
       
    37      cwvreg, cwconfig
    36 from cubicweb.req import RequestSessionBase
    38 from cubicweb.req import RequestSessionBase
    37 
    39 
    38 
    40 
    39 _MARKER = object()
    41 _MARKER = object()
    40 
    42 
   204     # connection to the CubicWeb repository
   206     # connection to the CubicWeb repository
   205     cnxprops = ConnectionProperties('inmemory')
   207     cnxprops = ConnectionProperties('inmemory')
   206     cnx = repo_connect(repo, login, cnxprops=cnxprops, **kwargs)
   208     cnx = repo_connect(repo, login, cnxprops=cnxprops, **kwargs)
   207     return repo, cnx
   209     return repo, cnx
   208 
   210 
       
   211 class _NeedAuthAccessMock(object):
       
   212     def __getattribute__(self, attr):
       
   213         raise AuthenticationError()
       
   214     def __nonzero__(self):
       
   215         return False
       
   216 
       
   217 class DBAPISession(object):
       
   218     def __init__(self, cnx, login=None, authinfo=None):
       
   219         self.cnx = cnx
       
   220         self.data = {}
       
   221         self.login = login
       
   222         self.authinfo = authinfo
       
   223         # dbapi session identifier is the same as the first connection
       
   224         # identifier, but may later differ in case of auto-reconnection as done
       
   225         # by the web authentication manager (in cw.web.views.authentication)
       
   226         if cnx is not None:
       
   227             self.sessionid = cnx.sessionid
       
   228         else:
       
   229             self.sessionid = None
       
   230 
       
   231     @property
       
   232     def anonymous_session(self):
       
   233         return not self.cnx or self.cnx.anonymous_connection
       
   234 
   209 
   235 
   210 class DBAPIRequest(RequestSessionBase):
   236 class DBAPIRequest(RequestSessionBase):
   211 
   237 
   212     def __init__(self, vreg, cnx=None):
   238     def __init__(self, vreg, session=None):
   213         super(DBAPIRequest, self).__init__(vreg)
   239         super(DBAPIRequest, self).__init__(vreg)
   214         try:
   240         try:
   215             # no vreg or config which doesn't handle translations
   241             # no vreg or config which doesn't handle translations
   216             self.translations = vreg.config.translations
   242             self.translations = vreg.config.translations
   217         except AttributeError:
   243         except AttributeError:
   218             self.translations = {}
   244             self.translations = {}
   219         self.set_default_language(vreg)
   245         self.set_default_language(vreg)
   220         # cache entities built during the request
   246         # cache entities built during the request
   221         self._eid_cache = {}
   247         self._eid_cache = {}
   222         # these args are initialized after a connection is
   248         if session is not None:
   223         # established
   249             self.set_session(session)
   224         self.cnx = None   # connection associated to the request
   250         else:
   225         self._user = None # request's user, set at authentication
   251             # these args are initialized after a connection is
   226         if cnx is not None:
   252             # established
   227             self.set_connection(cnx)
   253             self.session = None
       
   254             self.cnx = self.user = _NeedAuthAccessMock()
   228 
   255 
   229     def base_url(self):
   256     def base_url(self):
   230         return self.vreg.config['base-url']
   257         return self.vreg.config['base-url']
   231 
   258 
   232     def from_controller(self):
   259     def from_controller(self):
   233         return 'view'
   260         return 'view'
   234 
   261 
   235     def set_connection(self, cnx, user=None):
   262     def set_session(self, session, user=None):
   236         """method called by the session handler when the user is authenticated
   263         """method called by the session handler when the user is authenticated
   237         or an anonymous connection is open
   264         or an anonymous connection is open
   238         """
   265         """
   239         self.cnx = cnx
   266         self.session = session
   240         self.cursor = cnx.cursor(self)
   267         if session.cnx:
   241         self.set_user(user)
   268             self.cnx = session.cnx
       
   269             self.execute = session.cnx.cursor(self).execute
       
   270             if user is None:
       
   271                 user = self.cnx.user(self, {'lang': self.lang})
       
   272         if user is not None:
       
   273             self.user = user
       
   274             self.set_entity_cache(user)
       
   275 
       
   276     def execute(self, *args, **kwargs):
       
   277         """overriden when session is set. By default raise authentication error
       
   278         so authentication is requested.
       
   279         """
       
   280         raise AuthenticationError()
   242 
   281 
   243     def set_default_language(self, vreg):
   282     def set_default_language(self, vreg):
   244         try:
   283         try:
   245             self.lang = vreg.property_value('ui.language')
   284             self.lang = vreg.property_value('ui.language')
   246         except: # property may not be registered
   285         except: # property may not be registered
   254             # this occurs usually during test execution
   293             # this occurs usually during test execution
   255             self._ = self.__ = unicode
   294             self._ = self.__ = unicode
   256             self.pgettext = lambda x, y: y
   295             self.pgettext = lambda x, y: y
   257         self.debug('request default language: %s', self.lang)
   296         self.debug('request default language: %s', self.lang)
   258 
   297 
   259     def describe(self, eid):
       
   260         """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
       
   261         return self.cnx.describe(eid)
       
   262 
       
   263     def source_defs(self):
       
   264         """return the definition of sources used by the repository."""
       
   265         return self.cnx.source_defs()
       
   266 
       
   267     # entities cache management ###############################################
   298     # entities cache management ###############################################
   268 
   299 
   269     def entity_cache(self, eid):
   300     def entity_cache(self, eid):
   270         return self._eid_cache[eid]
   301         return self._eid_cache[eid]
   271 
   302 
   281         else:
   312         else:
   282             del self._eid_cache[eid]
   313             del self._eid_cache[eid]
   283 
   314 
   284     # low level session data management #######################################
   315     # low level session data management #######################################
   285 
   316 
   286     def session_data(self):
       
   287         """return a dictionnary containing session data"""
       
   288         return self.cnx.session_data()
       
   289 
       
   290     def get_session_data(self, key, default=None, pop=False):
       
   291         """return value associated to `key` in session data"""
       
   292         if self.cnx is None:
       
   293             return default # before the connection has been established
       
   294         return self.cnx.get_session_data(key, default, pop)
       
   295 
       
   296     def set_session_data(self, key, value):
       
   297         """set value associated to `key` in session data"""
       
   298         return self.cnx.set_session_data(key, value)
       
   299 
       
   300     def del_session_data(self, key):
       
   301         """remove value associated to `key` in session data"""
       
   302         return self.cnx.del_session_data(key)
       
   303 
       
   304     def get_shared_data(self, key, default=None, pop=False):
   317     def get_shared_data(self, key, default=None, pop=False):
   305         """return value associated to `key` in shared data"""
   318         """return value associated to `key` in shared data"""
   306         return self.cnx.get_shared_data(key, default, pop)
   319         return self.cnx.get_shared_data(key, default, pop)
   307 
   320 
   308     def set_shared_data(self, key, value, querydata=False):
   321     def set_shared_data(self, key, value, querydata=False):
   315         """
   328         """
   316         return self.cnx.set_shared_data(key, value, querydata)
   329         return self.cnx.set_shared_data(key, value, querydata)
   317 
   330 
   318     # server session compat layer #############################################
   331     # server session compat layer #############################################
   319 
   332 
       
   333     def describe(self, eid):
       
   334         """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
       
   335         return self.cnx.describe(eid)
       
   336 
       
   337     def source_defs(self):
       
   338         """return the definition of sources used by the repository."""
       
   339         return self.cnx.source_defs()
       
   340 
   320     def hijack_user(self, user):
   341     def hijack_user(self, user):
   321         """return a fake request/session using specified user"""
   342         """return a fake request/session using specified user"""
   322         req = DBAPIRequest(self.vreg)
   343         req = DBAPIRequest(self.vreg)
   323         req.set_connection(self.cnx, user)
   344         req.set_session(self.session, user)
   324         return req
   345         return req
   325 
   346 
   326     @property
   347     @deprecated('[3.8] use direct access to req.session.data dictionary')
   327     def user(self):
   348     def session_data(self):
   328         if self._user is None and self.cnx:
   349         """return a dictionnary containing session data"""
   329             self.set_user(self.cnx.user(self, {'lang': self.lang}))
   350         return self.session.data
   330         return self._user
   351 
   331 
   352     @deprecated('[3.8] use direct access to req.session.data dictionary')
   332     def set_user(self, user):
   353     def get_session_data(self, key, default=None, pop=False):
   333         self._user = user
   354         if pop:
   334         if user:
   355             return self.session.data.pop(key, default)
   335             self.set_entity_cache(user)
   356         return self.session.data.get(key, default)
   336 
   357 
   337     def execute(self, *args, **kwargs):
   358     @deprecated('[3.8] use direct access to req.session.data dictionary')
   338         """Session interface compatibility"""
   359     def set_session_data(self, key, value):
   339         return self.cursor.execute(*args, **kwargs)
   360         self.session.data[key] = value
       
   361 
       
   362     @deprecated('[3.8] use direct access to req.session.data dictionary')
       
   363     def del_session_data(self, key):
       
   364         self.session.data.pop(key, None)
       
   365 
   340 
   366 
   341 set_log_methods(DBAPIRequest, getLogger('cubicweb.dbapi'))
   367 set_log_methods(DBAPIRequest, getLogger('cubicweb.dbapi'))
   342 
   368 
   343 
   369 
   344 # exceptions ##################################################################
   370 # exceptions ##################################################################
   349     disconnect occurs, the data source name is not found, a transaction could
   375     disconnect occurs, the data source name is not found, a transaction could
   350     not be processed, a memory allocation error occurred during processing,
   376     not be processed, a memory allocation error occurred during processing,
   351     etc.
   377     etc.
   352     """
   378     """
   353 
   379 
   354 # module level objects ########################################################
   380 
   355 
   381 # cursor / connection objects ##################################################
   356 
   382 
   357 apilevel = '2.0'
   383 class Cursor(object):
   358 
   384     """These objects represent a database cursor, which is used to manage the
   359 """Integer constant stating the level of thread safety the interface supports.
   385     context of a fetch operation. Cursors created from the same connection are
   360 Possible values are:
   386     not isolated, i.e., any changes done to the database by a cursor are
   361 
   387     immediately visible by the other cursors. Cursors created from different
   362                 0     Threads may not share the module.
   388     connections are isolated.
   363                 1     Threads may share the module, but not connections.
   389     """
   364                 2     Threads may share the module and connections.
   390 
   365                 3     Threads may share the module, connections and
   391     def __init__(self, connection, repo, req=None):
   366                       cursors.
   392         """This read-only attribute return a reference to the Connection
   367 
   393         object on which the cursor was created.
   368 Sharing in the above context means that two threads may use a resource without
   394         """
   369 wrapping it using a mutex semaphore to implement resource locking. Note that
   395         self.connection = connection
   370 you cannot always make external resources thread safe by managing access using
   396         """optionnal issuing request instance"""
   371 a mutex: the resource may rely on global variables or other external sources
   397         self.req = req
   372 that are beyond your control.
   398         self._repo = repo
   373 """
   399         self._sessid = connection.sessionid
   374 threadsafety = 1
   400 
   375 
   401     def close(self):
   376 """String constant stating the type of parameter marker formatting expected by
   402         """no effect"""
   377 the interface. Possible values are :
   403         pass
   378 
   404 
   379                 'qmark'         Question mark style,
   405     def execute(self, rql, args=None, eid_key=None, build_descr=True):
   380                                 e.g. '...WHERE name=?'
   406         """execute a rql query, return resulting rows and their description in
   381                 'numeric'       Numeric, positional style,
   407         a :class:`~cubicweb.rset.ResultSet` object
   382                                 e.g. '...WHERE name=:1'
   408 
   383                 'named'         Named style,
   409         * `rql` should be an Unicode string or a plain ASCII string, containing
   384                                 e.g. '...WHERE name=:name'
   410           the rql query
   385                 'format'        ANSI C printf format codes,
   411 
   386                                 e.g. '...WHERE name=%s'
   412         * `args` the optional args dictionary associated to the query, with key
   387                 'pyformat'      Python extended format codes,
   413           matching named substitution in `rql`
   388                                 e.g. '...WHERE name=%(name)s'
   414 
   389 """
   415         * `build_descr` is a boolean flag indicating if the description should
   390 paramstyle = 'pyformat'
   416           be built on select queries (if false, the description will be en empty
   391 
   417           list)
   392 
   418 
   393 # connection object ###########################################################
   419         on INSERT queries, there will be one row for each inserted entity,
       
   420         containing its eid
       
   421 
       
   422         on SET queries, XXX describe
       
   423 
       
   424         DELETE queries returns no result.
       
   425 
       
   426         .. Note::
       
   427           to maximize the rql parsing/analyzing cache performance, you should
       
   428           always use substitute arguments in queries, i.e. avoid query such as::
       
   429 
       
   430             execute('Any X WHERE X eid 123')
       
   431 
       
   432           use::
       
   433 
       
   434             execute('Any X WHERE X eid %(x)s', {'x': 123})
       
   435         """
       
   436         if eid_key is not None:
       
   437             warn('[3.8] eid_key is deprecated, you can safely remove this argument',
       
   438                  DeprecationWarning, stacklevel=2)
       
   439         # XXX use named argument for build_descr in case repo is < 3.8
       
   440         rset = self._repo.execute(self._sessid, rql, args, build_descr=build_descr)
       
   441         rset.req = self.req
       
   442         return rset
       
   443 
       
   444 
       
   445 class LogCursor(Cursor):
       
   446     """override the standard cursor to log executed queries"""
       
   447 
       
   448     def execute(self, operation, parameters=None, eid_key=None, build_descr=True):
       
   449         """override the standard cursor to log executed queries"""
       
   450         if eid_key is not None:
       
   451             warn('[3.8] eid_key is deprecated, you can safely remove this argument',
       
   452                  DeprecationWarning, stacklevel=2)
       
   453         tstart, cstart = time(), clock()
       
   454         rset = Cursor.execute(self, operation, parameters, build_descr=build_descr)
       
   455         self.connection.executed_queries.append((operation, parameters,
       
   456                                                  time() - tstart, clock() - cstart))
       
   457         return rset
       
   458 
   394 
   459 
   395 class Connection(object):
   460 class Connection(object):
   396     """DB-API 2.0 compatible Connection object for CubicWeb
   461     """DB-API 2.0 compatible Connection object for CubicWeb
   397     """
   462     """
   398     # make exceptions available through the connection object
   463     # make exceptions available through the connection object
   399     ProgrammingError = ProgrammingError
   464     ProgrammingError = ProgrammingError
       
   465     # attributes that may be overriden per connection instance
       
   466     anonymous_connection = False
       
   467     cursor_class = Cursor
       
   468     vreg = None
       
   469     _closed = None
   400 
   470 
   401     def __init__(self, repo, cnxid, cnxprops=None):
   471     def __init__(self, repo, cnxid, cnxprops=None):
   402         self._repo = repo
   472         self._repo = repo
   403         self.sessionid = cnxid
   473         self.sessionid = cnxid
   404         self._close_on_del = getattr(cnxprops, 'close_on_del', True)
   474         self._close_on_del = getattr(cnxprops, 'close_on_del', True)
   405         self._cnxtype = getattr(cnxprops, 'cnxtype', 'pyro')
   475         self._cnxtype = getattr(cnxprops, 'cnxtype', 'pyro')
   406         self._closed = None
       
   407         if cnxprops and cnxprops.log_queries:
   476         if cnxprops and cnxprops.log_queries:
   408             self.executed_queries = []
   477             self.executed_queries = []
   409             self.cursor_class = LogCursor
   478             self.cursor_class = LogCursor
   410         else:
       
   411             self.cursor_class = Cursor
       
   412         self.anonymous_connection = False
       
   413         self.vreg = None
       
   414         # session's data
       
   415         self.data = {}
       
   416 
   479 
   417     def __repr__(self):
   480     def __repr__(self):
   418         if self.anonymous_connection:
   481         if self.anonymous_connection:
   419             return '<Connection %s (anonymous)>' % self.sessionid
   482             return '<Connection %s (anonymous)>' % self.sessionid
   420         return '<Connection %s>' % self.sessionid
   483         return '<Connection %s>' % self.sessionid
   428         else:
   491         else:
   429             self.rollback()
   492             self.rollback()
   430             return False #propagate the exception
   493             return False #propagate the exception
   431 
   494 
   432     def request(self):
   495     def request(self):
   433         return DBAPIRequest(self.vreg, self)
   496         return DBAPIRequest(self.vreg, DBAPISession(self))
   434 
       
   435     def session_data(self):
       
   436         """return a dictionnary containing session data"""
       
   437         return self.data
       
   438 
       
   439     def get_session_data(self, key, default=None, pop=False):
       
   440         """return value associated to `key` in session data"""
       
   441         if pop:
       
   442             return self.data.pop(key, default)
       
   443         else:
       
   444             return self.data.get(key, default)
       
   445 
       
   446     def set_session_data(self, key, value):
       
   447         """set value associated to `key` in session data"""
       
   448         self.data[key] = value
       
   449 
       
   450     def del_session_data(self, key):
       
   451         """remove value associated to `key` in session data"""
       
   452         try:
       
   453             del self.data[key]
       
   454         except KeyError:
       
   455             pass
       
   456 
   497 
   457     def check(self):
   498     def check(self):
   458         """raise `BadConnectionId` if the connection is no more valid"""
   499         """raise `BadConnectionId` if the connection is no more valid"""
   459         if self._closed is not None:
   500         if self._closed is not None:
   460             raise ProgrammingError('Closed connection')
   501             raise ProgrammingError('Closed connection')
   524             hm.register_system_hooks(config)
   565             hm.register_system_hooks(config)
   525             # instance specific hooks
   566             # instance specific hooks
   526             if self._repo.config.instance_hooks:
   567             if self._repo.config.instance_hooks:
   527                 hm.register_hooks(config.load_hooks(self.vreg))
   568                 hm.register_hooks(config.load_hooks(self.vreg))
   528 
   569 
   529     load_vobjects = deprecated()(load_appobjects)
       
   530 
       
   531     def use_web_compatible_requests(self, baseurl, sitetitle=None):
   570     def use_web_compatible_requests(self, baseurl, sitetitle=None):
   532         """monkey patch DBAPIRequest to fake a cw.web.request, so you should
   571         """monkey patch DBAPIRequest to fake a cw.web.request, so you should
   533         able to call html views using rset from a simple dbapi connection.
   572         able to call html views using rset from a simple dbapi connection.
   534 
   573 
   535         You should call `load_appobjects` at some point to register those views.
   574         You should call `load_appobjects` at some point to register those views.
   572         eid, login, groups, properties = self._repo.user_info(self.sessionid,
   611         eid, login, groups, properties = self._repo.user_info(self.sessionid,
   573                                                               props)
   612                                                               props)
   574         if req is None:
   613         if req is None:
   575             req = self.request()
   614             req = self.request()
   576         rset = req.eid_rset(eid, 'CWUser')
   615         rset = req.eid_rset(eid, 'CWUser')
   577         user = self.vreg['etypes'].etype_class('CWUser')(req, rset, row=0,
   616         if self.vreg is not None and 'etypes' in self.vreg:
   578                                                          groups=groups,
   617             user = self.vreg['etypes'].etype_class('CWUser')(req, rset, row=0,
   579                                                          properties=properties)
   618                                                              groups=groups,
       
   619                                                              properties=properties)
       
   620         else:
       
   621             from cubicweb.entity import Entity
       
   622             user = Entity(req, rset, row=0)
   580         user['login'] = login # cache login
   623         user['login'] = login # cache login
   581         return user
   624         return user
   582 
   625 
   583     def __del__(self):
   626     def __del__(self):
   584         """close the remote connection if necessary"""
   627         """close the remote connection if necessary"""
   709         raise `NoSuchTransaction` if not found or if session's user is not
   752         raise `NoSuchTransaction` if not found or if session's user is not
   710         allowed (eg not in managers group and the transaction doesn't belong to
   753         allowed (eg not in managers group and the transaction doesn't belong to
   711         him).
   754         him).
   712         """
   755         """
   713         return self._repo.undo_transaction(self.sessionid, txuuid)
   756         return self._repo.undo_transaction(self.sessionid, txuuid)
   714 
       
   715 
       
   716 # cursor object ###############################################################
       
   717 
       
   718 class Cursor(object):
       
   719     """This represents a database cursor, which is used to manage the
       
   720     context of a fetch operation. Cursors created from the same connection are
       
   721     not isolated, i.e., any changes done to the database by a cursor are
       
   722     immediately visible by the other cursors. Cursors created from different
       
   723     connections can or can not be isolated, depending on how the transaction
       
   724     support is implemented (see also the connection's rollback() and commit()
       
   725     methods.)
       
   726     """
       
   727 
       
   728     def __init__(self, connection, repo, req=None):
       
   729         # This read-only attribute returns a reference to the Connection
       
   730         # object on which the cursor was created.
       
   731         self.connection = connection
       
   732         # optionnal issuing request instance
       
   733         self.req = req
       
   734 
       
   735         # This read/write attribute specifies the number of rows to fetch at a
       
   736         # time with fetchmany(). It defaults to 1 meaning to fetch a single row
       
   737         # at a time.
       
   738         # Implementations must observe this value with respect to the fetchmany()
       
   739         # method, but are free to interact with the database a single row at a
       
   740         # time. It may also be used in the implementation of executemany().
       
   741         self.arraysize = 1
       
   742 
       
   743         self._repo = repo
       
   744         self._sessid = connection.sessionid
       
   745         self._res = None
       
   746         self._closed = None
       
   747         self._index = 0
       
   748 
       
   749     def close(self):
       
   750         """Close the cursor now (rather than whenever __del__ is called).  The
       
   751         cursor will be unusable from this point forward; an Error (or subclass)
       
   752         exception will be raised if any operation is attempted with the cursor.
       
   753         """
       
   754         self._closed = True
       
   755 
       
   756 
       
   757     def execute(self, operation, parameters=None, eid_key=None, build_descr=True):
       
   758         """Prepare and execute a database operation (query or command).
       
   759         Parameters may be provided as sequence or mapping and will be bound to
       
   760         variables in the operation.  Variables are specified in a
       
   761         database-specific notation (see the module's paramstyle attribute for
       
   762         details).
       
   763 
       
   764         A reference to the operation will be retained by the cursor.  If the
       
   765         same operation object is passed in again, then the cursor can optimize
       
   766         its behavior.  This is most effective for algorithms where the same
       
   767         operation is used, but different parameters are bound to it (many
       
   768         times).
       
   769 
       
   770         For maximum efficiency when reusing an operation, it is best to use the
       
   771         setinputsizes() method to specify the parameter types and sizes ahead
       
   772         of time.  It is legal for a parameter to not match the predefined
       
   773         information; the implementation should compensate, possibly with a loss
       
   774         of efficiency.
       
   775 
       
   776         The parameters may also be specified as list of tuples to e.g. insert
       
   777         multiple rows in a single operation, but this kind of usage is
       
   778         depreciated: executemany() should be used instead.
       
   779 
       
   780         Return values are not defined by the DB-API, but this here it returns a
       
   781         ResultSet object.
       
   782         """
       
   783         self._res = rset = self._repo.execute(self._sessid, operation,
       
   784                                               parameters, eid_key, build_descr)
       
   785         rset.req = self.req
       
   786         self._index = 0
       
   787         return rset
       
   788 
       
   789 
       
   790     def executemany(self, operation, seq_of_parameters):
       
   791         """Prepare a database operation (query or command) and then execute it
       
   792         against all parameter sequences or mappings found in the sequence
       
   793         seq_of_parameters.
       
   794 
       
   795         Modules are free to implement this method using multiple calls to the
       
   796         execute() method or by using array operations to have the database
       
   797         process the sequence as a whole in one call.
       
   798 
       
   799         Use of this method for an operation which produces one or more result
       
   800         sets constitutes undefined behavior, and the implementation is
       
   801         permitted (but not required) to raise an exception when it detects that
       
   802         a result set has been created by an invocation of the operation.
       
   803 
       
   804         The same comments as for execute() also apply accordingly to this
       
   805         method.
       
   806 
       
   807         Return values are not defined.
       
   808         """
       
   809         for parameters in seq_of_parameters:
       
   810             self.execute(operation, parameters)
       
   811             if self._res.rows is not None:
       
   812                 self._res = None
       
   813                 raise ProgrammingError('Operation returned a result set')
       
   814 
       
   815 
       
   816     def fetchone(self):
       
   817         """Fetch the next row of a query result set, returning a single
       
   818         sequence, or None when no more data is available.
       
   819 
       
   820         An Error (or subclass) exception is raised if the previous call to
       
   821         execute*() did not produce any result set or no call was issued yet.
       
   822         """
       
   823         if self._res is None:
       
   824             raise ProgrammingError('No result set')
       
   825         row = self._res.rows[self._index]
       
   826         self._index += 1
       
   827         return row
       
   828 
       
   829 
       
   830     def fetchmany(self, size=None):
       
   831         """Fetch the next set of rows of a query result, returning a sequence
       
   832         of sequences (e.g. a list of tuples). An empty sequence is returned
       
   833         when no more rows are available.
       
   834 
       
   835         The number of rows to fetch per call is specified by the parameter.  If
       
   836         it is not given, the cursor's arraysize determines the number of rows
       
   837         to be fetched. The method should try to fetch as many rows as indicated
       
   838         by the size parameter. If this is not possible due to the specified
       
   839         number of rows not being available, fewer rows may be returned.
       
   840 
       
   841         An Error (or subclass) exception is raised if the previous call to
       
   842         execute*() did not produce any result set or no call was issued yet.
       
   843 
       
   844         Note there are performance considerations involved with the size
       
   845         parameter.  For optimal performance, it is usually best to use the
       
   846         arraysize attribute.  If the size parameter is used, then it is best
       
   847         for it to retain the same value from one fetchmany() call to the next.
       
   848         """
       
   849         if self._res is None:
       
   850             raise ProgrammingError('No result set')
       
   851         if size is None:
       
   852             size = self.arraysize
       
   853         rows = self._res.rows[self._index:self._index + size]
       
   854         self._index += size
       
   855         return rows
       
   856 
       
   857 
       
   858     def fetchall(self):
       
   859         """Fetch all (remaining) rows of a query result, returning them as a
       
   860         sequence of sequences (e.g. a list of tuples).  Note that the cursor's
       
   861         arraysize attribute can affect the performance of this operation.
       
   862 
       
   863         An Error (or subclass) exception is raised if the previous call to
       
   864         execute*() did not produce any result set or no call was issued yet.
       
   865         """
       
   866         if self._res is None:
       
   867             raise ProgrammingError('No result set')
       
   868         if not self._res.rows:
       
   869             return []
       
   870         rows = self._res.rows[self._index:]
       
   871         self._index = len(self._res)
       
   872         return rows
       
   873 
       
   874 
       
   875     def setinputsizes(self, sizes):
       
   876         """This can be used before a call to execute*() to predefine memory
       
   877         areas for the operation's parameters.
       
   878 
       
   879         sizes is specified as a sequence -- one item for each input parameter.
       
   880         The item should be a Type Object that corresponds to the input that
       
   881         will be used, or it should be an integer specifying the maximum length
       
   882         of a string parameter.  If the item is None, then no predefined memory
       
   883         area will be reserved for that column (this is useful to avoid
       
   884         predefined areas for large inputs).
       
   885 
       
   886         This method would be used before the execute*() method is invoked.
       
   887 
       
   888         Implementations are free to have this method do nothing and users are
       
   889         free to not use it.
       
   890         """
       
   891         pass
       
   892 
       
   893 
       
   894     def setoutputsize(self, size, column=None):
       
   895         """Set a column buffer size for fetches of large columns (e.g. LONGs,
       
   896         BLOBs, etc.).  The column is specified as an index into the result
       
   897         sequence.  Not specifying the column will set the default size for all
       
   898         large columns in the cursor.
       
   899 
       
   900         This method would be used before the execute*() method is invoked.
       
   901 
       
   902         Implementations are free to have this method do nothing and users are
       
   903         free to not use it.
       
   904         """
       
   905         pass
       
   906 
       
   907 
       
   908 class LogCursor(Cursor):
       
   909     """override the standard cursor to log executed queries"""
       
   910 
       
   911     def execute(self, operation, parameters=None, eid_key=None, build_descr=True):
       
   912         """override the standard cursor to log executed queries"""
       
   913         tstart, cstart = time(), clock()
       
   914         rset = Cursor.execute(self, operation, parameters, eid_key, build_descr)
       
   915         self.connection.executed_queries.append((operation, parameters,
       
   916                                                  time() - tstart, clock() - cstart))
       
   917         return rset