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 |
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 |