fixes for some cases of neged inlined relations, may have to use (new) IntersectStep. XXX: write IntersectFetchStep
--- 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]
--- 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')
--- 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):
--- 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 = {
--- 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')
--- 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',