--- a/server/querier.py Thu Jan 15 17:37:43 2009 +0100
+++ b/server/querier.py Fri Jan 16 18:31:13 2009 +0100
@@ -196,7 +196,7 @@
self._insert_security(union, noinvariant)
self.rqlhelper.simplify(union)
self.sqlannotate(union)
- set_qdata(union, noinvariant)
+ set_qdata(self.schema.rschema, union, noinvariant)
if union.has_text_query:
self.cache_key = None
--- a/server/rqlannotation.py Thu Jan 15 17:37:43 2009 +0100
+++ b/server/rqlannotation.py Fri Jan 16 18:31:13 2009 +0100
@@ -20,7 +20,7 @@
#if server.DEBUG:
# print '-------- sql annotate', repr(rqlst)
getrschema = annotator.schema.rschema
- has_text_query = need_intersect = False
+ has_text_query = False
need_distinct = rqlst.distinct
for rel in rqlst.iget_nodes(Relation):
if rel.neged(strict=True):
@@ -29,13 +29,6 @@
else:
rschema = getrschema(rel.r_type)
if not rschema.is_final():
- # if one of the relation's variable is ambiguous, an intersection
- # will be necessary
- for vref in rel.get_nodes(VariableRef):
- var = vref.variable
- if not var.stinfo['selected'] and len(var.stinfo['possibletypes']) > 1:
- need_intersect = True
- break
if rschema.inlined:
try:
var = rel.children[1].children[0].variable
@@ -147,7 +140,6 @@
except CantSelectPrincipal:
stinfo['invariant'] = False
rqlst.need_distinct = need_distinct
- rqlst.need_intersect = need_intersect
return has_text_query
@@ -207,12 +199,12 @@
return principal
-def set_qdata(union, noinvariant):
+def set_qdata(getrschema, union, noinvariant):
"""recursive function to set querier data on variables in the syntax tree
"""
for select in union.children:
for subquery in select.with_:
- set_qdata(subquery.query, noinvariant)
+ set_qdata(getrschema, subquery.query, noinvariant)
for var in select.defined_vars.itervalues():
if var.stinfo['invariant']:
if var in noinvariant and not var.stinfo['principal'].r_type == 'has_text':
@@ -221,6 +213,23 @@
var._q_invariant = True
else:
var._q_invariant = False
+ for rel in select.iget_nodes(Relation):
+ if rel.neged(strict=True) and not rel.is_types_restriction():
+ rschema = getrschema(rel.r_type)
+ if not rschema.is_final():
+ # if one of the relation's variable is ambiguous but not
+ # invariant, an intersection will be necessary
+ for vref in rel.get_nodes(VariableRef):
+ var = vref.variable
+ if (not var._q_invariant and var.valuable_references() == 1
+ and len(var.stinfo['possibletypes']) > 1):
+ select.need_intersect = True
+ break
+ else:
+ continue
+ break
+ else:
+ select.need_intersect = False
class SQLGenAnnotator(object):
--- a/server/sources/native.py Thu Jan 15 17:37:43 2009 +0100
+++ b/server/sources/native.py Fri Jan 16 18:31:13 2009 +0100
@@ -188,7 +188,7 @@
rqlst.restricted_vars = ()
rqlst.children[0].solutions = self._sols
self.repo.querier.sqlgen_annotate(rqlst)
- set_qdata(rqlst, ())
+ set_qdata(self.schema.rschema, rqlst, ())
return rqlst
def set_schema(self, schema):
--- a/server/sources/rql2sql.py Thu Jan 15 17:37:43 2009 +0100
+++ b/server/sources/rql2sql.py Fri Jan 16 18:31:13 2009 +0100
@@ -488,7 +488,8 @@
sql.insert(1, 'FROM (SELECT 1) AS _T')
sqls.append('\n'.join(sql))
if select.need_intersect:
- if distinct:
+ # XXX use getattr for lgc bw compat, remove once 0.37.3 is out
+ if distinct or not getattr(self.dbms_helper, 'intersect_all_support', True):
return '\nINTERSECT\n'.join(sqls)
else:
return '\nINTERSECT ALL\n'.join(sqls)
--- a/server/test/unittest_rql2sql.py Thu Jan 15 17:37:43 2009 +0100
+++ b/server/test/unittest_rql2sql.py Fri Jan 16 18:31:13 2009 +0100
@@ -156,11 +156,6 @@
]
ADVANCED= [
- ('Any X WHERE X is ET, ET eid 2',
- '''SELECT rel_is0.eid_from
-FROM is_relation AS rel_is0
-WHERE rel_is0.eid_to=2'''),
-
("Societe S WHERE S nom 'Logilab' OR S nom 'Caesium'",
'''SELECT S.eid
@@ -1064,7 +1059,57 @@
]
+INTERSECT = [
+ ('Any SN WHERE NOT X in_state S, S name SN',
+ '''SELECT DISTINCT S.name
+FROM Affaire AS X, State AS S
+WHERE (X.in_state IS NULL OR X.in_state!=S.eid)
+INTERSECT
+SELECT DISTINCT S.name
+FROM EUser AS X, State AS S
+WHERE (X.in_state IS NULL OR X.in_state!=S.eid)
+INTERSECT
+SELECT DISTINCT S.name
+FROM Note AS X, State AS S
+WHERE (X.in_state IS NULL OR X.in_state!=S.eid)'''),
+ ('Any PN WHERE NOT X travaille S, X nom PN, S is IN(Division, Societe)',
+ '''SELECT X.nom
+FROM Personne AS X
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0,Division AS S WHERE rel_travaille0.eid_from=X.eid AND rel_travaille0.eid_to=S.eid)
+INTERSECT ALL
+SELECT X.nom
+FROM Personne AS X
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0,Societe AS S WHERE rel_travaille0.eid_from=X.eid AND rel_travaille0.eid_to=S.eid)'''),
+
+ ('Any PN WHERE NOT X travaille S, S nom PN, S is IN(Division, Societe)',
+ '''SELECT S.nom
+FROM Division AS S
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_to=S.eid)
+UNION ALL
+SELECT S.nom
+FROM Societe AS S
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_to=S.eid)'''),
+
+ ('Personne X WHERE NOT X travaille S, S nom "chouette"',
+ '''SELECT X.eid
+FROM Division AS S, Personne AS X
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=X.eid AND rel_travaille0.eid_to=S.eid) AND S.nom=chouette
+UNION ALL
+SELECT X.eid
+FROM Personne AS X, Societe AS S
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=X.eid AND rel_travaille0.eid_to=S.eid) AND S.nom=chouette
+UNION ALL
+SELECT X.eid
+FROM Personne AS X, SubDivision AS S
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=X.eid AND rel_travaille0.eid_to=S.eid) AND S.nom=chouette'''),
+
+ ('Any X WHERE X is ET, ET eid 2',
+ '''SELECT rel_is0.eid_from
+FROM is_relation AS rel_is0
+WHERE rel_is0.eid_to=2'''),
+
+ ]
from logilab.common.adbh import ADV_FUNC_HELPER_DIRECTORY
class PostgresSQLGeneratorTC(RQLGeneratorTC):
@@ -1196,6 +1241,10 @@
def test_negation(self):
for t in self._parse(NEGATIONS):
yield t
+
+ def test_intersection(self):
+ for t in self._parse(INTERSECT):
+ yield t
def test_union(self):
for t in self._parse((
@@ -1370,7 +1419,7 @@
self.o = SQLGenerator(schema, dbms_helper)
def _norm_sql(self, sql):
- return sql.strip().replace(' ILIKE ', ' LIKE ')
+ return sql.strip().replace(' ILIKE ', ' LIKE ').replace('\nINTERSECT ALL\n', '\nINTERSECT\n')
def test_union(self):
for t in self._parse((