diff -r 058bb3dc685f -r 0b59724cb3f2 spa2rql.py --- a/spa2rql.py Mon Jan 04 18:40:30 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,220 +0,0 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb 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. -# -# CubicWeb 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 CubicWeb. If not, see . -"""SPARQL -> RQL translator""" - -from logilab.common import make_domains -from rql import TypeResolverException -from fyzz.yappsparser import parse -from fyzz import ast - -from cubicweb.xy import xy - - -class UnsupportedQuery(Exception): pass - -def order_limit_offset(sparqlst): - addons = '' - if sparqlst.orderby: - sortterms = ', '.join('%s %s' % (var.name.upper(), ascdesc.upper()) - for var, ascdesc in sparqlst.orderby) - addons += ' ORDERBY %s' % sortterms - if sparqlst.limit: - addons += ' LIMIT %s' % sparqlst.limit - if sparqlst.offset: - addons += ' OFFSET %s' % sparqlst.offset - return addons - - -class QueryInfo(object): - """wrapper class containing necessary information to generate a RQL query - from a sparql syntax tree - """ - def __init__(self, sparqlst): - self.sparqlst = sparqlst - if sparqlst.selected == ['*']: - self.selection = [var.upper() for var in sparqlst.variables] - else: - self.selection = [var.name.upper() for var in sparqlst.selected] - self.possible_types = {} - self.infer_types_info = [] - self.union_params = [] - self.restrictions = [] - self.literals = {} - self._litcount = 0 - - def add_literal(self, value): - key = chr(ord('a') + self._litcount) - self._litcount += 1 - self.literals[key] = value - return key - - def set_possible_types(self, var, varpossibletypes): - """set/restrict possible types for the given variable. - - :return: True if something changed, else false. - :raise: TypeResolverException if no more type allowed - """ - varpossibletypes = set(varpossibletypes) - try: - ctypes = self.possible_types[var] - nbctypes = len(ctypes) - ctypes &= varpossibletypes - if not ctypes: - raise TypeResolverException('No possible type') - return len(ctypes) != nbctypes - except KeyError: - self.possible_types[var] = varpossibletypes - return True - - def infer_types(self): - # XXX should use something similar to rql.analyze for proper type inference - modified = True - # loop to infer types until nothing changed - while modified: - modified = False - for yams_predicates, subjvar, obj in self.infer_types_info: - nbchoices = len(yams_predicates) - # get possible types for the subject variable, according to the - # current predicate - svptypes = set(s for s, r, o in yams_predicates) - if not '*' in svptypes: - if self.set_possible_types(subjvar, svptypes): - modified = True - # restrict predicates according to allowed subject var types - if subjvar in self.possible_types: - yams_predicates[:] = [(s, r, o) for s, r, o in yams_predicates - if s == '*' or s in self.possible_types[subjvar]] - if isinstance(obj, ast.SparqlVar): - # make a valid rql var name - objvar = obj.name.upper() - # get possible types for the object variable, according to - # the current predicate - ovptypes = set(o for s, r, o in yams_predicates) - if not '*' in ovptypes: - if self.set_possible_types(objvar, ovptypes): - modified = True - # restrict predicates according to allowed object var types - if objvar in self.possible_types: - yams_predicates[:] = [(s, r, o) for s, r, o in yams_predicates - if o == '*' or o in self.possible_types[objvar]] - # ensure this still make sense - if not yams_predicates: - raise TypeResolverException('No yams predicate') - if len(yams_predicates) != nbchoices: - modified = True - - def build_restrictions(self): - # now, for each predicate - for yams_predicates, subjvar, obj in self.infer_types_info: - rel = yams_predicates[0] - # if there are several yams relation type equivalences, we will have - # to generate several unioned rql queries - for s, r, o in yams_predicates[1:]: - if r != rel[1]: - self.union_params.append((yams_predicates, subjvar, obj)) - break - # else we can simply add it to base rql restrictions - else: - restr = self.build_restriction(subjvar, rel[1], obj) - self.restrictions.append(restr) - - def build_restriction(self, subjvar, rtype, obj): - if isinstance(obj, ast.SparqlLiteral): - key = self.add_literal(obj.value) - objvar = '%%(%s)s' % key - else: - assert isinstance(obj, ast.SparqlVar) - # make a valid rql var name - objvar = obj.name.upper() - # else we can simply add it to base rql restrictions - return '%s %s %s' % (subjvar, rtype, objvar) - - def finalize(self): - """return corresponding rql query (string) / args (dict)""" - for varname, ptypes in self.possible_types.items(): - if len(ptypes) == 1: - self.restrictions.append('%s is %s' % (varname, next(iter(ptypes)))) - unions = [] - for releq, subjvar, obj in self.union_params: - thisunions = [] - for st, rt, ot in releq: - thisunions.append([self.build_restriction(subjvar, rt, obj)]) - if st != '*': - thisunions[-1].append('%s is %s' % (subjvar, st)) - if isinstance(obj, ast.SparqlVar) and ot != '*': - objvar = obj.name.upper() - thisunions[-1].append('%s is %s' % (objvar, objvar)) - if not unions: - unions = thisunions - else: - unions = zip(*make_domains([unions, thisunions])) - selection = 'Any ' + ', '.join(self.selection) - sparqlst = self.sparqlst - if sparqlst.distinct: - selection = 'DISTINCT ' + selection - if unions: - baserql = '%s WHERE %s' % (selection, ', '.join(self.restrictions)) - rqls = ['(%s, %s)' % (baserql, ', '.join(unionrestrs)) - for unionrestrs in unions] - rql = ' UNION '.join(rqls) - if sparqlst.orderby or sparqlst.limit or sparqlst.offset: - rql = '%s%s WITH %s BEING (%s)' % ( - selection, order_limit_offset(sparqlst), - ', '.join(self.selection), rql) - else: - rql = '%s%s WHERE %s' % (selection, order_limit_offset(sparqlst), - ', '.join(self.restrictions)) - return rql, self.literals - - -class Sparql2rqlTranslator(object): - def __init__(self, yschema): - self.yschema = yschema - - def translate(self, sparql): - sparqlst = parse(sparql) - if sparqlst.type != 'select': - raise UnsupportedQuery() - qi = QueryInfo(sparqlst) - for subj, predicate, obj in sparqlst.where: - if not isinstance(subj, ast.SparqlVar): - raise UnsupportedQuery() - # make a valid rql var name - subjvar = subj.name.upper() - if predicate in [('', 'a'), - ('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'type')]: - # special 'is' relation - if not isinstance(obj, tuple): - raise UnsupportedQuery() - # restrict possible types for the subject variable - qi.set_possible_types( - subjvar, xy.yeq(':'.join(obj), isentity=True)) - else: - # 'regular' relation (eg not 'is') - if not isinstance(predicate, tuple): - raise UnsupportedQuery() - # list of 3-uple - # (yams etype (subject), yams rtype, yams etype (object)) - # where subject / object entity type may '*' if not specified - yams_predicates = xy.yeq(':'.join(predicate)) - qi.infer_types_info.append((yams_predicates, subjvar, obj)) - if not isinstance(obj, (ast.SparqlLiteral, ast.SparqlVar)): - raise UnsupportedQuery() - qi.infer_types() - qi.build_restrictions() - return qi