rqlgen.py
author Rémi Cardona <remi.cardona@free.fr>
Wed, 23 Jul 2014 09:27:19 +0200
changeset 747 3d80ee7bd789
parent 537 ff1c66776652
permissions -rw-r--r--
[py3k] Import range from six.moves

# copyright 2004-2010 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/>.
"""Generation of RQL strings.

"""
__docformat__ = "restructuredtext en"

from six.moves import range

NOT = 1

class RQLGenerator(object):
    """
    Helper class to generate RQL strings.
    """

    def select(self, etype, nupplets=(), groups=(), sorts=()) :
        """
        Return a RQL selection query.

        :Parameters:
         * `etype`: the desired entity type (can be 'Any')

         * `nupplets`: a list of 4-uples (subject, relation, object, not).
           <subject> and <object> may be a string representing a variable
           or a constant. The special variable X represents the searched set
           of entities.
           <relation> is the statement axis.
           <not> is a boolean indicating it should be a negative statement
           (0 -> positive statement, 1 -> negative statement). You may omit
           this parameter, it default to 0.

         * `groups`: a list of variables to use in groups

         * `sorts`: a list of sort term. A sort term is a string designing a
           variable and optionnaly the sort order ('ASC' or 'DESC'). If the
           sort order is omitted default to 'ASC'

        Example:

        >>> s = RQLGenerator()
        >>> s.select('Any', (('X', 'eid', 14),) )
        'Any X\\nWHERE X eid 14'
        >>> s.select('Person',
        ...          ( ('X','work_for','S'), ('S','name','"Logilab"'),
        ...            ('X','firstname','F'), ('X','surname','S') ),
        ...          sorts=('F ASC', 'S DESC')
        ...          )
        'Person X\\nWHERE X work_for S , S name "Logilab" , X firstname F , X surname S\\nSORTBY F ASC, S DESC'
        """
        result = [etype + ' X']
        if nupplets:
            result.append(self.where(nupplets))
        if groups:
            result.append(self.groupby(groups))
        if sorts:
            result.append(self.sortby(sorts))
        return '\n'.join(result)


    def where(self, nupplets):
        """Return a WHERE statement.

        :Parameters:
         * `nupplets`: a list of 4-uples (subject, relation, object, not)
           <subject> and <object> maybe a string designing a variable or a
           constant. The special variable X represents the searched set of
           entities
           <relation> is the statement axis
           <not> is a boolean indicating it should be a negative statement
           (0 -> positive statement, 1 -> negative statement). You may omit
           this parameter, it default to 0.

        Example:

        >>> s = RQLGenerator()
        >>> s.where( (('X', 'eid', 14),) )
        'WHERE X eid 14'
        >>> s.where( ( ('X','work_for','S'), ('S','name','"Logilab"'),
        ...            ('X','firstname','F'), ('X','surname','S') ) )
        'WHERE X work_for S , S name "Logilab" , X firstname F , X surname S'
        """
        result = ['WHERE']
        total = len(nupplets)
        for i in range(total):
            nupplet = nupplets[i]
            if len(nupplet) == 4 and nupplet[3] == NOT:
                result.append('NOT')
            result.append(str(nupplet[0]))
            result.append(str(nupplet[1]))
            result.append(str(nupplet[2]))
            if i < total - 1:
                result.append(',')
        return ' '.join(result)


    def groupby(self, groups):
        """Return a GROUPBY statement.

        :param groups: a list of variables to use in groups

        Example:

        >>> s = RQLGenerator()
        >>> s.groupby(('F', 'S'))
        'GROUPBY F, S'
        """
        return 'GROUPBY %s' % ', '.join(groups)


    def sortby(self, sorts):
        """Return a SORTBY statement.

        :param sorts: a list of sort term. A sort term is a string designing a
          variable and optionnaly the sort order ('ASC' or 'DESC'). If the
          sort order is omitted default to 'ASC'


        Example:

        >>> s = RQLGenerator()
        >>> s.sortby(('F ASC', 'S DESC'))
        'SORTBY F ASC, S DESC'
        """
        return 'SORTBY %s' % ', '.join(sorts)


    def insert(self, etype, attributes):
        """Return an INSERT statement.

        :Parameters:
         * `etype`: the entity type to insert
         * `attributes`: a list of tuples (attr_name, attr_value)

        Example:

        >>> s = RQLGenerator()
        >>> s.insert('Person', (('firstname', "Clark"), ('lastname', "Kent")))
        'INSERT Person X: X firstname "Clark", X lastname "Kent"'
        """
        restrictions = ['X %s "%s"' % (attr_name, attr_value)
                        for attr_name, attr_value in attributes] # .items()]
        return 'INSERT %s X: %s' % (etype, ', '.join(restrictions))


    def delete(self, etype, attributes):
        """Return a DELETE statement.

        :Parameters:
         * `etype`: the entity type to delete
         * `attributes`: a list of tuples (attr_name, attr_value)

        Example:

        >>> s = RQLGenerator()
        >>> s.delete('Person', (('firstname', "Clark"), ('lastname', "Kent")))
        'DELETE Person X where X firstname "Clark", X lastname "Kent"'
        """
        restrictions = ['X %s "%s"' % (attr_name, attr_value)
                        for attr_name, attr_value in attributes] # .items()]
        return 'DELETE %s X where %s' % (etype, ', '.join(restrictions))


    def update(self, etype, old_descr, new_descr):
        """Return a SET statement.

        :Parameters:
         * `etype`: the entity type to update
         * `old_descr`: a list of tuples (attr_name, attr_value)
           that identifies the entity to update
         * `new_descr`: a list of tuples (attr_name, attr_value)
           that defines the attributes to update

        Example:

        >>> s = RQLGenerator()
        >>> s.update('Person', (('firstname', "Clark"), ('lastname', "Kent")),
        ...         (('job', "superhero"), ('nickname', "superman")))
        'SET X job "superhero", X nickname "superman" WHERE X is "Person", X firstname "Clark", X lastname "Kent"'
        """
        old_restrictions = ['X is "%s"' % etype]
        old_restrictions += ['X %s "%s"' % (attr_name, attr_value)
                             for attr_name, attr_value in old_descr]
        new_restrictions = ['X %s "%s"' % (attr_name, attr_value)
                            for attr_name, attr_value in new_descr]
        return 'SET %s WHERE %s' % (', '.join(new_restrictions),
                                    ', '.join(old_restrictions))

RQLGENERATOR = RQLGenerator()

def _test():
    """
    Launch doctest
    """
    import doctest, sys
    return doctest.testmod(sys.modules[__name__])

if __name__ == "__main__":
    _test()