server/repository.py
changeset 7514 32081892850e
parent 7399 972ed1843bd8
parent 7513 8f4422391e5a
child 7543 570522300e22
equal deleted inserted replaced
7497:7beb71d76d82 7514:32081892850e
    32 __docformat__ = "restructuredtext en"
    32 __docformat__ = "restructuredtext en"
    33 
    33 
    34 import sys
    34 import sys
    35 import threading
    35 import threading
    36 import Queue
    36 import Queue
       
    37 from warnings import warn
    37 from itertools import chain
    38 from itertools import chain
    38 from os.path import join
    39 from os.path import join
    39 from datetime import datetime
    40 from datetime import datetime
    40 from time import time, localtime, strftime
    41 from time import time, localtime, strftime
    41 
    42 
    80     for rschema in entity.e_schema.object_relations():
    81     for rschema in entity.e_schema.object_relations():
    81         rtype = str(rschema)
    82         rtype = str(rschema)
    82         if rtype in schema.VIRTUAL_RTYPES or (rtype, 'object') in NO_CACHE_RELATIONS:
    83         if rtype in schema.VIRTUAL_RTYPES or (rtype, 'object') in NO_CACHE_RELATIONS:
    83             continue
    84             continue
    84         entity.cw_set_relation_cache(rtype, 'object', session.empty_rset())
    85         entity.cw_set_relation_cache(rtype, 'object', session.empty_rset())
    85     # set inlined relation cache before call to after_add_entity
       
    86     for attr, value in relations:
       
    87         session.update_rel_cache_add(entity.eid, attr, value)
       
    88         del_existing_rel_if_needed(session, entity.eid, attr, value)
       
    89 
    86 
    90 def del_existing_rel_if_needed(session, eidfrom, rtype, eidto):
    87 def del_existing_rel_if_needed(session, eidfrom, rtype, eidto):
    91     """delete existing relation when adding a new one if card is 1 or ?
    88     """delete existing relation when adding a new one if card is 1 or ?
    92 
    89 
    93     have to be done once the new relation has been inserted to avoid having
    90     have to be done once the new relation has been inserted to avoid having
    94     an entity without a relation for some time
    91     an entity without a relation for some time
    95 
    92 
    96     this kind of behaviour has to be done in the repository so we don't have
    93     this kind of behaviour has to be done in the repository so we don't have
    97     hooks order hazardness
    94     hooks order hazardness
    98     """
    95     """
    99     # skip that for internal session or if integrity explicitly disabled
    96     # skip that if integrity explicitly disabled
   100     #
    97     if not session.is_hook_category_activated('activeintegrity'):
   101     # XXX we should imo rely on the orm to first fetch existing entity if any
       
   102     # then delete it.
       
   103     if session.is_internal_session \
       
   104            or not session.is_hook_category_activated('activeintegrity'):
       
   105         return
    98         return
   106     card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality')
    99     rdef = session.rtype_eids_rdef(rtype, eidfrom, eidto)
       
   100     card = rdef.cardinality
   107     # one may be tented to check for neweids but this may cause more than one
   101     # one may be tented to check for neweids but this may cause more than one
   108     # relation even with '1?'  cardinality if thoses relations are added in the
   102     # relation even with '1?'  cardinality if thoses relations are added in the
   109     # same transaction where the entity is being created. This never occurs from
   103     # same transaction where the entity is being created. This never occurs from
   110     # the web interface but may occurs during test or dbapi connection (though
   104     # the web interface but may occurs during test or dbapi connection (though
   111     # not expected for this).  So: don't do it, we pretend to ensure repository
   105     # not expected for this).  So: don't do it, we pretend to ensure repository
   113     #
   107     #
   114     # notes:
   108     # notes:
   115     # * inlined relations will be implicitly deleted for the subject entity
   109     # * inlined relations will be implicitly deleted for the subject entity
   116     # * we don't want read permissions to be applied but we want delete
   110     # * we don't want read permissions to be applied but we want delete
   117     #   permission to be checked
   111     #   permission to be checked
   118     if card[0] in '1?' and not session.repo.schema.rschema(rtype).inlined:
   112     if card[0] in '1?':
   119         with security_enabled(session, read=False):
   113         with security_enabled(session, read=False):
   120             session.execute('DELETE X %s Y WHERE X eid %%(x)s, '
   114             session.execute('DELETE X %s Y WHERE X eid %%(x)s, '
   121                             'NOT Y eid %%(y)s' % rtype,
   115                             'NOT Y eid %%(y)s' % rtype,
   122                                 {'x': eidfrom, 'y': eidto})
   116                                 {'x': eidfrom, 'y': eidto})
   123     if card[1] in '1?':
   117     if card[1] in '1?':
  1116         # mark eid as being deleted in session info and setup cache update
  1110         # mark eid as being deleted in session info and setup cache update
  1117         # operation
  1111         # operation
  1118         hook.CleanupDeletedEidsCacheOp.get_instance(session).add_data(entity.eid)
  1112         hook.CleanupDeletedEidsCacheOp.get_instance(session).add_data(entity.eid)
  1119         self._delete_info(session, entity, sourceuri, extid, scleanup)
  1113         self._delete_info(session, entity, sourceuri, extid, scleanup)
  1120 
  1114 
  1121     def delete_info_multi(self, session, entities, sourceuri, extids, scleanup=None):
       
  1122         """same as delete_info but accepts a list of entities and
       
  1123         extids with the same etype and belonging to the same source
       
  1124         """
       
  1125         # mark eid as being deleted in session info and setup cache update
       
  1126         # operation
       
  1127         op = hook.CleanupDeletedEidsCacheOp.get_instance(session)
       
  1128         for entity in entities:
       
  1129             op.add_data(entity.eid)
       
  1130         self._delete_info_multi(session, entities, sourceuri, extids, scleanup)
       
  1131 
       
  1132     def _delete_info(self, session, entity, sourceuri, extid, scleanup=None):
  1115     def _delete_info(self, session, entity, sourceuri, extid, scleanup=None):
  1133         """delete system information on deletion of an entity:
  1116         """delete system information on deletion of an entity:
  1134         * delete all remaining relations from/to this entity
  1117         * delete all remaining relations from/to this entity
  1135         * call delete info on the system source which will transfer record from
  1118         * call delete info on the system source which will transfer record from
  1136           the entities table to the deleted_entities table
  1119           the entities table to the deleted_entities table
  1158                     session.execute(rql, {'x': eid, 'seid': scleanup},
  1141                     session.execute(rql, {'x': eid, 'seid': scleanup},
  1159                                     build_descr=False)
  1142                                     build_descr=False)
  1160                 except:
  1143                 except:
  1161                     self.exception('error while cascading delete for entity %s '
  1144                     self.exception('error while cascading delete for entity %s '
  1162                                    'from %s. RQL: %s', entity, sourceuri, rql)
  1145                                    'from %s. RQL: %s', entity, sourceuri, rql)
  1163         self.system_source.delete_info(session, entity, sourceuri, extid)
  1146         self.system_source.delete_info_multi(session, [entity], sourceuri)
  1164 
  1147 
  1165     def _delete_info_multi(self, session, entities, sourceuri, extids, scleanup=None):
  1148     def _delete_info_multi(self, session, entities, sourceuri, scleanup=None):
  1166         """same as _delete_info but accepts a list of entities with
  1149         """same as _delete_info but accepts a list of entities with
  1167         the same etype and belinging to the same source.
  1150         the same etype and belinging to the same source.
  1168         """
  1151         """
  1169         pendingrtypes = session.transaction_data.get('pendingrtypes', ())
  1152         pendingrtypes = session.transaction_data.get('pendingrtypes', ())
  1170         # delete remaining relations: if user can delete the entity, he can
  1153         # delete remaining relations: if user can delete the entity, he can
  1171         # delete all its relations without security checking
  1154         # delete all its relations without security checking
  1172         assert entities and len(entities) == len(extids)
       
  1173         with security_enabled(session, read=False, write=False):
  1155         with security_enabled(session, read=False, write=False):
  1174             eids = [_e.eid for _e in entities]
  1156             eids = [_e.eid for _e in entities]
  1175             in_eids = ','.join((str(eid) for eid in eids))
  1157             in_eids = ','.join((str(eid) for eid in eids))
  1176             for rschema, _, role in entities[0].e_schema.relation_definitions():
  1158             for rschema, _, role in entities[0].e_schema.relation_definitions():
  1177                 rtype = rschema.type
  1159                 rtype = rschema.type
  1189                 try:
  1171                 try:
  1190                     session.execute(rql, {'seid': scleanup}, build_descr=False)
  1172                     session.execute(rql, {'seid': scleanup}, build_descr=False)
  1191                 except:
  1173                 except:
  1192                     self.exception('error while cascading delete for entity %s '
  1174                     self.exception('error while cascading delete for entity %s '
  1193                                    'from %s. RQL: %s', entities, sourceuri, rql)
  1175                                    'from %s. RQL: %s', entities, sourceuri, rql)
  1194         self.system_source.delete_info_multi(session, entities, sourceuri, extids)
  1176         self.system_source.delete_info_multi(session, entities, sourceuri)
  1195 
  1177 
  1196     def locate_relation_source(self, session, subject, rtype, object):
  1178     def locate_relation_source(self, session, subject, rtype, object):
  1197         subjsource = self.source_from_eid(subject, session)
  1179         subjsource = self.source_from_eid(subject, session)
  1198         objsource = self.source_from_eid(object, session)
  1180         objsource = self.source_from_eid(object, session)
  1199         if not subjsource is objsource:
  1181         if not subjsource is objsource:
  1253         # set caches asap
  1235         # set caches asap
  1254         extid = self.init_entity_caches(session, entity, source)
  1236         extid = self.init_entity_caches(session, entity, source)
  1255         if server.DEBUG & server.DBG_REPO:
  1237         if server.DEBUG & server.DBG_REPO:
  1256             print 'ADD entity', self, entity.__regid__, entity.eid, edited
  1238             print 'ADD entity', self, entity.__regid__, entity.eid, edited
  1257         relations = []
  1239         relations = []
       
  1240         prefill_entity_caches(entity, relations)
  1258         if source.should_call_hooks:
  1241         if source.should_call_hooks:
  1259             self.hm.call_hooks('before_add_entity', session, entity=entity)
  1242             self.hm.call_hooks('before_add_entity', session, entity=entity)
       
  1243         activintegrity = session.is_hook_category_activated('activeintegrity')
  1260         for attr in edited.iterkeys():
  1244         for attr in edited.iterkeys():
  1261             rschema = eschema.subjrels[attr]
  1245             rschema = eschema.subjrels[attr]
  1262             if not rschema.final: # inlined relation
  1246             if not rschema.final: # inlined relation
  1263                 relations.append((attr, edited[attr]))
  1247                 value = edited[attr]
       
  1248                 relations.append((attr, value))
       
  1249                 session.update_rel_cache_add(entity.eid, attr, value)
       
  1250                 rdef = session.rtype_eids_rdef(attr, entity.eid, value)
       
  1251                 if rdef.cardinality[1] in '1?' and activintegrity:
       
  1252                     with security_enabled(session, read=False):
       
  1253                         session.execute('DELETE X %s Y WHERE Y eid %%(y)s' % attr,
       
  1254                                         {'x': entity.eid, 'y': value})
  1264         edited.set_defaults()
  1255         edited.set_defaults()
  1265         if session.is_hook_category_activated('integrity'):
  1256         if session.is_hook_category_activated('integrity'):
  1266             edited.check(creation=True)
  1257             edited.check(creation=True)
  1267         prefill_entity_caches(entity, relations)
       
  1268         try:
  1258         try:
  1269             source.add_entity(session, entity)
  1259             source.add_entity(session, entity)
  1270         except UniqueTogetherError, exc:
  1260         except UniqueTogetherError, exc:
  1271             userhdlr = session.vreg['adapters'].select(
  1261             userhdlr = session.vreg['adapters'].select(
  1272                 'IUserFriendlyError', session, entity=entity, exc=exc)
  1262                 'IUserFriendlyError', session, entity=entity, exc=exc)
  1364                 entity.cw_edited = orig_edited
  1354                 entity.cw_edited = orig_edited
  1365 
  1355 
  1366 
  1356 
  1367     def glob_delete_entities(self, session, eids):
  1357     def glob_delete_entities(self, session, eids):
  1368         """delete a list of  entities and all related entities from the repository"""
  1358         """delete a list of  entities and all related entities from the repository"""
       
  1359         # mark eids as being deleted in session info and setup cache update
       
  1360         # operation (register pending eids before actual deletion to avoid
       
  1361         # multiple call to glob_delete_entities)
       
  1362         op = hook.CleanupDeletedEidsCacheOp.get_instance(session)
       
  1363         if not isinstance(eids, (set, frozenset)):
       
  1364             warn('[3.13] eids should be given as a set', DeprecationWarning,
       
  1365                  stacklevel=2)
       
  1366             eids = frozenset(eids)
       
  1367         eids = eids - op._container
       
  1368         op._container |= eids
  1369         data_by_etype_source = {} # values are ([list of eids],
  1369         data_by_etype_source = {} # values are ([list of eids],
  1370                                   #             [list of extid],
  1370                                   #             [list of extid],
  1371                                   #             [list of entities])
  1371                                   #             [list of entities])
  1372         #
  1372         #
  1373         # WARNING: the way this dictionary is populated is heavily optimized
  1373         # WARNING: the way this dictionary is populated is heavily optimized
  1375         # of the Python interpreter advertises large perf improvements
  1375         # of the Python interpreter advertises large perf improvements
  1376         # in setdefault, this should not be changed without profiling.
  1376         # in setdefault, this should not be changed without profiling.
  1377 
  1377 
  1378         for eid in eids:
  1378         for eid in eids:
  1379             etype, sourceuri, extid = self.type_and_source_from_eid(eid, session)
  1379             etype, sourceuri, extid = self.type_and_source_from_eid(eid, session)
       
  1380             # XXX should cache entity's cw_metainformation
  1380             entity = session.entity_from_eid(eid, etype)
  1381             entity = session.entity_from_eid(eid, etype)
  1381             _key = (etype, sourceuri)
  1382             try:
  1382             if _key not in data_by_etype_source:
  1383                 data_by_etype_source[(etype, sourceuri)].append(entity)
  1383                 data_by_etype_source[_key] = ([eid], [extid], [entity])
  1384             except KeyError:
  1384             else:
  1385                 data_by_etype_source[(etype, sourceuri)] = [entity]
  1385                 _data = data_by_etype_source[_key]
  1386         for (etype, sourceuri), entities in data_by_etype_source.iteritems():
  1386                 _data[0].append(eid)
       
  1387                 _data[1].append(extid)
       
  1388                 _data[2].append(entity)
       
  1389         for (etype, sourceuri), (eids, extids, entities) in data_by_etype_source.iteritems():
       
  1390             if server.DEBUG & server.DBG_REPO:
  1387             if server.DEBUG & server.DBG_REPO:
  1391                 print 'DELETE entities', etype, eids
  1388                 print 'DELETE entities', etype, [entity.eid for entity in entities]
  1392             #print 'DELETE entities', etype, len(eids)
       
  1393             source = self.sources_by_uri[sourceuri]
  1389             source = self.sources_by_uri[sourceuri]
  1394             if source.should_call_hooks:
  1390             if source.should_call_hooks:
  1395                 self.hm.call_hooks('before_delete_entity', session, entities=entities)
  1391                 self.hm.call_hooks('before_delete_entity', session, entities=entities)
  1396             self._delete_info_multi(session, entities, sourceuri, extids) # xxx
  1392             self._delete_info_multi(session, entities, sourceuri)
  1397             source.delete_entities(session, entities)
  1393             source.delete_entities(session, entities)
  1398             if source.should_call_hooks:
  1394             if source.should_call_hooks:
  1399                 self.hm.call_hooks('after_delete_entity', session, entities=entities)
  1395                 self.hm.call_hooks('after_delete_entity', session, entities=entities)
  1400         # don't clear cache here this is done in a hook on commit
  1396         # don't clear cache here, it is done in a hook on commit
  1401 
  1397 
  1402     def glob_add_relation(self, session, subject, rtype, object):
  1398     def glob_add_relation(self, session, subject, rtype, object):
  1403         """add a relation to the repository"""
  1399         """add a relation to the repository"""
  1404         self.glob_add_relations(session, {rtype: [(subject, object)]})
  1400         self.glob_add_relations(session, {rtype: [(subject, object)]})
  1405 
  1401 
  1407         """add several relations to the repository
  1403         """add several relations to the repository
  1408 
  1404 
  1409         relations is a dictionary rtype: [(subj_eid, obj_eid), ...]
  1405         relations is a dictionary rtype: [(subj_eid, obj_eid), ...]
  1410         """
  1406         """
  1411         sources = {}
  1407         sources = {}
       
  1408         subjects_by_types = {}
       
  1409         objects_by_types = {}
       
  1410         activintegrity = session.is_hook_category_activated('activeintegrity')
  1412         for rtype, eids_subj_obj in relations.iteritems():
  1411         for rtype, eids_subj_obj in relations.iteritems():
  1413             if server.DEBUG & server.DBG_REPO:
  1412             if server.DEBUG & server.DBG_REPO:
  1414                 for subject, object in relations:
  1413                 for subjeid, objeid in relations:
  1415                     print 'ADD relation', subject, rtype, object
  1414                     print 'ADD relation', subjeid, rtype, objeid
  1416             for subject, object in eids_subj_obj:
  1415             for subjeid, objeid in eids_subj_obj:
  1417                 source = self.locate_relation_source(session, subject, rtype, object)
  1416                 source = self.locate_relation_source(session, subjeid, rtype, objeid)
  1418                 if source not in sources:
  1417                 if source not in sources:
  1419                     relations_by_rtype = {}
  1418                     relations_by_rtype = {}
  1420                     sources[source] = relations_by_rtype
  1419                     sources[source] = relations_by_rtype
  1421                 else:
  1420                 else:
  1422                     relations_by_rtype = sources[source]
  1421                     relations_by_rtype = sources[source]
  1423                 if rtype in relations_by_rtype:
  1422                 if rtype in relations_by_rtype:
  1424                     relations_by_rtype[rtype].append((subject, object))
  1423                     relations_by_rtype[rtype].append((subjeid, objeid))
  1425                 else:
  1424                 else:
  1426                     relations_by_rtype[rtype] = [(subject, object)]
  1425                     relations_by_rtype[rtype] = [(subjeid, objeid)]
       
  1426                 if not activintegrity:
       
  1427                     continue
       
  1428                 # take care to relation of cardinality '?1', as all eids will
       
  1429                 # be inserted later, we've remove duplicated eids since they
       
  1430                 # won't be catched by `del_existing_rel_if_needed`
       
  1431                 rdef = session.rtype_eids_rdef(rtype, subjeid, objeid)
       
  1432                 card = rdef.cardinality
       
  1433                 if card[0] in '?1':
       
  1434                     with security_enabled(session, read=False):
       
  1435                         session.execute('DELETE X %s Y WHERE X eid %%(x)s, '
       
  1436                                         'NOT Y eid %%(y)s' % rtype,
       
  1437                                         {'x': subjeid, 'y': objeid})
       
  1438                     subjects = subjects_by_types.setdefault(rdef, {})
       
  1439                     if subjeid in subjects:
       
  1440                         del relations_by_rtype[rtype][subjects[subjeid]]
       
  1441                         subjects[subjeid] = len(relations_by_rtype[rtype]) - 1
       
  1442                         continue
       
  1443                     subjects[subjeid] = len(relations_by_rtype[rtype]) - 1
       
  1444                 if card[1] in '?1':
       
  1445                     with security_enabled(session, read=False):
       
  1446                         session.execute('DELETE X %s Y WHERE Y eid %%(y)s, '
       
  1447                                         'NOT X eid %%(x)s' % rtype,
       
  1448                                         {'x': subjeid, 'y': objeid})
       
  1449                     objects = objects_by_types.setdefault(rdef, {})
       
  1450                     if objeid in objects:
       
  1451                         del relations_by_rtype[rtype][objects[objeid]]
       
  1452                         objects[objeid] = len(relations_by_rtype[rtype])
       
  1453                         continue
       
  1454                     objects[objeid] = len(relations_by_rtype[rtype])
  1427         for source, relations_by_rtype in sources.iteritems():
  1455         for source, relations_by_rtype in sources.iteritems():
  1428             if source.should_call_hooks:
  1456             if source.should_call_hooks:
  1429                 for rtype, source_relations in relations_by_rtype.iteritems():
  1457                 for rtype, source_relations in relations_by_rtype.iteritems():
  1430                     for subject, object in source_relations:
       
  1431                         del_existing_rel_if_needed(session, subject, rtype, object)
       
  1432                     self.hm.call_hooks('before_add_relation', session,
  1458                     self.hm.call_hooks('before_add_relation', session,
  1433                                     rtype=rtype, eids_from_to=source_relations)
  1459                                     rtype=rtype, eids_from_to=source_relations)
  1434             for rtype, source_relations in relations_by_rtype.iteritems():
  1460             for rtype, source_relations in relations_by_rtype.iteritems():
  1435                 source.add_relations(session, rtype, source_relations)
  1461                 source.add_relations(session, rtype, source_relations)
  1436                 rschema = self.schema.rschema(rtype)
  1462                 rschema = self.schema.rschema(rtype)
  1437                 for subject, object in source_relations:
  1463                 for subjeid, objeid in source_relations:
  1438                     session.update_rel_cache_add(subject, rtype, object, rschema.symmetric)
  1464                     session.update_rel_cache_add(subjeid, rtype, objeid, rschema.symmetric)
  1439             if source.should_call_hooks:
  1465             if source.should_call_hooks:
  1440                 for rtype, source_relations in relations_by_rtype.iteritems():
  1466                 for rtype, source_relations in relations_by_rtype.iteritems():
  1441                     self.hm.call_hooks('after_add_relation', session,
  1467                     self.hm.call_hooks('after_add_relation', session,
  1442                                        rtype=rtype, eids_from_to=source_relations)
  1468                                        rtype=rtype, eids_from_to=source_relations)
  1443 
  1469