spa2rql.py
changeset 2422 96da7dc42eb5
child 2427 3e0ef847a546
equal deleted inserted replaced
2418:8f06e4f02733 2422:96da7dc42eb5
       
     1 """SPARQL -> RQL translator
       
     2 
       
     3 :organization: Logilab
       
     4 :copyright: 2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
       
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     6 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
       
     7 """
       
     8 from logilab.common import make_domains
       
     9 from rql import TypeResolverException
       
    10 from fyzz.yappsparser import parse
       
    11 from fyzz import ast
       
    12 
       
    13 from cubicweb.xy import xy
       
    14 
       
    15 
       
    16 class UnsupportedQuery(Exception): pass
       
    17 
       
    18 class QueryInfo(object):
       
    19     def __init__(self, sparqlst):
       
    20         self.sparqlst = sparqlst
       
    21         if sparqlst.selected == ['*']:
       
    22             self.selection = [var.upper() for var in sparqlst.variables]
       
    23         else:
       
    24             self.selection = [var.name.upper() for var in sparqlst.selected]
       
    25         self.possible_types = {}
       
    26         self.union_params = []
       
    27         self.restrictions = []
       
    28 
       
    29     def finalize(self):
       
    30         for varname, ptypes in self.possible_types.iteritems():
       
    31             if len(ptypes) == 1:
       
    32                 self.restrictions.append('%s is %s' % (varname, iter(ptypes).next()))
       
    33         unions = []
       
    34         for releq, subjvar, objvar in self.union_params:
       
    35             thisunions = []
       
    36             for st, rt, ot in releq:
       
    37                 thisunions.append(['%s %s %s' % (subjvar, rt, objvar)])
       
    38                 if st != '*':
       
    39                     thisunions[-1].append('%s is %s' % (subjvar, st))
       
    40                 if ot != '*':
       
    41                     thisunions[-1].append('%s is %s' % (objvar, ot))
       
    42             if not unions:
       
    43                 unions = thisunions
       
    44             else:
       
    45                 unions = zip(*make_domains([unions, thisunions]))
       
    46         baserql = 'Any %s WHERE %s' % (', '.join(self.selection),
       
    47                                        ', '.join(self.restrictions))
       
    48         if not unions:
       
    49             return baserql
       
    50         rqls = ['(%s, %s)' % (baserql, ', '.join(unionrestrs))
       
    51                 for unionrestrs in unions]
       
    52         return ' UNION '.join(rqls)
       
    53 
       
    54     def set_possible_types(self, var, varpossibletypes):
       
    55         varpossibletypes = set(varpossibletypes)
       
    56         try:
       
    57             self.possible_types[var] &= varpossibletypes
       
    58             if not self.possible_types[var]:
       
    59                 raise TypeResolverException()
       
    60         except KeyError:
       
    61             self.possible_types[var] = varpossibletypes
       
    62 
       
    63 
       
    64 class Sparql2rqlTranslator(object):
       
    65     def __init__(self, yschema):
       
    66         self.yschema = yschema
       
    67 
       
    68     def translate(self, sparql):
       
    69         sparqlst = parse(sparql)
       
    70         if sparqlst.type != 'select':
       
    71             raise UnsupportedQuery()
       
    72         qi = QueryInfo(sparqlst)
       
    73         for subj, predicate, obj in sparqlst.where:
       
    74             if not isinstance(subj, ast.SparqlVar):
       
    75                 raise UnsupportedQuery()
       
    76             subjvar = subj.name.upper()
       
    77             if predicate == ('', 'a'): # special 'is' relation
       
    78                 if not isinstance(obj, tuple):
       
    79                     raise UnsupportedQuery()
       
    80                 qi.set_possible_types(
       
    81                     subjvar, xy.yeq(':'.join(obj), isentity=True))
       
    82             else:
       
    83                 if not isinstance(predicate, tuple):
       
    84                     raise UnsupportedQuery()
       
    85                 releq = xy.yeq(':'.join(predicate))
       
    86                 svptypes = set(s for s, r, o in releq)
       
    87                 if not '*' in svptypes:
       
    88                     qi.set_possible_types(subjvar, svptypes)
       
    89                 if subjvar in qi.possible_types:
       
    90                     releq = [(s, r, o) for s, r, o in releq
       
    91                              if s == '*' or s in qi.possible_types[subjvar]]
       
    92                 if isinstance(obj, ast.SparqlVar):
       
    93                     objvar = obj.name.upper()
       
    94                     ovptypes = set(o for s, r, o in releq)
       
    95                     if not '*' in ovptypes:
       
    96                         qi.set_possible_types(objvar, ovptypes)
       
    97                     if objvar in qi.possible_types:
       
    98                         releq = [(s, r, o) for s, r, o in releq
       
    99                                  if o == '*' or o in qi.possible_types[objvar]]
       
   100                 else:
       
   101                     raise UnsupportedQuery()
       
   102                 rel = releq[0]
       
   103                 for s, r, o in releq[1:]:
       
   104                     if r != rel[1]:
       
   105                         qi.union_params.append((releq, subjvar, objvar))
       
   106                         break
       
   107                 else:
       
   108                     qi.restrictions.append('%s %s %s' % (subj.name.upper(),
       
   109                                                          rel[1],
       
   110                                                          obj.name.upper()))
       
   111         return qi