|
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 |