24 from logilab import database as db |
24 from logilab import database as db |
25 from logilab.common.testlib import mock_object |
25 from logilab.common.testlib import mock_object |
26 from logilab.common.decorators import monkeypatch |
26 from logilab.common.decorators import monkeypatch |
27 |
27 |
28 from rql import BadRQLQuery |
28 from rql import BadRQLQuery |
|
29 from rql import RQLHelper |
29 from rql.utils import register_function, FunctionDescr |
30 from rql.utils import register_function, FunctionDescr |
30 |
31 |
31 from cubicweb import devtools |
32 from cubicweb import devtools |
32 from cubicweb.devtools.repotest import RQLGeneratorTC |
33 from cubicweb.devtools.fake import FakeRepo, FakeConfig, FakeConnection |
33 from cubicweb.server.sources.rql2sql import SQLGenerator, remove_unused_solutions |
34 from cubicweb.devtools.testlib import BaseTestCase |
|
35 from cubicweb.server import rqlannotation |
|
36 from cubicweb.server.querier import QuerierHelper, ExecutionPlan |
|
37 from cubicweb.server.sources import rql2sql |
|
38 |
|
39 _orig_select_principal = rqlannotation._select_principal |
|
40 _orig_check_permissions = ExecutionPlan._check_permissions |
34 |
41 |
35 |
42 |
36 def setUpModule(): |
43 def setUpModule(): |
37 """Monkey-patch the SQL generator to ensure solutions order is predictable.""" |
44 """Monkey-patch the SQL generator to ensure solutions order is predictable.""" |
38 global orig_solutions_sql |
45 global orig_solutions_sql |
39 orig_solutions_sql = SQLGenerator._solutions_sql |
46 orig_solutions_sql = rql2sql.SQLGenerator._solutions_sql |
40 |
47 |
41 @monkeypatch |
48 @monkeypatch |
42 def _solutions_sql(self, select, solutions, distinct, needalias): |
49 def _solutions_sql(self, select, solutions, distinct, needalias): |
43 return orig_solutions_sql(self, select, sorted(solutions), distinct, needalias) |
50 return orig_solutions_sql(self, select, sorted(solutions), distinct, needalias) |
44 |
51 |
45 |
52 |
46 def tearDownModule(): |
53 def tearDownModule(): |
47 """Remove monkey-patch done in setUpModule""" |
54 """Remove monkey-patch done in setUpModule""" |
48 SQLGenerator._solutions_sql = orig_solutions_sql |
55 rql2sql.SQLGenerator._solutions_sql = orig_solutions_sql |
49 |
56 |
50 |
57 |
51 # add a dumb registered procedure |
58 # add a dumb registered procedure |
52 class stockproc(FunctionDescr): |
59 class stockproc(FunctionDescr): |
53 supported_backends = ('postgres', 'sqlite', 'mysql') |
60 supported_backends = ('postgres', 'sqlite', 'mysql') |
1224 |
1231 |
1225 ('Any X WHERE X is ET, ET eid 2', |
1232 ('Any X WHERE X is ET, ET eid 2', |
1226 '''SELECT rel_is0.eid_from |
1233 '''SELECT rel_is0.eid_from |
1227 FROM is_relation AS rel_is0 |
1234 FROM is_relation AS rel_is0 |
1228 WHERE rel_is0.eid_to=2'''), |
1235 WHERE rel_is0.eid_to=2'''), |
1229 |
1236 ] |
1230 ] |
1237 |
|
1238 |
|
1239 class RQLGeneratorTC(BaseTestCase): |
|
1240 schema = backend = None # set this in concrete class |
|
1241 |
|
1242 @classmethod |
|
1243 def setUpClass(cls): |
|
1244 if cls.backend is not None: |
|
1245 try: |
|
1246 cls.dbhelper = db.get_db_helper(cls.backend) |
|
1247 except ImportError as ex: |
|
1248 self.skipTest(str(ex)) |
|
1249 |
|
1250 def setUp(self): |
|
1251 self.repo = FakeRepo(self.schema, config=FakeConfig(apphome=self.datadir)) |
|
1252 self.repo.system_source = mock_object(dbdriver=self.backend) |
|
1253 self.rqlhelper = RQLHelper(self.schema, |
|
1254 special_relations={'eid': 'uid', |
|
1255 'has_text': 'fti'}, |
|
1256 backend=self.backend) |
|
1257 self.qhelper = QuerierHelper(self.repo, self.schema) |
|
1258 |
|
1259 def _dummy_check_permissions(self, rqlst): |
|
1260 return {(): rqlst.solutions}, set() |
|
1261 |
|
1262 ExecutionPlan._check_permissions = _dummy_check_permissions |
|
1263 |
|
1264 def _select_principal(scope, relations): |
|
1265 def sort_key(something): |
|
1266 try: |
|
1267 return something.r_type |
|
1268 except AttributeError: |
|
1269 return (something[0].r_type, something[1]) |
|
1270 return _orig_select_principal(scope, relations, |
|
1271 _sort=lambda rels: sorted(rels, key=sort_key)) |
|
1272 |
|
1273 rqlannotation._select_principal = _select_principal |
|
1274 if self.backend is not None: |
|
1275 self.o = rql2sql.SQLGenerator(self.schema, self.dbhelper) |
|
1276 |
|
1277 def tearDown(self): |
|
1278 ExecutionPlan._check_permissions = _orig_check_permissions |
|
1279 rqlannotation._select_principal = _orig_select_principal |
|
1280 |
|
1281 def _prepare(self, rql): |
|
1282 #print '******************** prepare', rql |
|
1283 union = self.rqlhelper.parse(rql) |
|
1284 #print '********* parsed', union.as_string() |
|
1285 self.rqlhelper.compute_solutions(union) |
|
1286 #print '********* solutions', solutions |
|
1287 self.rqlhelper.simplify(union) |
|
1288 #print '********* simplified', union.as_string() |
|
1289 plan = self.qhelper.plan_factory(union, {}, FakeConnection(self.repo)) |
|
1290 plan.preprocess(union) |
|
1291 for select in union.children: |
|
1292 select.solutions.sort(key=lambda x: list(x.items())) |
|
1293 #print '********* ppsolutions', solutions |
|
1294 return union |
|
1295 |
|
1296 |
1231 class CWRQLTC(RQLGeneratorTC): |
1297 class CWRQLTC(RQLGeneratorTC): |
1232 backend = 'sqlite' |
1298 backend = 'sqlite' |
1233 |
1299 |
1234 def setUp(self): |
1300 def setUp(self): |
1235 self.__class__.schema = schema |
1301 self.__class__.schema = schema |
2271 class removeUnsusedSolutionsTC(unittest.TestCase): |
2337 class removeUnsusedSolutionsTC(unittest.TestCase): |
2272 def test_invariant_not_varying(self): |
2338 def test_invariant_not_varying(self): |
2273 rqlst = mock_object(defined_vars={}) |
2339 rqlst = mock_object(defined_vars={}) |
2274 rqlst.defined_vars['A'] = mock_object(scope=rqlst, stinfo={}, _q_invariant=True) |
2340 rqlst.defined_vars['A'] = mock_object(scope=rqlst, stinfo={}, _q_invariant=True) |
2275 rqlst.defined_vars['B'] = mock_object(scope=rqlst, stinfo={}, _q_invariant=False) |
2341 rqlst.defined_vars['B'] = mock_object(scope=rqlst, stinfo={}, _q_invariant=False) |
2276 self.assertEqual(remove_unused_solutions(rqlst, [{'A': 'RugbyGroup', 'B': 'RugbyTeam'}, |
2342 self.assertEqual( |
2277 {'A': 'FootGroup', 'B': 'FootTeam'}], None), |
2343 rql2sql.remove_unused_solutions(rqlst, [{'A': 'RugbyGroup', 'B': 'RugbyTeam'}, |
2278 ([{'A': 'RugbyGroup', 'B': 'RugbyTeam'}, |
2344 {'A': 'FootGroup', 'B': 'FootTeam'}], None), |
2279 {'A': 'FootGroup', 'B': 'FootTeam'}], |
2345 ([{'A': 'RugbyGroup', 'B': 'RugbyTeam'}, |
2280 {}, set('B')) |
2346 {'A': 'FootGroup', 'B': 'FootTeam'}], |
2281 ) |
2347 {}, set('B')) |
|
2348 ) |
2282 |
2349 |
2283 def test_invariant_varying(self): |
2350 def test_invariant_varying(self): |
2284 rqlst = mock_object(defined_vars={}) |
2351 rqlst = mock_object(defined_vars={}) |
2285 rqlst.defined_vars['A'] = mock_object(scope=rqlst, stinfo={}, _q_invariant=True) |
2352 rqlst.defined_vars['A'] = mock_object(scope=rqlst, stinfo={}, _q_invariant=True) |
2286 rqlst.defined_vars['B'] = mock_object(scope=rqlst, stinfo={}, _q_invariant=False) |
2353 rqlst.defined_vars['B'] = mock_object(scope=rqlst, stinfo={}, _q_invariant=False) |
2287 self.assertEqual(remove_unused_solutions(rqlst, [{'A': 'RugbyGroup', 'B': 'RugbyTeam'}, |
2354 self.assertEqual( |
2288 {'A': 'FootGroup', 'B': 'RugbyTeam'}], None), |
2355 rql2sql.remove_unused_solutions(rqlst, [{'A': 'RugbyGroup', 'B': 'RugbyTeam'}, |
2289 ([{'A': 'RugbyGroup', 'B': 'RugbyTeam'}], {}, set()) |
2356 {'A': 'FootGroup', 'B': 'RugbyTeam'}], None), |
2290 ) |
2357 ([{'A': 'RugbyGroup', 'B': 'RugbyTeam'}], {}, set()) |
|
2358 ) |
2291 |
2359 |
2292 |
2360 |
2293 if __name__ == '__main__': |
2361 if __name__ == '__main__': |
2294 unittest.main() |
2362 unittest.main() |