server/sqlutils.py
changeset 9466 c3a5f4507f12
parent 9447 0636c4960259
child 9724 e45bf9baa7b7
equal deleted inserted replaced
9465:86d4b41ae339 9466:c3a5f4507f12
     1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     1 # copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     3 #
     3 #
     4 # This file is part of CubicWeb.
     4 # This file is part of CubicWeb.
     5 #
     5 #
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    18 """SQL utilities functions and classes."""
    18 """SQL utilities functions and classes."""
    19 
    19 
    20 __docformat__ = "restructuredtext en"
    20 __docformat__ = "restructuredtext en"
    21 
    21 
       
    22 import sys
    22 import os
    23 import os
    23 import re
    24 import re
    24 import subprocess
    25 import subprocess
    25 from datetime import datetime, date
    26 from os.path import abspath
    26 from itertools import ifilter
    27 from itertools import ifilter
       
    28 from logging import getLogger
    27 
    29 
    28 from logilab import database as db, common as lgc
    30 from logilab import database as db, common as lgc
    29 from logilab.common.shellutils import ProgressBar
    31 from logilab.common.shellutils import ProgressBar
    30 from logilab.common.date import todate, todatetime, utcdatetime, utctime
    32 from logilab.common.deprecation import deprecated
       
    33 from logilab.common.logging_ext import set_log_methods
    31 from logilab.database.sqlgen import SQLGenerator
    34 from logilab.database.sqlgen import SQLGenerator
    32 
    35 
    33 from cubicweb import Binary, ConfigurationError, server
    36 from cubicweb import Binary, ConfigurationError
    34 from cubicweb.uilib import remove_html_tags
    37 from cubicweb.uilib import remove_html_tags
    35 from cubicweb.schema import PURE_VIRTUAL_RTYPES
    38 from cubicweb.schema import PURE_VIRTUAL_RTYPES
    36 from cubicweb.server import SQL_CONNECT_HOOKS
    39 from cubicweb.server import SQL_CONNECT_HOOKS
    37 from cubicweb.server.utils import crypt_password
    40 from cubicweb.server.utils import crypt_password
    38 from rql.utils import RQL_FUNCTIONS_REGISTRY
       
    39 
    41 
    40 lgc.USE_MX_DATETIME = False
    42 lgc.USE_MX_DATETIME = False
    41 SQL_PREFIX = 'cw_'
    43 SQL_PREFIX = 'cw_'
    42 
    44 
    43 def _run_command(cmd):
    45 def _run_command(cmd):
   176     cmds += ['DROP TABLE %s;' % name
   178     cmds += ['DROP TABLE %s;' % name
   177              for name in ifilter(_SQL_DROP_ALL_USER_TABLES_FILTER_FUNCTION, dbhelper.list_tables(sqlcursor))]
   179              for name in ifilter(_SQL_DROP_ALL_USER_TABLES_FILTER_FUNCTION, dbhelper.list_tables(sqlcursor))]
   178     return '\n'.join(cmds)
   180     return '\n'.join(cmds)
   179 
   181 
   180 
   182 
       
   183 class ConnectionWrapper(object):
       
   184     """handle connection to the system source, at some point associated to a
       
   185     :class:`Session`
       
   186     """
       
   187 
       
   188     # since 3.19, we only have to manage the system source connection
       
   189     def __init__(self, system_source):
       
   190         # dictionary of (source, connection), indexed by sources'uri
       
   191         self._source = system_source
       
   192         self.cnx = system_source.get_connection()
       
   193         self.cu = self.cnx.cursor()
       
   194 
       
   195     def commit(self):
       
   196         """commit the current transaction for this user"""
       
   197         # let exception propagates
       
   198         self.cnx.commit()
       
   199 
       
   200     def rollback(self):
       
   201         """rollback the current transaction for this user"""
       
   202         # catch exceptions, rollback other sources anyway
       
   203         try:
       
   204             self.cnx.rollback()
       
   205         except Exception:
       
   206             self._source.critical('rollback error', exc_info=sys.exc_info())
       
   207             # error on rollback, the connection is much probably in a really
       
   208             # bad state. Replace it by a new one.
       
   209             self.reconnect()
       
   210 
       
   211     def close(self, i_know_what_i_do=False):
       
   212         """close all connections in the set"""
       
   213         if i_know_what_i_do is not True: # unexpected closing safety belt
       
   214             raise RuntimeError('connections set shouldn\'t be closed')
       
   215         try:
       
   216             self.cu.close()
       
   217             self.cu = None
       
   218         except Exception:
       
   219             pass
       
   220         try:
       
   221             self.cnx.close()
       
   222             self.cnx = None
       
   223         except Exception:
       
   224             pass
       
   225 
       
   226     # internals ###############################################################
       
   227 
       
   228     def cnxset_freed(self):
       
   229         """connections set is being freed from a session"""
       
   230         pass # no nothing by default
       
   231 
       
   232     def reconnect(self):
       
   233         """reopen a connection for this source or all sources if none specified
       
   234         """
       
   235         try:
       
   236             # properly close existing connection if any
       
   237             self.cnx.close()
       
   238         except Exception:
       
   239             pass
       
   240         self._source.info('trying to reconnect')
       
   241         self.cnx = self._source.get_connection()
       
   242         self.cu = self.cnx.cursor()
       
   243 
       
   244     @deprecated('[3.19] use .cu instead')
       
   245     def __getitem__(self, uri):
       
   246         assert uri == 'system'
       
   247         return self.cu
       
   248 
       
   249     @deprecated('[3.19] use repo.system_source instead')
       
   250     def source(self, uid):
       
   251         assert uid == 'system'
       
   252         return self._source
       
   253 
       
   254     @deprecated('[3.19] use .cnx instead')
       
   255     def connection(self, uid):
       
   256         assert uid == 'system'
       
   257         return self.cnx
       
   258 
       
   259 
       
   260 class SqliteConnectionWrapper(ConnectionWrapper):
       
   261     """Sqlite specific connection wrapper: close the connection each time it's
       
   262     freed (and reopen it later when needed)
       
   263     """
       
   264     def __init__(self, system_source):
       
   265         # don't call parent's __init__, we don't want to initiate the connection
       
   266         self._source = system_source
       
   267 
       
   268     _cnx = None
       
   269 
       
   270     def cnxset_freed(self):
       
   271         self.cu.close()
       
   272         self.cnx.close()
       
   273         self.cnx = self.cu = None
       
   274 
       
   275     @property
       
   276     def cnx(self):
       
   277         if self._cnx is None:
       
   278             self._cnx = self._source.get_connection()
       
   279             self._cu = self._cnx.cursor()
       
   280         return self._cnx
       
   281     @cnx.setter
       
   282     def cnx(self, value):
       
   283         self._cnx = value
       
   284 
       
   285     @property
       
   286     def cu(self):
       
   287         if self._cnx is None:
       
   288             self._cnx = self._source.get_connection()
       
   289             self._cu = self._cnx.cursor()
       
   290         return self._cu
       
   291     @cu.setter
       
   292     def cu(self, value):
       
   293         self._cu = value
       
   294 
       
   295 
   181 class SQLAdapterMixIn(object):
   296 class SQLAdapterMixIn(object):
   182     """Mixin for SQL data sources, getting a connection from a configuration
   297     """Mixin for SQL data sources, getting a connection from a configuration
   183     dictionary and handling connection locking
   298     dictionary and handling connection locking
   184     """
   299     """
       
   300     cnx_wrap = ConnectionWrapper
   185 
   301 
   186     def __init__(self, source_config):
   302     def __init__(self, source_config):
   187         try:
   303         try:
   188             self.dbdriver = source_config['db-driver'].lower()
   304             self.dbdriver = source_config['db-driver'].lower()
   189             dbname = source_config['db-name']
   305             dbname = source_config['db-name']
   207         self.InterfaceError = dbapi_module.InterfaceError
   323         self.InterfaceError = dbapi_module.InterfaceError
   208         self.DbapiError = dbapi_module.Error
   324         self.DbapiError = dbapi_module.Error
   209         self._binary = self.dbhelper.binary_value
   325         self._binary = self.dbhelper.binary_value
   210         self._process_value = dbapi_module.process_value
   326         self._process_value = dbapi_module.process_value
   211         self._dbencoding = dbencoding
   327         self._dbencoding = dbencoding
       
   328         if self.dbdriver == 'sqlite':
       
   329             self.cnx_wrap = SqliteConnectionWrapper
       
   330             self.dbhelper.dbname = abspath(self.dbhelper.dbname)
       
   331 
       
   332     def wrapped_connection(self):
       
   333         """open and return a connection to the database, wrapped into a class
       
   334         handling reconnection and all
       
   335         """
       
   336         return self.cnx_wrap(self)
   212 
   337 
   213     def get_connection(self):
   338     def get_connection(self):
   214         """open and return a connection to the database"""
   339         """open and return a connection to the database"""
   215         return self.dbhelper.get_connection()
   340         return self.dbhelper.get_connection()
   216 
   341 
   318 
   443 
   319     # these are overridden by set_log_methods below
   444     # these are overridden by set_log_methods below
   320     # only defining here to prevent pylint from complaining
   445     # only defining here to prevent pylint from complaining
   321     info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
   446     info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
   322 
   447 
   323 from logging import getLogger
       
   324 from cubicweb import set_log_methods
       
   325 set_log_methods(SQLAdapterMixIn, getLogger('cubicweb.sqladapter'))
   448 set_log_methods(SQLAdapterMixIn, getLogger('cubicweb.sqladapter'))
   326 
   449 
   327 
   450 
   328 class SqliteCnxLoggingWrapper(object):
   451 # connection initialization functions ##########################################
   329     def __init__(self, source=None):
       
   330         self.source = source
       
   331         self._cnx = None
       
   332 
       
   333     def cursor(self):
       
   334         # sqlite connections can only be used in the same thread, so
       
   335         # create a new one each time necessary. If it appears to be time
       
   336         # consuming, find another way
       
   337         if self._cnx is None:
       
   338             # direct access to SQLAdapterMixIn to get an unwrapped connection
       
   339             self._cnx = SQLAdapterMixIn.get_connection(self.source)
       
   340             if server.DEBUG & server.DBG_SQL:
       
   341                 print 'sql cnx OPEN', self._cnx
       
   342         return self._cnx.cursor()
       
   343 
       
   344     def commit(self):
       
   345         if self._cnx is not None:
       
   346             if server.DEBUG & (server.DBG_SQL | server.DBG_RQL):
       
   347                 print 'sql cnx COMMIT', self._cnx
       
   348             self._cnx.commit()
       
   349 
       
   350     def rollback(self):
       
   351         if self._cnx is not None:
       
   352             if server.DEBUG & (server.DBG_SQL | server.DBG_RQL):
       
   353                 print 'sql cnx ROLLBACK', self._cnx
       
   354             self._cnx.rollback()
       
   355 
       
   356     def close(self):
       
   357         if self._cnx is not None:
       
   358             if server.DEBUG & server.DBG_SQL:
       
   359                 print 'sql cnx CLOSE', self._cnx
       
   360             self._cnx.close()
       
   361             self._cnx = None
       
   362 
       
   363 
   452 
   364 def init_sqlite_connexion(cnx):
   453 def init_sqlite_connexion(cnx):
   365 
   454 
   366     class group_concat(object):
   455     class group_concat(object):
   367         def __init__(self):
   456         def __init__(self):