[node] fix add_type_restriction (closes #81817)

authorAurelien Campeas <aurelien.campeas@logilab.fr>
changeset33ac0225993e
branchdefault
phasepublic
hiddenno
parent revision#1965eda264bd fix Referencable.get_type crash when no solution given and 'is IN(ET1, ET2..) is used. Closes #81865
child revision#bb70a998ced6 0.31
files modified by this revision
ChangeLog
nodes.py
test/unittest_nodes.py
# HG changeset patch
# User Aurelien Campeas <aurelien.campeas@logilab.fr>
# Date 1320859057 -3600
# Wed Nov 09 18:17:37 2011 +0100
# Node ID 33ac0225993ec40ae593bb3f7b1912a8383efca2
# Parent 1965eda264bd152da47e49553b929a6fcc6c5f91
[node] fix add_type_restriction (closes #81817)

diff --git a/ChangeLog b/ChangeLog
@@ -5,11 +5,11 @@
1      * #78681: don't crash on column aliases used in outer join
2      * #81394: HAVING support in write queries (INSERT,SET,DELETE)
3      * #80799: fix wrong type analysis with 'NOT identity'
4      * when possible, use entity type as translation context of relation
5        (break cw < 3.13.10 compat)
6 -
7 +    * #81817: fix add_type_restriction for cases where some types restriction is already in there
8 
9  2011-09-07  --  0.30.1
10 
11      * #74727: allow entity types to end with a capitalized letter
12        provided they contain a lower-cased letter
diff --git a/nodes.py b/nodes.py
@@ -28,11 +28,11 @@
13  from datetime import datetime, date, time, timedelta
14  from time import localtime
15 
16  from logilab.database import DYNAMIC_RTYPE
17 
18 -from rql import CoercionError
19 +from rql import CoercionError, RQLException
20  from rql.base import BaseNode, Node, BinaryNode, LeafNode
21  from rql.utils import (function_description, quote, uquote, build_visitor_stub,
22                         common_parent)
23 
24  CONSTANT_TYPES = frozenset((None, 'Date', 'Datetime', 'Boolean', 'Float', 'Int',
@@ -216,10 +216,43 @@
25          assert c_type in ('Int', 'Substitute'), "Error got c_type=%r in eid restriction" % c_type
26          return self.add_constant_restriction(var, 'eid', eid, c_type)
27 
28      def add_type_restriction(self, var, etype):
29          """builds a restriction node to express : variable is etype"""
30 +        typerel = var.stinfo.get('typerel', None)
31 +        if typerel:
32 +            istarget = typerel.children[1].children[0]
33 +            if typerel.r_type == 'is':
34 +                if isinstance(istarget, Constant):
35 +                    etypes = (istarget.value,)
36 +                else: # Function (IN)
37 +                    etypes = [et.value for et in istarget.children]
38 +                if etype not in etypes:
39 +                    raise RQLException('%r not in %r' % (etype, etypes))
40 +                if len(etypes) > 1:
41 +                    for child in istarget.children:
42 +                        if child.value != etype:
43 +                            istarget.remove(child)
44 +            else:
45 +                # let's botte en touche IN cases (who would do that anyway ?)
46 +                if isinstance(istarget, Function):
47 +                    msg = 'adding type restriction over is_instance_of IN is not supported'
48 +                    raise NotImplementedError(msg)
49 +                schema = self.root.schema
50 +                if schema is None:
51 +                    msg = 'restriction with is_instance_of cannot be done without a schema'
52 +                    raise RQLException(msg)
53 +                # let's check the restriction is compatible
54 +                eschema = schema[etype]
55 +                ancestors = set(eschema.ancestors())
56 +                ancestors.add(etype) # let's be unstrict
57 +                if istarget.value in ancestors:
58 +                    istarget.value = etype
59 +                else:
60 +                    raise RQLException('type restriction %s-%s cannot be made on %s' %
61 +                                       (var, etype, self))
62 +            return typerel
63          return self.add_constant_restriction(var, 'is', etype, 'etype')
64 
65 
66  # base RQL nodes ##############################################################
67 
diff --git a/test/unittest_nodes.py b/test/unittest_nodes.py
@@ -19,11 +19,11 @@
68 
69  from datetime import date, datetime
70 
71  from logilab.common.testlib import TestCase, unittest_main
72 
73 -from rql import nodes, stmts, parse, BadRQLQuery, RQLHelper
74 +from rql import nodes, stmts, parse, BadRQLQuery, RQLHelper, RQLException
75 
76  from unittest_analyze import DummySchema
77  schema = DummySchema()
78  from rql.stcheck import RQLSTAnnotator
79  annotator = RQLSTAnnotator(schema, {})
@@ -53,10 +53,47 @@
80      def test_string(self):
81          self.assertEqual(nodes.etype_from_pyobj('hop'), 'String')
82          self.assertEqual(nodes.etype_from_pyobj(u'hop'), 'String')
83 
84 
85 +class TypesRestrictionNodesTest(TestCase):
86 +
87 +    def setUp(self):
88 +        self.parse = helper.parse
89 +        self.simplify = helper.simplify
90 +
91 +    def test_add_is_type_restriction(self):
92 +        tree = self.parse('Any X WHERE X is Person')
93 +        select = tree.children[0]
94 +        x = select.get_selected_variables().next()
95 +        self.assertRaises(RQLException, select.add_type_restriction, x.variable, 'Babar')
96 +        select.add_type_restriction(x.variable, 'Person')
97 +        self.assertEqual(tree.as_string(), 'Any X WHERE X is Person')
98 +
99 +    def test_add_new_is_type_restriction_in(self):
100 +        tree = self.parse('Any X WHERE X is IN(Person, Company)')
101 +        select = tree.children[0]
102 +        x = select.get_selected_variables().next()
103 +        select.add_type_restriction(x.variable, 'Company')
104 +        # implementation is KISS (the IN remains)
105 +        self.assertEqual(tree.as_string(), 'Any X WHERE X is IN(Company)')
106 +
107 +    def test_add_is_in_type_restriction(self):
108 +        tree = self.parse('Any X WHERE X is IN(Person, Company)')
109 +        select = tree.children[0]
110 +        x = select.get_selected_variables().next()
111 +        self.assertRaises(RQLException, select.add_type_restriction, x.variable, 'Babar')
112 +        self.assertEqual(tree.as_string(), 'Any X WHERE X is IN(Person, Company)')
113 +
114 +    # XXX a full schema is needed, see test in cw/server/test/unittest_security
115 +    # def test_add_is_against_isintance_type_restriction(self):
116 +    #     tree = self.parse('Any X WHERE X is_instance_of Person')
117 +    #     select = tree.children[0]
118 +    #     x = select.get_selected_variables().next()
119 +    #     select.add_type_restriction(x.variable, 'Student')
120 +    #     self.parse(tree.as_string())
121 +
122  class NodesTest(TestCase):
123      def _parse(self, rql, normrql=None):
124          tree = parse(rql + ';')
125          tree.check_references()
126          if normrql is None: