cubicweb/server/test/unittest_rql2sql.py
changeset 12235 03b94c9863de
parent 12234 14fdd5888cbb
child 12236 75a6b2f40c44
equal deleted inserted replaced
12234:14fdd5888cbb 12235:03b94c9863de
     1 # copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     1 # copyright 2003 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     3 #
     3 #
     4 # This file is part of CubicWeb.
     4 # This file is part of CubicWeb.
     5 #
     5 #
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
    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()