# HG changeset patch # User Sylvain Thenault # Date 1231336623 -3600 # Node ID 0a426be2f3a25aa32921aaa8c69a926ba0027dc4 # Parent bfe0e95571aaab1a72a3c0c176204b3a819e8ae6 fixes for some cases of neged inlined relations, may have to use (new) IntersectStep. XXX: write IntersectFetchStep diff -r bfe0e95571aa -r 0a426be2f3a2 server/msplanner.py --- a/server/msplanner.py Wed Jan 07 14:55:16 2009 +0100 +++ b/server/msplanner.py Wed Jan 07 14:57:03 2009 +0100 @@ -180,8 +180,6 @@ # processing self._compute_sourcesvars() self._remove_invalid_sources() - #if server.DEBUG: - # print 'planner sources vars', self._sourcesvars self._compute_needsplit() self._inputmaps = {} if rqlhelper is not None: # else test @@ -212,6 +210,7 @@ for solidx in self._solindices if not (source.support_relation(rtype) or rtype in source.dont_cross_relations)) + def _compute_sourcesvars(self): """compute for each variable/solution in the rqlst which sources support @@ -277,11 +276,9 @@ # during bootstrap) if not rel.is_types_restriction() and not rschema(rel.r_type).is_final(): # nothing to do if relation is not supported by multiple sources - relsources = [source for source in repo.sources - if source.support_relation(rel.r_type) - or rel.r_type in source.dont_cross_relations] + relsources = repo.rel_type_sources(rel.r_type) if len(relsources) < 2: - if relsources:# and not relsources[0] in self._sourcesvars: + if relsources: # this means the relation is using a variable inlined as # a constant and another unsupported variable, in which # case we put the relation in sourcesvars @@ -344,7 +341,7 @@ for const in vconsts: if const.scope in source_scopes: self._set_source_for_var(source, const) - + def _extern_term(self, term, vsources, inserted): var = term.variable if var.stinfo['constnode']: @@ -361,13 +358,24 @@ return termv def _remove_sources_until_stable(self, var, vsources): + sourcesvars = self._sourcesvars for ovar, rel in self._linkedvars.get(var, ()): if not var.scope is ovar.scope and rel.scope.neged(strict=True): # can't get information from relation inside a NOT exists # where variables don't belong to the same scope continue - if rel.neged(strict=True): - # neged relation doesn't allow to infer variable sources + relsources = self._session.repo.rel_type_sources(rel.r_type) + if rel.neged(strict=True) and ( + len(relsources) < 2 + or not isinstance(ovar, Variable) + or ovar.valuable_references() != 1 + or any(sourcesvars[source][var] != sourcesvars[source][ovar] + for source in relsources + if var in sourcesvars.get(source, ()) + and ovar in sourcesvars.get(source, ()))): + # neged relation doesn't allow to infer variable sources unless we're + # on a multisource relation for a variable only used by this relation + # (eg "Any X WHERE NOT X multisource_rel Y" and over is Y), iif continue norelsup = self._norel_support_set(rel.r_type) # compute invalid sources for variables and remove them @@ -898,10 +906,16 @@ step = AggrStep(plan, selection, select, atemptable, temptable) step.children = steps elif len(steps) > 1: - if temptable: - step = UnionFetchStep(plan) + if select.need_intersect: + if temptable: + step = IntersectFetchStep(plan) + else: + step = IntersectStep(plan) else: - step = UnionStep(plan) + if temptable: + step = UnionFetchStep(plan) + else: + step = UnionStep(plan) step.children = steps else: step = steps[0] diff -r bfe0e95571aa -r 0a426be2f3a2 server/mssteps.py --- a/server/mssteps.py Wed Jan 07 14:55:16 2009 +0100 +++ b/server/mssteps.py Wed Jan 07 14:57:03 2009 +0100 @@ -264,6 +264,22 @@ return (self.__class__.__name__, self.limit, self.offset) +class IntersectStep(UnionStep): + """return intersection of results of child in-memory steps (e.g. OneFetchStep / AggrStep)""" + + def execute(self): + """execute this step""" + result = set() + for step in self.children: + result &= frozenset(step.execute()) + result = list(result) + if self.offset: + result = result[offset:] + if self.limit: + result = result[:limit] + return result + + class UnionFetchStep(Step): """union results of child steps using temporary tables (e.g. FetchStep)""" @@ -272,4 +288,4 @@ self.execute_children() -__all__ = ('FetchStep', 'AggrStep', 'UnionStep', 'UnionFetchStep') +__all__ = ('FetchStep', 'AggrStep', 'UnionStep', 'UnionFetchStep', 'IntersectStep') diff -r bfe0e95571aa -r 0a426be2f3a2 server/repository.py --- a/server/repository.py Wed Jan 07 14:55:16 2009 +0100 +++ b/server/repository.py Wed Jan 07 14:57:03 2009 +0100 @@ -896,6 +896,11 @@ source = subjsource return source + @cached + def rel_type_sources(self, rtype): + return [source for source in self.sources + if source.support_relation(rtype) or rtype in source.dont_cross_relations] + def locate_etype_source(self, etype): for source in self.sources: if source.support_entity(etype, 1): diff -r bfe0e95571aa -r 0a426be2f3a2 server/test/data/schema/Affaire.py --- a/server/test/data/schema/Affaire.py Wed Jan 07 14:55:16 2009 +0100 +++ b/server/test/data/schema/Affaire.py Wed Jan 07 14:57:03 2009 +0100 @@ -26,6 +26,7 @@ wf_info_for = ObjectRelation('TrInfo', cardinality='1*', composite='object') depends_on = SubjectRelation('Affaire') + require_permission = SubjectRelation('EPermission') class concerne(RelationType): permissions = { diff -r bfe0e95571aa -r 0a426be2f3a2 server/test/data/schema/custom.py --- a/server/test/data/schema/custom.py Wed Jan 07 14:55:16 2009 +0100 +++ b/server/test/data/schema/custom.py Wed Jan 07 14:57:03 2009 +0100 @@ -15,6 +15,12 @@ subject = ('Card', 'Note') object = 'Note' +class multisource_inlined_rel(RelationType): + inlined = True + cardinality = '?*' + subject = ('Card', 'Note') + object = ('Affaire', 'Note') + class see_also(RelationDefinition): subject = ('Bookmark', 'Note') diff -r bfe0e95571aa -r 0a426be2f3a2 server/test/unittest_msplanner.py --- a/server/test/unittest_msplanner.py Wed Jan 07 14:55:16 2009 +0100 +++ b/server/test/unittest_msplanner.py Wed Jan 07 14:57:03 2009 +0100 @@ -28,7 +28,7 @@ class FakeCardSource(AbstractSource): uri = 'ccc' support_entities = {'Card': True, 'Note': True, 'State': True} - support_relations = {'in_state': True, 'multisource_rel': True} + support_relations = {'in_state': True, 'multisource_rel': True, 'multisource_inlined_rel': True} dont_cross_relations = set(('fiche',)) def syntax_tree_search(self, *args, **kwargs): @@ -60,6 +60,7 @@ def setUp(self): #_QuerierTC.setUp(self) + clear_cache(repo, 'rel_type_sources') self.o = repo.querier self.session = repo._sessions.values()[0] self.pool = self.session.set_pool() @@ -1144,7 +1145,7 @@ ('FetchStep', [('Any X WHERE X is Note', [{'X': 'Note'}])], [self.rql, self.system], None, {'X': 'table1.C0'}, []), - ('UnionStep', None, None, + ('IntersectStep', None, None, [('OneFetchStep', [('Any SN WHERE NOT X in_state S, S name SN, S is State, X is IN(Affaire, EUser)', [{'S': 'State', 'SN': 'String', 'X': 'Affaire'}, @@ -1435,7 +1436,29 @@ ]), ], {'x': ueid, 'y': ueid}) + + def test_delete_entity1(self): + repo._type_source_cache[999999] = ('Note', 'system', 999999) + self._test('DELETE Note X WHERE X eid %(x)s, NOT Y multisource_rel X', + [('DeleteEntitiesStep', + [('OneFetchStep', [('Any 999999 WHERE NOT Y multisource_rel 999999, Y is IN(Card, Note)', + [{'Y': 'Card'}, {'Y': 'Note'}])], + None, None, [self.system], {}, []) + ]) + ], + {'x': 999999}) + def test_delete_entity2(self): + repo._type_source_cache[999999] = ('Note', 'system', 999999) + self._test('DELETE Note X WHERE X eid %(x)s, NOT X multisource_inlined_rel Y', + [('DeleteEntitiesStep', + [('OneFetchStep', [('Any X WHERE X eid 999999, NOT X multisource_inlined_rel Y, X is Note, Y is IN(Affaire, Note)', + [{'X': 'Note', 'Y': 'Affaire'}, {'X': 'Note', 'Y': 'Note'}])], + None, None, [self.system], {}, []) + ]) + ], + {'x': 999999}) + def test_update(self): self._test('SET X copain Y WHERE X login "comme", Y login "cochon"', [('FetchStep',