server/session.py
changeset 10351 91e63306e277
parent 10347 52a976c5d27a
child 10352 bab2befaac9b
equal deleted inserted replaced
10350:31327bd26931 10351:91e63306e277
     1 # copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     1 # copyright 2003-2015 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
   386 
   386 
   387     Holds all connection related data
   387     Holds all connection related data
   388 
   388 
   389     Database connection resources:
   389     Database connection resources:
   390 
   390 
   391       :attr:`running_dbapi_query`, boolean flag telling if the executing query
   391       :attr:`hooks_in_progress`, boolean flag telling if the executing
   392       is coming from a dbapi connection or is a query from within the repository
   392       query is coming from a repoapi connection or is a query from
       
   393       within the repository (e.g. started by hooks)
   393 
   394 
   394       :attr:`cnxset`, the connections set to use to execute queries on sources.
   395       :attr:`cnxset`, the connections set to use to execute queries on sources.
   395       If the transaction is read only, the connection set may be freed between
   396       If the transaction is read only, the connection set may be freed between
   396       actual queries. This allows multiple connections with a reasonably low
   397       actual queries. This allows multiple connections with a reasonably low
   397       connection set pool size.  Control mechanism is detailed below.
   398       connection set pool size.  Control mechanism is detailed below.
   443       read/write security is currently activated.
   444       read/write security is currently activated.
   444 
   445 
   445     """
   446     """
   446 
   447 
   447     is_request = False
   448     is_request = False
       
   449     hooks_in_progress = False
   448     mode = 'read'
   450     mode = 'read'
   449 
   451 
   450     def __init__(self, session, cnxid=None, session_handled=False):
   452     def __init__(self, session, cnxid=None, session_handled=False):
   451         # using super(Connection, self) confuse some test hack
   453         # using super(Connection, self) confuse some test hack
   452         RequestSessionBase.__init__(self, session.vreg)
   454         RequestSessionBase.__init__(self, session.vreg)
   482 
   484 
   483         #: connection set used to execute queries on sources
   485         #: connection set used to execute queries on sources
   484         self._cnxset = None
   486         self._cnxset = None
   485         #: CnxSetTracker used to report cnxset usage
   487         #: CnxSetTracker used to report cnxset usage
   486         self._cnxset_tracker = CnxSetTracker()
   488         self._cnxset_tracker = CnxSetTracker()
   487         #: is this connection from a client or internal to the repo
       
   488         self.running_dbapi_query = True
       
   489         # internal (root) session
   489         # internal (root) session
   490         self.is_internal_session = isinstance(session.user, InternalManager)
   490         self.is_internal_session = isinstance(session.user, InternalManager)
   491 
   491 
   492         #: dict containing arbitrary data cleared at the end of the transaction
   492         #: dict containing arbitrary data cleared at the end of the transaction
   493         self.transaction_data = {}
   493         self.transaction_data = {}
   524             self.set_language(self.user.prefered_language())
   524             self.set_language(self.user.prefered_language())
   525         else:
   525         else:
   526             self._set_user(session.user)
   526             self._set_user(session.user)
   527 
   527 
   528 
   528 
   529     # live cycle handling ####################################################
   529     # life cycle handling ####################################################
   530 
   530 
   531     def __enter__(self):
   531     def __enter__(self):
   532         assert self._open is None # first opening
   532         assert self._open is None # first opening
   533         self._open = True
   533         self._open = True
   534         return self
   534         return self
   537         assert self._open # actually already open
   537         assert self._open # actually already open
   538         assert self._cnxset_count == 0
   538         assert self._cnxset_count == 0
   539         self.rollback()
   539         self.rollback()
   540         self._open = False
   540         self._open = False
   541 
   541 
   542 
   542     @contextmanager
       
   543     def running_hooks_ops(self):
       
   544         """this context manager should be called whenever hooks or operations
       
   545         are about to be run (but after hook selection)
       
   546 
       
   547         It will help the undo logic record pertinent metadata or some
       
   548         hooks to run (or not) depending on who/what issued the query.
       
   549         """
       
   550         prevmode = self.hooks_in_progress
       
   551         self.hooks_in_progress = True
       
   552         yield
       
   553         self.hooks_in_progress = prevmode
   543 
   554 
   544     # shared data handling ###################################################
   555     # shared data handling ###################################################
   545 
   556 
   546     @property
   557     @property
   547     def data(self):
   558     def data(self):
   941         return self._read_security
   952         return self._read_security
   942 
   953 
   943     @read_security.setter
   954     @read_security.setter
   944     @_open_only
   955     @_open_only
   945     def read_security(self, activated):
   956     def read_security(self, activated):
   946         oldmode = self._read_security
       
   947         self._read_security = activated
   957         self._read_security = activated
   948         # running_dbapi_query used to detect hooks triggered by a 'dbapi' query
       
   949         # (eg not issued on the session). This is tricky since we the execution
       
   950         # model of a (write) user query is:
       
   951         #
       
   952         # repository.execute (security enabled)
       
   953         #  \-> querier.execute
       
   954         #       \-> repo.glob_xxx (add/update/delete entity/relation)
       
   955         #            \-> deactivate security before calling hooks
       
   956         #                 \-> WE WANT TO CHECK QUERY NATURE HERE
       
   957         #                      \-> potentially, other calls to querier.execute
       
   958         #
       
   959         # so we can't rely on simply checking session.read_security, but
       
   960         # recalling the first transition from DEFAULT_SECURITY to something
       
   961         # else (False actually) is not perfect but should be enough
       
   962         #
       
   963         # also reset running_dbapi_query to true when we go back to
       
   964         # DEFAULT_SECURITY
       
   965         self.running_dbapi_query = (oldmode is DEFAULT_SECURITY
       
   966                                     or activated is DEFAULT_SECURITY)
       
   967 
   958 
   968     # undo support ############################################################
   959     # undo support ############################################################
   969 
   960 
   970     @_open_only
   961     @_open_only
   971     def ertype_supports_undo(self, ertype):
   962     def ertype_supports_undo(self, ertype):
  1096                 processed = []
  1087                 processed = []
  1097                 self.commit_state = 'precommit'
  1088                 self.commit_state = 'precommit'
  1098                 if debug:
  1089                 if debug:
  1099                     print self.commit_state, '*' * 20
  1090                     print self.commit_state, '*' * 20
  1100                 try:
  1091                 try:
  1101                     while self.pending_operations:
  1092                     with self.running_hooks_ops():
  1102                         operation = self.pending_operations.pop(0)
  1093                         while self.pending_operations:
  1103                         operation.processed = 'precommit'
  1094                             operation = self.pending_operations.pop(0)
  1104                         processed.append(operation)
  1095                             operation.processed = 'precommit'
  1105                         if debug:
  1096                             processed.append(operation)
  1106                             print operation
  1097                             if debug:
  1107                         operation.handle_event('precommit_event')
  1098                                 print operation
       
  1099                             operation.handle_event('precommit_event')
  1108                     self.pending_operations[:] = processed
  1100                     self.pending_operations[:] = processed
  1109                     self.debug('precommit transaction %s done', self.connectionid)
  1101                     self.debug('precommit transaction %s done', self.connectionid)
  1110                 except BaseException:
  1102                 except BaseException:
  1111                     # if error on [pre]commit:
  1103                     # if error on [pre]commit:
  1112                     #
  1104                     #
  1119                     # instead of having to implements rollback, revertprecommit
  1111                     # instead of having to implements rollback, revertprecommit
  1120                     # and revertcommit, that will be enough in mont case.
  1112                     # and revertcommit, that will be enough in mont case.
  1121                     operation.failed = True
  1113                     operation.failed = True
  1122                     if debug:
  1114                     if debug:
  1123                         print self.commit_state, '*' * 20
  1115                         print self.commit_state, '*' * 20
  1124                     for operation in reversed(processed):
  1116                     with self.running_hooks_ops():
  1125                         if debug:
  1117                         for operation in reversed(processed):
  1126                             print operation
  1118                             if debug:
  1127                         try:
  1119                                 print operation
  1128                             operation.handle_event('revertprecommit_event')
  1120                             try:
  1129                         except BaseException:
  1121                                 operation.handle_event('revertprecommit_event')
  1130                             self.critical('error while reverting precommit',
  1122                             except BaseException:
  1131                                           exc_info=True)
  1123                                 self.critical('error while reverting precommit',
       
  1124                                               exc_info=True)
  1132                     # XXX use slice notation since self.pending_operations is a
  1125                     # XXX use slice notation since self.pending_operations is a
  1133                     # read-only property.
  1126                     # read-only property.
  1134                     self.pending_operations[:] = processed + self.pending_operations
  1127                     self.pending_operations[:] = processed + self.pending_operations
  1135                     self.rollback(free_cnxset)
  1128                     self.rollback(free_cnxset)
  1136                     raise
  1129                     raise
  1137                 self.cnxset.commit()
  1130                 self.cnxset.commit()
  1138                 self.commit_state = 'postcommit'
  1131                 self.commit_state = 'postcommit'
  1139                 if debug:
  1132                 if debug:
  1140                     print self.commit_state, '*' * 20
  1133                     print self.commit_state, '*' * 20
  1141                 while self.pending_operations:
  1134                 with self.running_hooks_ops():
  1142                     operation = self.pending_operations.pop(0)
  1135                     while self.pending_operations:
  1143                     if debug:
  1136                         operation = self.pending_operations.pop(0)
  1144                         print operation
  1137                         if debug:
  1145                     operation.processed = 'postcommit'
  1138                             print operation
  1146                     try:
  1139                         operation.processed = 'postcommit'
  1147                         operation.handle_event('postcommit_event')
  1140                         try:
  1148                     except BaseException:
  1141                             operation.handle_event('postcommit_event')
  1149                         self.critical('error while postcommit',
  1142                         except BaseException:
  1150                                       exc_info=sys.exc_info())
  1143                             self.critical('error while postcommit',
       
  1144                                           exc_info=sys.exc_info())
  1151                 self.debug('postcommit transaction %s done', self.connectionid)
  1145                 self.debug('postcommit transaction %s done', self.connectionid)
  1152                 return self.transaction_uuid(set=False)
  1146                 return self.transaction_uuid(set=False)
  1153         finally:
  1147         finally:
  1154             self._session_timestamp.touch()
  1148             self._session_timestamp.touch()
  1155             if free_cnxset:
  1149             if free_cnxset: