test/unittest_stcheck.py
author Philippe Pepiot <philippe.pepiot@logilab.fr>
Wed, 06 Nov 2019 11:27:36 +0100
changeset 868 3ee87bec4482
parent 861 d5e1e2b627fc
child 869 d521e746de34
permissions -rw-r--r--
Stop building python2 wheels and workaround broken py38 wheel This script is used to publish wheel binary packages to pypi. Since we're going to remove python2 support, stop generating wheels for python2. Also py38 is out and our wheel doesn't compile with py38 due to a bug of logilab-common. Waiting a fix, don't build wheel for py38. % docker run --rm -it quay.io/pypa/manylinux1_x86_64 ls -1A /opt/python cp27-cp27m cp27-cp27mu cp34-cp34m cp35-cp35m cp36-cp36m cp37-cp37m cp38-cp38

# copyright 2004-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of rql.
#
# rql is free software: you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
#
# rql is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with rql. If not, see <http://www.gnu.org/licenses/>.

from __future__ import print_function
import six

from rql import RQLHelper, BadRQLQuery, stmts, nodes
from unittest_analyze import DummySchema

if six.PY2:
    import unittest2 as unittest
else:
    import unittest

BAD_QUERIES = (
    'Any X, Y GROUPBY X',

    'Any X WHERE X name Person',

    'Any X WHERE X name nofunction(Y)',

    'Any X WHERE X name nofunction(Y)',

    'Any Y WHERE X name "toto"',

    'Any X WHERE X noattr "toto"',

    'Any X WHERE X is NonExistant',

    'Any UPPER(Y) WHERE X name "toto"',

    'Any C ORDERBY N where C located P, P eid %(x)s',

    'Any X, MAX(COUNT(B)) GROUPBY X WHERE B concerns X;',

    '(Any X WHERE X nom "toto") UNION (Any X,F WHERE X firstname F);',

    'Any X, X/Z WHERE X is Person; WITH Y BEING (Any SUM(X) WHERE X is Person)',

    'Any X WHERE X name "Toto", P is Person',

    "Any X WHERE X eid 0, X eid 1",

    # DISTINCT+ORDERBY tests ###################################################
    # cant sort on Y, B <- work_for X is multivalued
    'DISTINCT Any X ORDERBY Y WHERE B work_for X, B name Y',
    # cant sort on PN, there may be different PF values for the same PN value
    # XXX untrue if PF or PN is marked as unique
    'DISTINCT Any PF ORDERBY PN WHERE P firstname PF, P name PN',
    # cant sort on XN, there may be different PF values for the same PF value
    'DISTINCT Any PF ORDERBY X WHERE P work_for X, P firstname PF',
    'DISTINCT Any PF ORDERBY XN WHERE P work_for X, P firstname PF, X name XN',
)

OK_QUERIES = (
    '(Any N,COUNT(X) GROUPBY N WHERE X name N)'
    ' UNION '
    '(Any N,COUNT(X) GROUPBY N WHERE X firstname N)',

    'DISTINCT Any X, MAX(Y) GROUPBY X WHERE X is Person, Y is Company',

    # DISTINCT+ORDERBY tests ###################################################
    # sorting allowed since order variable reachable from a selected
    # variable with only ?1 cardinality
    'DISTINCT Any P ORDERBY PN WHERE P work_for X, P name PN',
    'DISTINCT Any P ORDERBY XN WHERE P work_for X, X name XN',
    'Any X WHERE X eid > 0, X eid < 42',
    'Any X WHERE X eid 1, X eid < 42',
    'Any X WHERE X number CAST(Int, Y), X name Y',
    'SET X number CAST(Int, Y) WHERE X name Y',
)


class CheckClassTest(unittest.TestCase):
    """check wrong queries are correctly detected"""

    def setUp(self):
        helper = RQLHelper(DummySchema(), None, {'eid': 'uid'})
        self.parse = helper.parse
        self.simplify = helper.simplify

    def _test(self, rql):
        try:
            self.assertRaises(BadRQLQuery, self.parse, rql)
        except Exception:
            print(rql)
            raise

    def test_raise(self):
        for rql in BAD_QUERIES:
            with self.subTest(rql=rql):
                self._test(rql)

    def test_ok(self):
        for rql in OK_QUERIES:
            with self.subTest(rql=rql):
                self.parse(rql)

    def _test_rewrite(self, rql, expected):
        rqlst = self.parse(rql)
        self.simplify(rqlst)
        self.assertEqual(rqlst.as_string(), expected)

    def test_rewrite(self):
        for rql, expected in (
            ('Person X',
             'Any X WHERE X is Person'),
            ("Any X WHERE X eid IN (12), X name 'toto'",
             'Any X WHERE X eid 12, X name "toto"'),
            ('Any X WHERE X work_for Y, Y eid 12',
             'Any X WHERE X work_for 12'),
            ('Any X WHERE Y work_for X, Y eid 12',
             'Any X WHERE 12 work_for X'),
            ('Any X WHERE X work_for Y, Y eid IN (12)',
             'Any X WHERE X work_for 12'),
            ('Any X ORDERBY Y WHERE X work_for Y, Y eid IN (12)',
             'Any X WHERE X work_for 12'),
            ('Any X WHERE X eid 12',
             'Any 12'),
            ('Any X WHERE X is Person, X eid 12',
             'Any 12'),
            ('Any X,Y WHERE X eid 0, Y eid 1, X work_for Y',
             'Any 0,1 WHERE 0 work_for 1'),
            # test symmetric OR rewrite
            ("DISTINCT Any P WHERE P connait S OR S connait P, S name 'chouette'",
             'DISTINCT Any P WHERE P connait S, S name "chouette"'),
            # queries that should not be rewritten
            ('DELETE Person X WHERE X eid 12',
             'DELETE Person X WHERE X eid 12'),
            ('Any X WHERE X work_for Y, Y eid IN (12, 13)',
             'Any X WHERE X work_for Y, Y eid IN(12, 13)'),
            ('Any X WHERE X work_for Y, NOT Y eid 12',
             'Any X WHERE X work_for Y, NOT Y eid 12'),
            ('Any X WHERE NOT X eid 12',
             'Any X WHERE NOT X eid 12'),
            ('Any N WHERE X eid 12, X name N',
             'Any N WHERE X eid 12, X name N'),

            ('Any X WHERE X eid > 12',
             'Any X WHERE X eid > 12'),

            ('Any X WHERE X eid 12, X connait P?, X work_for Y',
             'Any X WHERE X eid 12, X connait P?, X work_for Y'),
            ('Any X WHERE X eid 12, P? connait X',
             'Any X WHERE X eid 12, P? connait X'),

            ("Any X WHERE X firstname 'lulu',"
             "EXISTS (X owned_by U, U name 'lulufanclub' OR U name 'managers');",
             'Any X WHERE X firstname "lulu", '
             'EXISTS(X owned_by U, (U name "lulufanclub") OR (U name "managers"))'),

            ('Any X WHERE X eid 12, EXISTS(X name "hop" OR X work_for Y?)',
             'Any 12 WHERE EXISTS((A name "hop") OR (A work_for Y?), 12 identity A)'),

            ('(Any X WHERE X eid 12) UNION (Any X ORDERBY X WHERE X eid 13)',
             '(Any 12) UNION (Any 13)'),

            ('Any X WITH X BEING (Any X WHERE X eid 12)',
             'Any X WITH X BEING (Any 12)'),

            ('Any X GROUPBY X WHERE X eid 12, X connait Y HAVING COUNT(Y) > 10',
             'Any X GROUPBY X WHERE X eid 12, X connait Y HAVING COUNT(Y) > 10'),

            # A eid 12 can be removed since the type analyzer checked its existence
            ('Any X WHERE A eid 12, X connait Y',
             'Any X WHERE X connait Y'),

            ('Any X WHERE EXISTS(X work_for Y, Y eid 12) OR X eid 12',
             'Any X WHERE (EXISTS(X work_for 12)) OR (X eid 12)'),

            ('Any X WHERE EXISTS(X work_for Y, Y eid IN (12)) OR X eid IN (12)',
             'Any X WHERE (EXISTS(X work_for 12)) OR (X eid 12)'),
        ):
            with self.subTest(rql=rql):
                self._test_rewrite(rql, expected)

    def test_subquery_graphdict(self):
        # test two things:
        # * we get graph information from subquery
        # * we see that we can sort on VCS (eg we have a unique value path from VF to VCD)
        rqlst = self.parse(('DISTINCT Any VF ORDERBY VCD DESC WHERE '
                            'VC work_for S, S name "draft" '
                            'WITH VF, VC, VCD BEING (Any VF, MAX(VC), VCD GROUPBY VF, VCD '
                            '                        WHERE VC connait VF, VC creation_date VCD)'))
        self.assertEqual(rqlst.children[0].vargraph,
                         {'VCD': ['VC'], 'VF': ['VC'], 'S': ['VC'], 'VC': ['S', 'VF', 'VCD'],
                          ('VC', 'S'): 'work_for',
                          ('VC', 'VF'): 'connait',
                          ('VC', 'VCD'): 'creation_date'})
        self.assertEqual(rqlst.children[0].aggregated, set(('VC',)))


class CopyTest(unittest.TestCase):

    def setUp(self):
        helper = RQLHelper(DummySchema(), None, {'eid': 'uid'})
        self.parse = helper.parse
        self.simplify = helper.simplify
        self.annotate = helper.annotate

    def test_copy_exists(self):
        tree = self.parse(
            "Any X WHERE X firstname 'lulu',"
            "EXISTS (X owned_by U, U work_for G, G name 'lulufanclub' OR G name 'managers');"
        )
        self.simplify(tree)
        copy = tree.copy()
        exists = copy.get_nodes(nodes.Exists)[0]
        self.assertTrue(exists.children[0].parent is exists)
        self.assertTrue(exists.parent)

    def test_copy_internals(self):
        root = self.parse('Any X,U WHERE C owned_by U, NOT X owned_by U, X eid 1, C eid 2')
        self.simplify(root)
        stmt = root.children[0]
        self.assertEqual(stmt.defined_vars['U'].valuable_references(), 3)
        copy = stmts.Select()
        copy.append_selected(stmt.selection[0].copy(copy))
        copy.append_selected(stmt.selection[1].copy(copy))
        copy.set_where(stmt.where.copy(copy))
        newroot = stmts.Union()
        newroot.append(copy)
        self.annotate(newroot)
        self.simplify(newroot)
        self.assertEqual(
            newroot.as_string(),
            'Any 1,U WHERE 2 owned_by U, NOT EXISTS(1 owned_by U)',
        )
        self.assertEqual(copy.defined_vars['U'].valuable_references(), 3)


class AnnotateTest(unittest.TestCase):

    def setUp(self):
        helper = RQLHelper(DummySchema(), None, {'eid': 'uid'})
        self.parse = helper.parse

    def test_is_rel_no_scope_1(self):
        """is relation used as type restriction should not affect variable's
        scope, and should not be included in stinfo['relations']
        """
        rqlst = self.parse('Any X WHERE C is Company, EXISTS(X work_for C)').children[0]
        C = rqlst.defined_vars['C']
        self.assertFalse(C.scope is rqlst, C.scope)
        self.assertEqual(len(C.stinfo['relations']), 1)

    def test_is_rel_no_scope_2(self):
        rqlst = self.parse('Any X, ET WHERE C is ET, EXISTS(X work_for C)').children[0]
        C = rqlst.defined_vars['C']
        self.assertTrue(C.scope is rqlst, C.scope)
        self.assertEqual(len(C.stinfo['relations']), 2)

    def test_not_rel_normalization_1(self):
        rqlst = self.parse('Any X WHERE C is Company, NOT X work_for C').children[0]
        self.assertEqual(rqlst.as_string(), 'Any X WHERE C is Company, NOT EXISTS(X work_for C)')
        C = rqlst.defined_vars['C']
        self.assertFalse(C.scope is rqlst, C.scope)

    def test_not_rel_normalization_2(self):
        rqlst = self.parse('Any X, ET WHERE C is ET, NOT X work_for C').children[0]
        self.assertEqual(rqlst.as_string(), 'Any X,ET WHERE C is ET, NOT EXISTS(X work_for C)')
        C = rqlst.defined_vars['C']
        self.assertTrue(C.scope is rqlst, C.scope)

    def test_not_rel_normalization_3(self):
        rqlst = self.parse(
            'Any X WHERE C is Company, X work_for C, NOT C name "World Company"'
        ).children[0]
        self.assertEqual(
            rqlst.as_string(),
            'Any X WHERE C is Company, X work_for C, NOT C name "World Company"',
        )
        C = rqlst.defined_vars['C']
        self.assertTrue(C.scope is rqlst, C.scope)

    def test_not_rel_normalization_4(self):
        rqlst = self.parse(
            'Any X WHERE C is Company, NOT (X work_for C, C name "World Company")'
        ).children[0]
        self.assertEqual(
            rqlst.as_string(),
            'Any X WHERE C is Company, NOT EXISTS(X work_for C, C name "World Company")',
        )
        C = rqlst.defined_vars['C']
        self.assertFalse(C.scope is rqlst, C.scope)

    def test_not_rel_normalization_5(self):
        rqlst = self.parse(
            'Any X WHERE X work_for C, '
            'EXISTS(C identity D, NOT Y work_for D, D name "World Company")'
        ).children[0]
        self.assertEqual(
            rqlst.as_string(),
            'Any X WHERE X work_for C, '
            'EXISTS(C identity D, NOT EXISTS(Y work_for D), D name "World Company")',
        )
        D = rqlst.defined_vars['D']
        D = rqlst.defined_vars['D']
        self.assertFalse(D.scope is rqlst, D.scope)
        self.assertTrue(D.scope.parent.scope is rqlst, D.scope.parent.scope)

    def test_subquery_annotation_1(self):
        rqlst = self.parse(
            'Any X WITH X BEING (Any X WHERE C is Company, EXISTS(X work_for C))'
        ).children[0]
        C = rqlst.with_[0].query.children[0].defined_vars['C']
        self.assertFalse(C.scope is rqlst, C.scope)
        self.assertEqual(len(C.stinfo['relations']), 1)

    def test_subquery_annotation_2(self):
        rqlst = self.parse(
            'Any X,ET WITH X,ET BEING (Any X, ET WHERE C is ET, EXISTS(X work_for C))'
        ).children[0]
        C = rqlst.with_[0].query.children[0].defined_vars['C']
        self.assertTrue(C.scope is rqlst.with_[0].query.children[0], C.scope)
        self.assertEqual(len(C.stinfo['relations']), 2)
        X = rqlst.get_variable('X')
        self.assertTrue(X.scope is rqlst, X.scope)

    def test_no_attr_var_if_uid_rel(self):
        with self.assertRaises(BadRQLQuery) as cm:
            self.parse('Any X, Y WHERE X work_for Z, Y work_for Z, X eid > Y')
        self.assertEqual(
            str(cm.exception),
            'variable Y should not be used as rhs of attribute relation X eid > Y',
        )

    def test_no_uid_rel_if_not_constant(self):
        rqlst = self.parse('Any X,EID WHERE X eid EID').children[0]
        self.assertEqual(rqlst.defined_vars['X'].stinfo['uidrel'], None)
        rqlst = self.parse('Any X WHERE X eid IN (1,2,3)').children[0]
        self.assertTrue(rqlst.defined_vars['X'].stinfo['uidrel'])


if __name__ == '__main__':
    unittest.main()