dbapi.py
branchtls-sprint
changeset 1544 d8fb60c56d69
parent 1524 1d7575f5deaf
child 1846 738541c2a729
equal deleted inserted replaced
1543:dca9817bb337 1544:d8fb60c56d69
    14 from time import time, clock
    14 from time import time, clock
    15 
    15 
    16 from cubicweb import ConnectionError, RequestSessionMixIn, set_log_methods
    16 from cubicweb import ConnectionError, RequestSessionMixIn, set_log_methods
    17 from cubicweb.cwvreg import CubicWebRegistry, MulCnxCubicWebRegistry
    17 from cubicweb.cwvreg import CubicWebRegistry, MulCnxCubicWebRegistry
    18 from cubicweb.cwconfig import CubicWebNoAppConfiguration
    18 from cubicweb.cwconfig import CubicWebNoAppConfiguration
    19         
    19 
    20 _MARKER = object()
    20 _MARKER = object()
    21 
    21 
    22 class ConnectionProperties(object):
    22 class ConnectionProperties(object):
    23     def __init__(self, cnxtype=None, lang=None, close=True, log=False):
    23     def __init__(self, cnxtype=None, lang=None, close=True, log=False):
    24         self.cnxtype = cnxtype or 'pyro'
    24         self.cnxtype = cnxtype or 'pyro'
    27         self.close_on_del = close
    27         self.close_on_del = close
    28 
    28 
    29 
    29 
    30 def get_repository(method, database=None, config=None, vreg=None):
    30 def get_repository(method, database=None, config=None, vreg=None):
    31     """get a proxy object to the CubicWeb repository, using a specific RPC method.
    31     """get a proxy object to the CubicWeb repository, using a specific RPC method.
    32      
    32 
    33     Only 'in-memory' and 'pyro' are supported for now. Either vreg or config
    33     Only 'in-memory' and 'pyro' are supported for now. Either vreg or config
    34     argument should be given
    34     argument should be given
    35     """
    35     """
    36     assert method in ('pyro', 'inmemory')
    36     assert method in ('pyro', 'inmemory')
    37     assert vreg or config
    37     assert vreg or config
    58             raise ConnectionError('Could not get repository for %s '
    58             raise ConnectionError('Could not get repository for %s '
    59                                   '(not registered in Pyro), '
    59                                   '(not registered in Pyro), '
    60                                   'you may have to restart your server-side '
    60                                   'you may have to restart your server-side '
    61                                   'application' % nsid)
    61                                   'application' % nsid)
    62         return core.getProxyForURI(uri)
    62         return core.getProxyForURI(uri)
    63         
    63 
    64 def repo_connect(repo, user, password, cnxprops=None):
    64 def repo_connect(repo, user, password, cnxprops=None):
    65     """Constructor to create a new connection to the CubicWeb repository.
    65     """Constructor to create a new connection to the CubicWeb repository.
    66     
    66 
    67     Returns a Connection instance.
    67     Returns a Connection instance.
    68     """
    68     """
    69     cnxprops = cnxprops or ConnectionProperties('inmemory')
    69     cnxprops = cnxprops or ConnectionProperties('inmemory')
    70     cnxid = repo.connect(unicode(user), password, cnxprops=cnxprops)
    70     cnxid = repo.connect(unicode(user), password, cnxprops=cnxprops)
    71     cnx = Connection(repo, cnxid, cnxprops)
    71     cnx = Connection(repo, cnxid, cnxprops)
    72     if cnxprops.cnxtype == 'inmemory':
    72     if cnxprops.cnxtype == 'inmemory':
    73         cnx.vreg = repo.vreg
    73         cnx.vreg = repo.vreg
    74     return cnx
    74     return cnx
    75     
    75 
    76 def connect(database=None, user=None, password=None, host=None,
    76 def connect(database=None, user=None, password=None, host=None,
    77             group=None, cnxprops=None, port=None, setvreg=True, mulcnx=True,
    77             group=None, cnxprops=None, port=None, setvreg=True, mulcnx=True,
    78             initlog=True):
    78             initlog=True):
    79     """Constructor for creating a connection to the CubicWeb repository.
    79     """Constructor for creating a connection to the CubicWeb repository.
    80     Returns a Connection object.
    80     Returns a Connection object.
   108     cnx.vreg = vreg
   108     cnx.vreg = vreg
   109     return cnx
   109     return cnx
   110 
   110 
   111 def in_memory_cnx(config, user, password):
   111 def in_memory_cnx(config, user, password):
   112     """usefull method for testing and scripting to get a dbapi.Connection
   112     """usefull method for testing and scripting to get a dbapi.Connection
   113     object connected to an in-memory repository instance 
   113     object connected to an in-memory repository instance
   114     """
   114     """
   115     if isinstance(config, CubicWebRegistry):
   115     if isinstance(config, CubicWebRegistry):
   116         vreg = config
   116         vreg = config
   117         config = None
   117         config = None
   118     else:
   118     else:
   124     cnx = repo_connect(repo, user, password, cnxprops=cnxprops)
   124     cnx = repo_connect(repo, user, password, cnxprops=cnxprops)
   125     return repo, cnx
   125     return repo, cnx
   126 
   126 
   127 
   127 
   128 class DBAPIRequest(RequestSessionMixIn):
   128 class DBAPIRequest(RequestSessionMixIn):
   129     
   129 
   130     def __init__(self, vreg, cnx=None):
   130     def __init__(self, vreg, cnx=None):
   131         super(DBAPIRequest, self).__init__(vreg)
   131         super(DBAPIRequest, self).__init__(vreg)
   132         try:
   132         try:
   133             # no vreg or config which doesn't handle translations
   133             # no vreg or config which doesn't handle translations
   134             self.translations = vreg.config.translations
   134             self.translations = vreg.config.translations
   144         if cnx is not None:
   144         if cnx is not None:
   145             self.set_connection(cnx)
   145             self.set_connection(cnx)
   146 
   146 
   147     def base_url(self):
   147     def base_url(self):
   148         return self.vreg.config['base-url']
   148         return self.vreg.config['base-url']
   149     
   149 
   150     def from_controller(self):
   150     def from_controller(self):
   151         return 'view'
   151         return 'view'
   152     
   152 
   153     def set_connection(self, cnx, user=None):
   153     def set_connection(self, cnx, user=None):
   154         """method called by the session handler when the user is authenticated
   154         """method called by the session handler when the user is authenticated
   155         or an anonymous connection is open
   155         or an anonymous connection is open
   156         """
   156         """
   157         self.cnx = cnx
   157         self.cnx = cnx
   158         self.cursor = cnx.cursor(self)
   158         self.cursor = cnx.cursor(self)
   159         self.set_user(user)
   159         self.set_user(user)
   160     
   160 
   161     def set_default_language(self, vreg):
   161     def set_default_language(self, vreg):
   162         try:
   162         try:
   163             self.lang = vreg.property_value('ui.language')
   163             self.lang = vreg.property_value('ui.language')
   164         except: # property may not be registered
   164         except: # property may not be registered
   165             self.lang = 'en'
   165             self.lang = 'en'
   173 
   173 
   174     def decorate_rset(self, rset):
   174     def decorate_rset(self, rset):
   175         rset.vreg = self.vreg
   175         rset.vreg = self.vreg
   176         rset.req = self
   176         rset.req = self
   177         return rset
   177         return rset
   178     
   178 
   179     def describe(self, eid):
   179     def describe(self, eid):
   180         """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
   180         """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
   181         return self.cnx.describe(eid)
   181         return self.cnx.describe(eid)
   182     
   182 
   183     def source_defs(self):
   183     def source_defs(self):
   184         """return the definition of sources used by the repository."""
   184         """return the definition of sources used by the repository."""
   185         return self.cnx.source_defs()
   185         return self.cnx.source_defs()
   186             
   186 
   187     # entities cache management ###############################################
   187     # entities cache management ###############################################
   188     
   188 
   189     def entity_cache(self, eid):
   189     def entity_cache(self, eid):
   190         return self._eid_cache[eid]
   190         return self._eid_cache[eid]
   191     
   191 
   192     def set_entity_cache(self, entity):
   192     def set_entity_cache(self, entity):
   193         self._eid_cache[entity.eid] = entity
   193         self._eid_cache[entity.eid] = entity
   194 
   194 
   195     def cached_entities(self):
   195     def cached_entities(self):
   196         return self._eid_cache.values()
   196         return self._eid_cache.values()
   197     
   197 
   198     def drop_entity_cache(self, eid=None):
   198     def drop_entity_cache(self, eid=None):
   199         if eid is None:
   199         if eid is None:
   200             self._eid_cache = {}
   200             self._eid_cache = {}
   201         else:
   201         else:
   202             del self._eid_cache[eid]
   202             del self._eid_cache[eid]
   208         return self.cnx.session_data()
   208         return self.cnx.session_data()
   209 
   209 
   210     def get_session_data(self, key, default=None, pop=False):
   210     def get_session_data(self, key, default=None, pop=False):
   211         """return value associated to `key` in session data"""
   211         """return value associated to `key` in session data"""
   212         return self.cnx.get_session_data(key, default, pop)
   212         return self.cnx.get_session_data(key, default, pop)
   213         
   213 
   214     def set_session_data(self, key, value):
   214     def set_session_data(self, key, value):
   215         """set value associated to `key` in session data"""
   215         """set value associated to `key` in session data"""
   216         return self.cnx.set_session_data(key, value)
   216         return self.cnx.set_session_data(key, value)
   217         
   217 
   218     def del_session_data(self, key):
   218     def del_session_data(self, key):
   219         """remove value associated to `key` in session data"""
   219         """remove value associated to `key` in session data"""
   220         return self.cnx.del_session_data(key)
   220         return self.cnx.del_session_data(key)
   221 
   221 
   222     def get_shared_data(self, key, default=None, pop=False):
   222     def get_shared_data(self, key, default=None, pop=False):
   223         """return value associated to `key` in shared data"""
   223         """return value associated to `key` in shared data"""
   224         return self.cnx.get_shared_data(key, default, pop)
   224         return self.cnx.get_shared_data(key, default, pop)
   225         
   225 
   226     def set_shared_data(self, key, value, querydata=False):
   226     def set_shared_data(self, key, value, querydata=False):
   227         """set value associated to `key` in shared data
   227         """set value associated to `key` in shared data
   228 
   228 
   229         if `querydata` is true, the value will be added to the repository
   229         if `querydata` is true, the value will be added to the repository
   230         session's query data which are cleared on commit/rollback of the current
   230         session's query data which are cleared on commit/rollback of the current
   243 
   243 
   244     def set_user(self, user):
   244     def set_user(self, user):
   245         self._user = user
   245         self._user = user
   246         if user:
   246         if user:
   247             self.set_entity_cache(user)
   247             self.set_entity_cache(user)
   248         
   248 
   249     def execute(self, *args, **kwargs):
   249     def execute(self, *args, **kwargs):
   250         """Session interface compatibility"""
   250         """Session interface compatibility"""
   251         return self.cursor.execute(*args, **kwargs)
   251         return self.cursor.execute(*args, **kwargs)
   252 
   252 
   253 set_log_methods(DBAPIRequest, getLogger('cubicweb.dbapi'))
   253 set_log_methods(DBAPIRequest, getLogger('cubicweb.dbapi'))
   254         
   254 
   255         
   255 
   256 # exceptions ##################################################################
   256 # exceptions ##################################################################
   257 
   257 
   258 class ProgrammingError(Exception): #DatabaseError):
   258 class ProgrammingError(Exception): #DatabaseError):
   259     """Exception raised for errors that are related to the database's operation
   259     """Exception raised for errors that are related to the database's operation
   260     and not necessarily under the control of the programmer, e.g. an unexpected
   260     and not necessarily under the control of the programmer, e.g. an unexpected
   286 threadsafety = 1
   286 threadsafety = 1
   287 
   287 
   288 """String constant stating the type of parameter marker formatting expected by
   288 """String constant stating the type of parameter marker formatting expected by
   289 the interface. Possible values are :
   289 the interface. Possible values are :
   290 
   290 
   291                 'qmark'         Question mark style, 
   291                 'qmark'         Question mark style,
   292                                 e.g. '...WHERE name=?'
   292                                 e.g. '...WHERE name=?'
   293                 'numeric'       Numeric, positional style, 
   293                 'numeric'       Numeric, positional style,
   294                                 e.g. '...WHERE name=:1'
   294                                 e.g. '...WHERE name=:1'
   295                 'named'         Named style, 
   295                 'named'         Named style,
   296                                 e.g. '...WHERE name=:name'
   296                                 e.g. '...WHERE name=:name'
   297                 'format'        ANSI C printf format codes, 
   297                 'format'        ANSI C printf format codes,
   298                                 e.g. '...WHERE name=%s'
   298                                 e.g. '...WHERE name=%s'
   299                 'pyformat'      Python extended format codes, 
   299                 'pyformat'      Python extended format codes,
   300                                 e.g. '...WHERE name=%(name)s'
   300                                 e.g. '...WHERE name=%(name)s'
   301 """
   301 """
   302 paramstyle = 'pyformat'
   302 paramstyle = 'pyformat'
   303 
   303 
   304 
   304 
   331             return '<Connection %s (anonymous)>' % self.sessionid
   331             return '<Connection %s (anonymous)>' % self.sessionid
   332         return '<Connection %s>' % self.sessionid
   332         return '<Connection %s>' % self.sessionid
   333 
   333 
   334     def request(self):
   334     def request(self):
   335         return DBAPIRequest(self.vreg, self)
   335         return DBAPIRequest(self.vreg, self)
   336     
   336 
   337     def session_data(self):
   337     def session_data(self):
   338         """return a dictionnary containing session data"""
   338         """return a dictionnary containing session data"""
   339         return self.data
   339         return self.data
   340         
   340 
   341     def get_session_data(self, key, default=None, pop=False):
   341     def get_session_data(self, key, default=None, pop=False):
   342         """return value associated to `key` in session data"""
   342         """return value associated to `key` in session data"""
   343         if pop:
   343         if pop:
   344             return self.data.pop(key, default)
   344             return self.data.pop(key, default)
   345         else:
   345         else:
   346             return self.data.get(key, default)
   346             return self.data.get(key, default)
   347         
   347 
   348     def set_session_data(self, key, value):
   348     def set_session_data(self, key, value):
   349         """set value associated to `key` in session data"""
   349         """set value associated to `key` in session data"""
   350         self.data[key] = value
   350         self.data[key] = value
   351         
   351 
   352     def del_session_data(self, key):
   352     def del_session_data(self, key):
   353         """remove value associated to `key` in session data"""
   353         """remove value associated to `key` in session data"""
   354         try:
   354         try:
   355             del self.data[key]
   355             del self.data[key]
   356         except KeyError:
   356         except KeyError:
   357             pass    
   357             pass
   358 
   358 
   359     def check(self):
   359     def check(self):
   360         """raise `BadSessionId` if the connection is no more valid"""
   360         """raise `BadSessionId` if the connection is no more valid"""
   361         self._repo.check_session(self.sessionid)
   361         self._repo.check_session(self.sessionid)
   362 
   362 
   363     def get_shared_data(self, key, default=None, pop=False):
   363     def get_shared_data(self, key, default=None, pop=False):
   364         """return value associated to `key` in shared data"""
   364         """return value associated to `key` in shared data"""
   365         return self._repo.get_shared_data(self.sessionid, key, default, pop)
   365         return self._repo.get_shared_data(self.sessionid, key, default, pop)
   366         
   366 
   367     def set_shared_data(self, key, value, querydata=False):
   367     def set_shared_data(self, key, value, querydata=False):
   368         """set value associated to `key` in shared data
   368         """set value associated to `key` in shared data
   369 
   369 
   370         if `querydata` is true, the value will be added to the repository
   370         if `querydata` is true, the value will be added to the repository
   371         session's query data which are cleared on commit/rollback of the current
   371         session's query data which are cleared on commit/rollback of the current
   372         transaction, and won't be available through the connexion, only on the
   372         transaction, and won't be available through the connexion, only on the
   373         repository side.
   373         repository side.
   374         """
   374         """
   375         return self._repo.set_shared_data(self.sessionid, key, value, querydata)
   375         return self._repo.set_shared_data(self.sessionid, key, value, querydata)
   376         
   376 
   377     def get_schema(self):
   377     def get_schema(self):
   378         """Return the schema currently used by the repository.
   378         """Return the schema currently used by the repository.
   379         
   379 
   380         This is NOT part of the DB-API.
   380         This is NOT part of the DB-API.
   381         """
   381         """
   382         if self._closed is not None:
   382         if self._closed is not None:
   383             raise ProgrammingError('Closed connection')
   383             raise ProgrammingError('Closed connection')
   384         return self._repo.get_schema()
   384         return self._repo.get_schema()
   412             hm.set_schema(hm.schema) # reset structure
   412             hm.set_schema(hm.schema) # reset structure
   413             hm.register_system_hooks(config)
   413             hm.register_system_hooks(config)
   414             # application specific hooks
   414             # application specific hooks
   415             if self._repo.config.application_hooks:
   415             if self._repo.config.application_hooks:
   416                 hm.register_hooks(config.load_hooks(self.vreg))
   416                 hm.register_hooks(config.load_hooks(self.vreg))
   417             
   417 
   418     def source_defs(self):
   418     def source_defs(self):
   419         """Return the definition of sources used by the repository.
   419         """Return the definition of sources used by the repository.
   420         
   420 
   421         This is NOT part of the DB-API.
   421         This is NOT part of the DB-API.
   422         """
   422         """
   423         if self._closed is not None:
   423         if self._closed is not None:
   424             raise ProgrammingError('Closed connection')
   424             raise ProgrammingError('Closed connection')
   425         return self._repo.source_defs()
   425         return self._repo.source_defs()
   441         if self._closed is None and self._close_on_del:
   441         if self._closed is None and self._close_on_del:
   442             try:
   442             try:
   443                 self.close()
   443                 self.close()
   444             except:
   444             except:
   445                 pass
   445                 pass
   446     
   446 
   447     def describe(self, eid):
   447     def describe(self, eid):
   448         return self._repo.describe(self.sessionid, eid)
   448         return self._repo.describe(self.sessionid, eid)
   449             
   449 
   450     def close(self):
   450     def close(self):
   451         """Close the connection now (rather than whenever __del__ is called).
   451         """Close the connection now (rather than whenever __del__ is called).
   452         
   452 
   453         The connection will be unusable from this point forward; an Error (or
   453         The connection will be unusable from this point forward; an Error (or
   454         subclass) exception will be raised if any operation is attempted with
   454         subclass) exception will be raised if any operation is attempted with
   455         the connection. The same applies to all cursor objects trying to use the
   455         the connection. The same applies to all cursor objects trying to use the
   456         connection.  Note that closing a connection without committing the
   456         connection.  Note that closing a connection without committing the
   457         changes first will cause an implicit rollback to be performed.
   457         changes first will cause an implicit rollback to be performed.
   463 
   463 
   464     def commit(self):
   464     def commit(self):
   465         """Commit any pending transaction to the database. Note that if the
   465         """Commit any pending transaction to the database. Note that if the
   466         database supports an auto-commit feature, this must be initially off. An
   466         database supports an auto-commit feature, this must be initially off. An
   467         interface method may be provided to turn it back on.
   467         interface method may be provided to turn it back on.
   468             
   468 
   469         Database modules that do not support transactions should implement this
   469         Database modules that do not support transactions should implement this
   470         method with void functionality.
   470         method with void functionality.
   471         """
   471         """
   472         if not self._closed is None:
   472         if not self._closed is None:
   473             raise ProgrammingError('Connection is already closed')
   473             raise ProgrammingError('Connection is already closed')
   474         self._repo.commit(self.sessionid)
   474         self._repo.commit(self.sessionid)
   475 
   475 
   476     def rollback(self):
   476     def rollback(self):
   477         """This method is optional since not all databases provide transaction
   477         """This method is optional since not all databases provide transaction
   478         support.
   478         support.
   479             
   479 
   480         In case a database does provide transactions this method causes the the
   480         In case a database does provide transactions this method causes the the
   481         database to roll back to the start of any pending transaction.  Closing
   481         database to roll back to the start of any pending transaction.  Closing
   482         a connection without committing the changes first will cause an implicit
   482         a connection without committing the changes first will cause an implicit
   483         rollback to be performed.
   483         rollback to be performed.
   484         """
   484         """
   508     immediately visible by the other cursors. Cursors created from different
   508     immediately visible by the other cursors. Cursors created from different
   509     connections can or can not be isolated, depending on how the transaction
   509     connections can or can not be isolated, depending on how the transaction
   510     support is implemented (see also the connection's rollback() and commit()
   510     support is implemented (see also the connection's rollback() and commit()
   511     methods.)
   511     methods.)
   512     """
   512     """
   513     
   513 
   514     def __init__(self, connection, repo, req=None):
   514     def __init__(self, connection, repo, req=None):
   515         """This read-only attribute return a reference to the Connection
   515         """This read-only attribute return a reference to the Connection
   516         object on which the cursor was created.
   516         object on which the cursor was created.
   517         """
   517         """
   518         self.connection = connection
   518         self.connection = connection
   520         self.req = req
   520         self.req = req
   521 
   521 
   522         """This read/write attribute specifies the number of rows to fetch at a
   522         """This read/write attribute specifies the number of rows to fetch at a
   523         time with fetchmany(). It defaults to 1 meaning to fetch a single row
   523         time with fetchmany(). It defaults to 1 meaning to fetch a single row
   524         at a time.
   524         at a time.
   525         
   525 
   526         Implementations must observe this value with respect to the fetchmany()
   526         Implementations must observe this value with respect to the fetchmany()
   527         method, but are free to interact with the database a single row at a
   527         method, but are free to interact with the database a single row at a
   528         time. It may also be used in the implementation of executemany().
   528         time. It may also be used in the implementation of executemany().
   529         """
   529         """
   530         self.arraysize = 1
   530         self.arraysize = 1
   533         self._sessid = connection.sessionid
   533         self._sessid = connection.sessionid
   534         self._res = None
   534         self._res = None
   535         self._closed = None
   535         self._closed = None
   536         self._index = 0
   536         self._index = 0
   537 
   537 
   538         
   538 
   539     def close(self):
   539     def close(self):
   540         """Close the cursor now (rather than whenever __del__ is called).  The
   540         """Close the cursor now (rather than whenever __del__ is called).  The
   541         cursor will be unusable from this point forward; an Error (or subclass)
   541         cursor will be unusable from this point forward; an Error (or subclass)
   542         exception will be raised if any operation is attempted with the cursor.
   542         exception will be raised if any operation is attempted with the cursor.
   543         """
   543         """
   544         self._closed = True
   544         self._closed = True
   545 
   545 
   546             
   546 
   547     def execute(self, operation, parameters=None, eid_key=None, build_descr=True):
   547     def execute(self, operation, parameters=None, eid_key=None, build_descr=True):
   548         """Prepare and execute a database operation (query or command).
   548         """Prepare and execute a database operation (query or command).
   549         Parameters may be provided as sequence or mapping and will be bound to
   549         Parameters may be provided as sequence or mapping and will be bound to
   550         variables in the operation.  Variables are specified in a
   550         variables in the operation.  Variables are specified in a
   551         database-specific notation (see the module's paramstyle attribute for
   551         database-specific notation (see the module's paramstyle attribute for
   552         details).
   552         details).
   553         
   553 
   554         A reference to the operation will be retained by the cursor.  If the
   554         A reference to the operation will be retained by the cursor.  If the
   555         same operation object is passed in again, then the cursor can optimize
   555         same operation object is passed in again, then the cursor can optimize
   556         its behavior.  This is most effective for algorithms where the same
   556         its behavior.  This is most effective for algorithms where the same
   557         operation is used, but different parameters are bound to it (many
   557         operation is used, but different parameters are bound to it (many
   558         times).
   558         times).
   559         
   559 
   560         For maximum efficiency when reusing an operation, it is best to use the
   560         For maximum efficiency when reusing an operation, it is best to use the
   561         setinputsizes() method to specify the parameter types and sizes ahead
   561         setinputsizes() method to specify the parameter types and sizes ahead
   562         of time.  It is legal for a parameter to not match the predefined
   562         of time.  It is legal for a parameter to not match the predefined
   563         information; the implementation should compensate, possibly with a loss
   563         information; the implementation should compensate, possibly with a loss
   564         of efficiency.
   564         of efficiency.
   565         
   565 
   566         The parameters may also be specified as list of tuples to e.g. insert
   566         The parameters may also be specified as list of tuples to e.g. insert
   567         multiple rows in a single operation, but this kind of usage is
   567         multiple rows in a single operation, but this kind of usage is
   568         depreciated: executemany() should be used instead.
   568         depreciated: executemany() should be used instead.
   569         
   569 
   570         Return values are not defined by the DB-API, but this here it returns a
   570         Return values are not defined by the DB-API, but this here it returns a
   571         ResultSet object.
   571         ResultSet object.
   572         """
   572         """
   573         self._res = res = self._repo.execute(self._sessid, operation,
   573         self._res = res = self._repo.execute(self._sessid, operation,
   574                                              parameters, eid_key, build_descr)
   574                                              parameters, eid_key, build_descr)
   575         self.req.decorate_rset(res)
   575         self.req.decorate_rset(res)
   576         self._index = 0
   576         self._index = 0
   577         return res
   577         return res
   578         
   578 
   579 
   579 
   580     def executemany(self, operation, seq_of_parameters):
   580     def executemany(self, operation, seq_of_parameters):
   581         """Prepare a database operation (query or command) and then execute it
   581         """Prepare a database operation (query or command) and then execute it
   582         against all parameter sequences or mappings found in the sequence
   582         against all parameter sequences or mappings found in the sequence
   583         seq_of_parameters.
   583         seq_of_parameters.
   584         
   584 
   585         Modules are free to implement this method using multiple calls to the
   585         Modules are free to implement this method using multiple calls to the
   586         execute() method or by using array operations to have the database
   586         execute() method or by using array operations to have the database
   587         process the sequence as a whole in one call.
   587         process the sequence as a whole in one call.
   588         
   588 
   589         Use of this method for an operation which produces one or more result
   589         Use of this method for an operation which produces one or more result
   590         sets constitutes undefined behavior, and the implementation is
   590         sets constitutes undefined behavior, and the implementation is
   591         permitted (but not required) to raise an exception when it detects that
   591         permitted (but not required) to raise an exception when it detects that
   592         a result set has been created by an invocation of the operation.
   592         a result set has been created by an invocation of the operation.
   593         
   593 
   594         The same comments as for execute() also apply accordingly to this
   594         The same comments as for execute() also apply accordingly to this
   595         method.
   595         method.
   596         
   596 
   597         Return values are not defined.
   597         Return values are not defined.
   598         """
   598         """
   599         for parameters in seq_of_parameters:
   599         for parameters in seq_of_parameters:
   600             self.execute(operation, parameters)
   600             self.execute(operation, parameters)
   601             if self._res.rows is not None:
   601             if self._res.rows is not None:
   604 
   604 
   605 
   605 
   606     def fetchone(self):
   606     def fetchone(self):
   607         """Fetch the next row of a query result set, returning a single
   607         """Fetch the next row of a query result set, returning a single
   608         sequence, or None when no more data is available.
   608         sequence, or None when no more data is available.
   609         
   609 
   610         An Error (or subclass) exception is raised if the previous call to
   610         An Error (or subclass) exception is raised if the previous call to
   611         execute*() did not produce any result set or no call was issued yet.
   611         execute*() did not produce any result set or no call was issued yet.
   612         """
   612         """
   613         if self._res is None:
   613         if self._res is None:
   614             raise ProgrammingError('No result set')
   614             raise ProgrammingError('No result set')
   615         row = self._res.rows[self._index]
   615         row = self._res.rows[self._index]
   616         self._index += 1
   616         self._index += 1
   617         return row
   617         return row
   618 
   618 
   619         
   619 
   620     def fetchmany(self, size=None):
   620     def fetchmany(self, size=None):
   621         """Fetch the next set of rows of a query result, returning a sequence
   621         """Fetch the next set of rows of a query result, returning a sequence
   622         of sequences (e.g. a list of tuples). An empty sequence is returned
   622         of sequences (e.g. a list of tuples). An empty sequence is returned
   623         when no more rows are available.
   623         when no more rows are available.
   624         
   624 
   625         The number of rows to fetch per call is specified by the parameter.  If
   625         The number of rows to fetch per call is specified by the parameter.  If
   626         it is not given, the cursor's arraysize determines the number of rows
   626         it is not given, the cursor's arraysize determines the number of rows
   627         to be fetched. The method should try to fetch as many rows as indicated
   627         to be fetched. The method should try to fetch as many rows as indicated
   628         by the size parameter. If this is not possible due to the specified
   628         by the size parameter. If this is not possible due to the specified
   629         number of rows not being available, fewer rows may be returned.
   629         number of rows not being available, fewer rows may be returned.
   630         
   630 
   631         An Error (or subclass) exception is raised if the previous call to
   631         An Error (or subclass) exception is raised if the previous call to
   632         execute*() did not produce any result set or no call was issued yet.
   632         execute*() did not produce any result set or no call was issued yet.
   633         
   633 
   634         Note there are performance considerations involved with the size
   634         Note there are performance considerations involved with the size
   635         parameter.  For optimal performance, it is usually best to use the
   635         parameter.  For optimal performance, it is usually best to use the
   636         arraysize attribute.  If the size parameter is used, then it is best
   636         arraysize attribute.  If the size parameter is used, then it is best
   637         for it to retain the same value from one fetchmany() call to the next.
   637         for it to retain the same value from one fetchmany() call to the next.
   638         """
   638         """
   642             size = self.arraysize
   642             size = self.arraysize
   643         rows = self._res.rows[self._index:self._index + size]
   643         rows = self._res.rows[self._index:self._index + size]
   644         self._index += size
   644         self._index += size
   645         return rows
   645         return rows
   646 
   646 
   647         
   647 
   648     def fetchall(self):
   648     def fetchall(self):
   649         """Fetch all (remaining) rows of a query result, returning them as a
   649         """Fetch all (remaining) rows of a query result, returning them as a
   650         sequence of sequences (e.g. a list of tuples).  Note that the cursor's
   650         sequence of sequences (e.g. a list of tuples).  Note that the cursor's
   651         arraysize attribute can affect the performance of this operation.
   651         arraysize attribute can affect the performance of this operation.
   652         
   652 
   653         An Error (or subclass) exception is raised if the previous call to
   653         An Error (or subclass) exception is raised if the previous call to
   654         execute*() did not produce any result set or no call was issued yet.
   654         execute*() did not produce any result set or no call was issued yet.
   655         """
   655         """
   656         if self._res is None:
   656         if self._res is None:
   657             raise ProgrammingError('No result set')
   657             raise ProgrammingError('No result set')
   663 
   663 
   664 
   664 
   665     def setinputsizes(self, sizes):
   665     def setinputsizes(self, sizes):
   666         """This can be used before a call to execute*() to predefine memory
   666         """This can be used before a call to execute*() to predefine memory
   667         areas for the operation's parameters.
   667         areas for the operation's parameters.
   668         
   668 
   669         sizes is specified as a sequence -- one item for each input parameter.
   669         sizes is specified as a sequence -- one item for each input parameter.
   670         The item should be a Type Object that corresponds to the input that
   670         The item should be a Type Object that corresponds to the input that
   671         will be used, or it should be an integer specifying the maximum length
   671         will be used, or it should be an integer specifying the maximum length
   672         of a string parameter.  If the item is None, then no predefined memory
   672         of a string parameter.  If the item is None, then no predefined memory
   673         area will be reserved for that column (this is useful to avoid
   673         area will be reserved for that column (this is useful to avoid
   674         predefined areas for large inputs).
   674         predefined areas for large inputs).
   675         
   675 
   676         This method would be used before the execute*() method is invoked.
   676         This method would be used before the execute*() method is invoked.
   677         
   677 
   678         Implementations are free to have this method do nothing and users are
   678         Implementations are free to have this method do nothing and users are
   679         free to not use it.
   679         free to not use it.
   680         """
   680         """
   681         pass
   681         pass
   682 
   682 
   683         
   683 
   684     def setoutputsize(self, size, column=None):
   684     def setoutputsize(self, size, column=None):
   685         """Set a column buffer size for fetches of large columns (e.g. LONGs,
   685         """Set a column buffer size for fetches of large columns (e.g. LONGs,
   686         BLOBs, etc.).  The column is specified as an index into the result
   686         BLOBs, etc.).  The column is specified as an index into the result
   687         sequence.  Not specifying the column will set the default size for all
   687         sequence.  Not specifying the column will set the default size for all
   688         large columns in the cursor.
   688         large columns in the cursor.
   689         
   689 
   690         This method would be used before the execute*() method is invoked.
   690         This method would be used before the execute*() method is invoked.
   691         
   691 
   692         Implementations are free to have this method do nothing and users are
   692         Implementations are free to have this method do nothing and users are
   693         free to not use it.
   693         free to not use it.
   694         """    
   694         """
   695         pass
   695         pass
   696 
   696 
   697     
   697 
   698 class LogCursor(Cursor):
   698 class LogCursor(Cursor):
   699     """override the standard cursor to log executed queries"""
   699     """override the standard cursor to log executed queries"""
   700     
   700 
   701     def execute(self, operation, parameters=None, eid_key=None, build_descr=True):
   701     def execute(self, operation, parameters=None, eid_key=None, build_descr=True):
   702         """override the standard cursor to log executed queries"""
   702         """override the standard cursor to log executed queries"""
   703         tstart, cstart = time(), clock()
   703         tstart, cstart = time(), clock()
   704         rset = Cursor.execute(self, operation, parameters, eid_key, build_descr)
   704         rset = Cursor.execute(self, operation, parameters, eid_key, build_descr)
   705         self.connection.executed_queries.append((operation, parameters,
   705         self.connection.executed_queries.append((operation, parameters,