test/unittest_rqlrewrite.py
changeset 9205 ea32e964fbf8
parent 9189 9448215c73c4
child 9262 7fc54e02291f
equal deleted inserted replaced
9149:31ed9dd946d1 9205:ea32e964fbf8
     1 # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     1 # copyright 2003-2013 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
    21 from yams import BadSchemaDefinition
    21 from yams import BadSchemaDefinition
    22 from rql import parse, nodes, RQLHelper
    22 from rql import parse, nodes, RQLHelper
    23 
    23 
    24 from cubicweb import Unauthorized, rqlrewrite
    24 from cubicweb import Unauthorized, rqlrewrite
    25 from cubicweb.schema import RRQLExpression, ERQLExpression
    25 from cubicweb.schema import RRQLExpression, ERQLExpression
    26 from cubicweb.devtools import repotest, TestServerConfiguration
    26 from cubicweb.devtools import repotest, TestServerConfiguration, BaseApptestConfiguration
    27 
    27 
    28 
    28 
    29 def setUpModule(*args):
    29 def setUpModule(*args):
    30     global rqlhelper, schema
    30     global rqlhelper, schema
    31     config = TestServerConfiguration(RQLRewriteTC.datapath('rewrite'))
    31     config = TestServerConfiguration(RQLRewriteTC.datapath('rewrite'))
    44     global rqlhelper, schema
    44     global rqlhelper, schema
    45     del rqlhelper, schema
    45     del rqlhelper, schema
    46 
    46 
    47 def eid_func_map(eid):
    47 def eid_func_map(eid):
    48     return {1: 'CWUser',
    48     return {1: 'CWUser',
    49             2: 'Card'}[eid]
    49             2: 'Card',
       
    50             3: 'Affaire'}[eid]
    50 
    51 
    51 def rewrite(rqlst, snippets_map, kwargs, existingvars=None):
    52 def rewrite(rqlst, snippets_map, kwargs, existingvars=None):
    52     class FakeVReg:
    53     class FakeVReg:
    53         schema = schema
    54         schema = schema
    54         @staticmethod
    55         @staticmethod
    70                                     expression='Any X WHERE '+snippet)
    71                                     expression='Any X WHERE '+snippet)
    71                     or snippet
    72                     or snippet
    72                     for snippet in exprs]
    73                     for snippet in exprs]
    73         snippets.append((dict([v]), rqlexprs))
    74         snippets.append((dict([v]), rqlexprs))
    74     rqlhelper.compute_solutions(rqlst.children[0], {'eid': eid_func_map}, kwargs=kwargs)
    75     rqlhelper.compute_solutions(rqlst.children[0], {'eid': eid_func_map}, kwargs=kwargs)
    75     solutions = rqlst.children[0].solutions
    76     rewriter.rewrite(rqlst.children[0], snippets, kwargs, existingvars)
    76     rewriter.rewrite(rqlst.children[0], snippets, solutions, kwargs,
       
    77                      existingvars)
       
    78     test_vrefs(rqlst.children[0])
    77     test_vrefs(rqlst.children[0])
    79     return rewriter.rewritten
    78     return rewriter.rewritten
    80 
    79 
    81 def test_vrefs(node):
    80 def test_vrefs(node):
    82     vrefmaps = {}
    81     vrefmaps = {}
   200                              'EXISTS(X created_by B), EXISTS(Y created_by B), '
   199                              'EXISTS(X created_by B), EXISTS(Y created_by B), '
   201                              'X is Card, Y is IN(Division, Note, Societe) '
   200                              'X is Card, Y is IN(Division, Note, Societe) '
   202                              'WITH LA BEING (Any LA WHERE (EXISTS(A created_by B, LA documented_by A)) OR (EXISTS(E created_by B, LA concerne E)), '
   201                              'WITH LA BEING (Any LA WHERE (EXISTS(A created_by B, LA documented_by A)) OR (EXISTS(E created_by B, LA concerne E)), '
   203                              'B eid %(D)s, LA is Affaire)')
   202                              'B eid %(D)s, LA is Affaire)')
   204 
   203 
       
   204 
       
   205     def test_ambiguous_optional_same_exprs(self):
       
   206         """See #3013535"""
       
   207         # see test of the same name in RewriteFullTC: original problem is
       
   208         # unreproducible here because it actually lies in
       
   209         # RQLRewriter.insert_local_checks
       
   210         rqlst = parse('Any A,AR,X,CD WHERE A concerne X?, A ref AR, A eid %(a)s, X creation_date CD')
       
   211         rewrite(rqlst, {('X', 'X'): ('X created_by U',),}, {'a': 3})
       
   212         self.assertEqual(rqlst.as_string(),
       
   213                          u'Any A,AR,X,CD WHERE A concerne X?, A ref AR, A eid %(a)s WITH X,CD BEING (Any X,CD WHERE X creation_date CD, EXISTS(X created_by B), B eid %(A)s, X is IN(Division, Note, Societe))')
       
   214 
   205     def test_optional_var_inlined(self):
   215     def test_optional_var_inlined(self):
   206         c1 = ('X require_permission P')
   216         c1 = ('X require_permission P')
   207         c2 = ('X inlined_card O, O require_permission P')
   217         c2 = ('X inlined_card O, O require_permission P')
   208         rqlst = parse('Any C,A,R WHERE A? inlined_card C, A ref R')
   218         rqlst = parse('Any C,A,R WHERE A? inlined_card C, A ref R')
   209         rewrite(rqlst, {('C', 'X'): (c1,),
   219         rewrite(rqlst, {('C', 'X'): (c1,),
   290         rqlst = parse('Card C WHERE C in_state STATE')
   300         rqlst = parse('Card C WHERE C in_state STATE')
   291         rewrite(rqlst, {('C', 'X'): (snippet,)}, {})
   301         rewrite(rqlst, {('C', 'X'): (snippet,)}, {})
   292         self.assertEqual(rqlst.as_string(),
   302         self.assertEqual(rqlst.as_string(),
   293                          "Any C WHERE C in_state STATE, C is Card, "
   303                          "Any C WHERE C in_state STATE, C is Card, "
   294                          "EXISTS(STATE name 'hop'), STATE is State")
   304                          "EXISTS(STATE name 'hop'), STATE is State")
       
   305 
   295     def test_relation_optimization_3_rhs(self):
   306     def test_relation_optimization_3_rhs(self):
   296         snippet = ('TW? subworkflow_exit X, TW name "hop"')
   307         snippet = ('TW? subworkflow_exit X, TW name "hop"')
   297         rqlst = parse('WorkflowTransition C WHERE C subworkflow_exit EXIT')
   308         rqlst = parse('WorkflowTransition C WHERE C subworkflow_exit EXIT')
   298         rewrite(rqlst, {('EXIT', 'X'): (snippet,)}, {})
   309         rewrite(rqlst, {('EXIT', 'X'): (snippet,)}, {})
   299         self.assertEqual(rqlst.as_string(),
   310         self.assertEqual(rqlst.as_string(),
   306         rqlst = parse('Card C WHERE C in_state STATE?')
   317         rqlst = parse('Card C WHERE C in_state STATE?')
   307         rewrite(rqlst, {('C', 'X'): (snippet,)}, {})
   318         rewrite(rqlst, {('C', 'X'): (snippet,)}, {})
   308         self.assertEqual(rqlst.as_string(),
   319         self.assertEqual(rqlst.as_string(),
   309                          "Any C WHERE C in_state STATE?, C is Card, "
   320                          "Any C WHERE C in_state STATE?, C is Card, "
   310                          "EXISTS(C in_state A, A name 'hop', A is State), STATE is State")
   321                          "EXISTS(C in_state A, A name 'hop', A is State), STATE is State")
       
   322 
   311     def test_relation_non_optimization_1_rhs(self):
   323     def test_relation_non_optimization_1_rhs(self):
   312         snippet = ('TW subworkflow_exit X, TW name "hop"')
   324         snippet = ('TW subworkflow_exit X, TW name "hop"')
   313         rqlst = parse('SubWorkflowExitPoint EXIT WHERE C? subworkflow_exit EXIT')
   325         rqlst = parse('SubWorkflowExitPoint EXIT WHERE C? subworkflow_exit EXIT')
   314         rewrite(rqlst, {('EXIT', 'X'): (snippet,)}, {})
   326         rewrite(rqlst, {('EXIT', 'X'): (snippet,)}, {})
   315         self.assertEqual(rqlst.as_string(),
   327         self.assertEqual(rqlst.as_string(),
   316                          "Any EXIT WHERE C? subworkflow_exit EXIT, EXIT is SubWorkflowExitPoint, "
   328                          "Any EXIT WHERE C? subworkflow_exit EXIT, EXIT is SubWorkflowExitPoint, "
   317                          "EXISTS(A subworkflow_exit EXIT, A name 'hop', A is WorkflowTransition), "
   329                          "EXISTS(A subworkflow_exit EXIT, A name 'hop', A is WorkflowTransition), "
   318                          "C is WorkflowTransition")
   330                          "C is WorkflowTransition")
       
   331 
       
   332     def test_relation_non_optimization_2(self):
       
   333         """See #3024730"""
       
   334         # 'X inlined_note N' must not be shared with 'C inlined_note N'
       
   335         # previously inserted, else this may introduce duplicated results, as N
       
   336         # will then be shared by multiple EXISTS and so at SQL generation time,
       
   337         # the table will be in the FROM clause of the outermost query
       
   338         rqlst = parse('Any A,C WHERE A inlined_card C')
       
   339         rewrite(rqlst, {('A', 'X'): ('X inlined_card C, C inlined_note N, N owned_by U',),
       
   340                         ('C', 'X'): ('X inlined_note N, N owned_by U',)}, {})
       
   341         self.assertEqual(rqlst.as_string(),
       
   342                          'Any A,C WHERE A inlined_card C, D eid %(E)s, '
       
   343                          'EXISTS(C inlined_note B, B owned_by D, B is Note), '
       
   344                          'EXISTS(C inlined_note F, F owned_by D, F is Note), '
       
   345                          'A is Affaire, C is Card')
   319 
   346 
   320     def test_unsupported_constraint_1(self):
   347     def test_unsupported_constraint_1(self):
   321         # CWUser doesn't have require_permission
   348         # CWUser doesn't have require_permission
   322         trinfo_constraint = ('X wf_info_for Y, Y require_permission P, P name "read"')
   349         trinfo_constraint = ('X wf_info_for Y, Y require_permission P, P name "read"')
   323         rqlst = parse('Any U,T WHERE U is CWUser, T wf_info_for U')
   350         rqlst = parse('Any U,T WHERE U is CWUser, T wf_info_for U')
   457         c_bad = ERQLExpression('X documented_by R, A in_state R')
   484         c_bad = ERQLExpression('X documented_by R, A in_state R')
   458 
   485 
   459         rqlst = parse('Any A, R WHERE A ref R, S is Affaire')
   486         rqlst = parse('Any A, R WHERE A ref R, S is Affaire')
   460         rewrite(rqlst, {('A', 'X'): (c_ok, c_bad)}, {})
   487         rewrite(rqlst, {('A', 'X'): (c_ok, c_bad)}, {})
   461 
   488 
       
   489 
       
   490 from cubicweb.devtools.testlib import CubicWebTC
       
   491 from logilab.common.decorators import classproperty
       
   492 
       
   493 class RewriteFullTC(CubicWebTC):
       
   494     @classproperty
       
   495     def config(cls):
       
   496         return BaseApptestConfiguration(apphome=cls.datapath('rewrite'))
       
   497 
       
   498     def process(self, rql, args=None):
       
   499         if args is None:
       
   500             args = {}
       
   501         querier = self.repo.querier
       
   502         union = querier.parse(rql)
       
   503         querier.solutions(self.session, union, args)
       
   504         querier._annotate(union)
       
   505         plan = querier.plan_factory(union, args, self.session)
       
   506         plan.preprocess(union)
       
   507         return union
       
   508 
       
   509     def test_ambiguous_optional_same_exprs(self):
       
   510         """See #3013535"""
       
   511         edef1 = self.schema['Societe']
       
   512         edef2 = self.schema['Division']
       
   513         edef3 = self.schema['Note']
       
   514         with self.temporary_permissions((edef1, {'read': (ERQLExpression('X owned_by U'),)}),
       
   515                                         (edef2, {'read': (ERQLExpression('X owned_by U'),)}),
       
   516                                         (edef3, {'read': (ERQLExpression('X owned_by U'),)})):
       
   517             union = self.process('Any A,AR,X,CD WHERE A concerne X?, A ref AR, X creation_date CD')
       
   518             self.assertEqual('Any A,AR,X,CD WHERE A concerne X?, A ref AR, A is Affaire '
       
   519                              'WITH X,CD BEING (Any X,CD WHERE X creation_date CD, '
       
   520                              'EXISTS(X owned_by %(A)s), X is IN(Division, Note, Societe))',
       
   521                              union.as_string())
       
   522 
       
   523 
       
   524     def test_xxxx(self):
       
   525         edef1 = self.schema['Societe']
       
   526         edef2 = self.schema['Division']
       
   527         read_expr = ERQLExpression('X responsable E, U has_read_permission E')
       
   528         with self.temporary_permissions((edef1, {'read': (read_expr,)}),
       
   529                                         (edef2, {'read': (read_expr,)})):
       
   530             union = self.process('Any X,AA,AC,AD ORDERBY AD DESC '
       
   531                                  'WHERE X responsable E, X nom AA, '
       
   532                                  'X responsable AC?, AC modification_date AD')
       
   533             self.assertEqual('Any X,AA,AC,AD ORDERBY AD DESC '
       
   534                              'WHERE X responsable E, X nom AA, '
       
   535                              'X responsable AC?, AC modification_date AD, '
       
   536                              'AC is CWUser, E is CWUser, X is IN(Division, Societe)',
       
   537                              union.as_string())
       
   538 
   462 if __name__ == '__main__':
   539 if __name__ == '__main__':
   463     unittest_main()
   540     unittest_main()