server/sources/native.py
changeset 4964 d9e8af8a7a42
parent 4943 7f5b83578fec
child 5011 57c4c6399b44
equal deleted inserted replaced
4963:6b0832bbd1da 4964:d9e8af8a7a42
    17 
    17 
    18 from pickle import loads, dumps
    18 from pickle import loads, dumps
    19 from threading import Lock
    19 from threading import Lock
    20 from datetime import datetime
    20 from datetime import datetime
    21 from base64 import b64decode, b64encode
    21 from base64 import b64decode, b64encode
       
    22 from contextlib import contextmanager
    22 
    23 
    23 from logilab.common.compat import any
    24 from logilab.common.compat import any
    24 from logilab.common.cache import Cache
    25 from logilab.common.cache import Cache
    25 from logilab.common.decorators import cached, clear_cache
    26 from logilab.common.decorators import cached, clear_cache
    26 from logilab.common.configuration import Method
    27 from logilab.common.configuration import Method
   189         self.do_fti = not repo.config['delay-full-text-indexation']
   190         self.do_fti = not repo.config['delay-full-text-indexation']
   190         # sql queries cache
   191         # sql queries cache
   191         self._cache = Cache(repo.config['rql-cache-size'])
   192         self._cache = Cache(repo.config['rql-cache-size'])
   192         self._temp_table_data = {}
   193         self._temp_table_data = {}
   193         self._eid_creation_lock = Lock()
   194         self._eid_creation_lock = Lock()
       
   195         # (etype, attr) / storage mapping
       
   196         self._storages = {}
   194         # XXX no_sqlite_wrap trick since we've a sqlite locking pb when
   197         # XXX no_sqlite_wrap trick since we've a sqlite locking pb when
   195         # running unittest_multisources with the wrapping below
   198         # running unittest_multisources with the wrapping below
   196         if self.dbdriver == 'sqlite' and \
   199         if self.dbdriver == 'sqlite' and \
   197                not getattr(repo.config, 'no_sqlite_wrap', False):
   200                not getattr(repo.config, 'no_sqlite_wrap', False):
   198             from cubicweb.server.sources.extlite import ConnectionWrapper
   201             from cubicweb.server.sources.extlite import ConnectionWrapper
   264     def map_attribute(self, etype, attr, cb):
   267     def map_attribute(self, etype, attr, cb):
   265         self._rql_sqlgen.attr_map['%s.%s' % (etype, attr)] = cb
   268         self._rql_sqlgen.attr_map['%s.%s' % (etype, attr)] = cb
   266 
   269 
   267     def unmap_attribute(self, etype, attr):
   270     def unmap_attribute(self, etype, attr):
   268         self._rql_sqlgen.attr_map.pop('%s.%s' % (etype, attr), None)
   271         self._rql_sqlgen.attr_map.pop('%s.%s' % (etype, attr), None)
       
   272 
       
   273     def set_storage(self, etype, attr, storage):
       
   274         storage_dict = self._storages.setdefault(etype, {})
       
   275         storage_dict[attr] = storage
       
   276         self.map_attribute(etype, attr, storage.sqlgen_callback)
       
   277 
       
   278     def unset_storage(self, etype, attr):
       
   279         self._storages[etype].pop(attr)
       
   280         # if etype has no storage left, remove the entry
       
   281         if not self._storages[etype]:
       
   282             del self._storages[etype]
       
   283         self.unmap_attribute(etype, attr)
   269 
   284 
   270     # ISource interface #######################################################
   285     # ISource interface #######################################################
   271 
   286 
   272     def compile_rql(self, rql, sols):
   287     def compile_rql(self, rql, sols):
   273         rqlst = self.repo.vreg.rqlhelper.parse(rql)
   288         rqlst = self.repo.vreg.rqlhelper.parse(rql)
   400                 try:
   415                 try:
   401                     del self._temp_table_data[table]
   416                     del self._temp_table_data[table]
   402                 except KeyError:
   417                 except KeyError:
   403                     continue
   418                     continue
   404 
   419 
       
   420     @contextmanager
       
   421     def _storage_handler(self, entity, event):
       
   422         # 1/ memorize values as they are before the storage is called.
       
   423         #    For instance, the BFSStorage will replace the `data`
       
   424         #    binary value with a Binary containing the destination path
       
   425         #    on the filesystem. To make the entity.data usage absolutely
       
   426         #    transparent, we'll have to reset entity.data to its binary
       
   427         #    value once the SQL query will be executed
       
   428         orig_values = {}
       
   429         etype = entity.__regid__
       
   430         for attr, storage in self._storages.get(etype, {}).items():
       
   431             if attr in entity.edited_attributes:
       
   432                 orig_values[attr] = entity[attr]
       
   433                 handler = getattr(storage, 'entity_%s' % event)
       
   434                 handler(entity, attr)
       
   435         yield # 2/ execute the source's instructions
       
   436         # 3/ restore original values
       
   437         for attr, value in orig_values.items():
       
   438             entity[attr] = value
       
   439 
   405     def add_entity(self, session, entity):
   440     def add_entity(self, session, entity):
   406         """add a new entity to the source"""
   441         """add a new entity to the source"""
   407         attrs = self.preprocess_entity(entity)
   442         with self._storage_handler(entity, 'added'):
   408         sql = self.sqlgen.insert(SQL_PREFIX + entity.__regid__, attrs)
   443             attrs = self.preprocess_entity(entity)
   409         self.doexec(session, sql, attrs)
   444             sql = self.sqlgen.insert(SQL_PREFIX + entity.__regid__, attrs)
   410         if session.undoable_action('C', entity.__regid__):
   445             self.doexec(session, sql, attrs)
   411             self._record_tx_action(session, 'tx_entity_actions', 'C',
   446             if session.undoable_action('C', entity.__regid__):
   412                                    etype=entity.__regid__, eid=entity.eid)
   447                 self._record_tx_action(session, 'tx_entity_actions', 'C',
       
   448                                        etype=entity.__regid__, eid=entity.eid)
   413 
   449 
   414     def update_entity(self, session, entity):
   450     def update_entity(self, session, entity):
   415         """replace an entity in the source"""
   451         """replace an entity in the source"""
   416         attrs = self.preprocess_entity(entity)
   452         with self._storage_handler(entity, 'updated'):
   417         if session.undoable_action('U', entity.__regid__):
   453             attrs = self.preprocess_entity(entity)
   418             changes = self._save_attrs(session, entity, attrs)
   454             if session.undoable_action('U', entity.__regid__):
   419             self._record_tx_action(session, 'tx_entity_actions', 'U',
   455                 changes = self._save_attrs(session, entity, attrs)
   420                                    etype=entity.__regid__, eid=entity.eid,
   456                 self._record_tx_action(session, 'tx_entity_actions', 'U',
   421                                    changes=self._binary(dumps(changes)))
   457                                        etype=entity.__regid__, eid=entity.eid,
   422         sql = self.sqlgen.update(SQL_PREFIX + entity.__regid__, attrs,
   458                                        changes=self._binary(dumps(changes)))
   423                                  ['cw_eid'])
   459             sql = self.sqlgen.update(SQL_PREFIX + entity.__regid__, attrs,
   424         self.doexec(session, sql, attrs)
   460                                      ['cw_eid'])
       
   461             self.doexec(session, sql, attrs)
   425 
   462 
   426     def delete_entity(self, session, entity):
   463     def delete_entity(self, session, entity):
   427         """delete an entity from the source"""
   464         """delete an entity from the source"""
   428         if session.undoable_action('D', entity.__regid__):
   465         with self._storage_handler(entity, 'deleted'):
   429             attrs = [SQL_PREFIX + r.type
   466             if session.undoable_action('D', entity.__regid__):
   430                      for r in entity.e_schema.subject_relations()
   467                 attrs = [SQL_PREFIX + r.type
   431                      if (r.final or r.inlined) and not r in VIRTUAL_RTYPES]
   468                          for r in entity.e_schema.subject_relations()
   432             changes = self._save_attrs(session, entity, attrs)
   469                          if (r.final or r.inlined) and not r in VIRTUAL_RTYPES]
   433             self._record_tx_action(session, 'tx_entity_actions', 'D',
   470                 changes = self._save_attrs(session, entity, attrs)
   434                                    etype=entity.__regid__, eid=entity.eid,
   471                 self._record_tx_action(session, 'tx_entity_actions', 'D',
   435                                    changes=self._binary(dumps(changes)))
   472                                        etype=entity.__regid__, eid=entity.eid,
   436         attrs = {'cw_eid': entity.eid}
   473                                        changes=self._binary(dumps(changes)))
   437         sql = self.sqlgen.delete(SQL_PREFIX + entity.__regid__, attrs)
   474             attrs = {'cw_eid': entity.eid}
   438         self.doexec(session, sql, attrs)
   475             sql = self.sqlgen.delete(SQL_PREFIX + entity.__regid__, attrs)
       
   476             self.doexec(session, sql, attrs)
   439 
   477 
   440     def _add_relation(self, session, subject, rtype, object, inlined=False):
   478     def _add_relation(self, session, subject, rtype, object, inlined=False):
   441         """add a relation to the source"""
   479         """add a relation to the source"""
   442         if inlined is False:
   480         if inlined is False:
   443             attrs = {'eid_from': subject, 'eid_to': object}
   481             attrs = {'eid_from': subject, 'eid_to': object}