13 from cubicweb.xy import xy |
13 from cubicweb.xy import xy |
14 |
14 |
15 |
15 |
16 class UnsupportedQuery(Exception): pass |
16 class UnsupportedQuery(Exception): pass |
17 |
17 |
|
18 |
18 class QueryInfo(object): |
19 class QueryInfo(object): |
|
20 """wrapper class containing necessary information to generate a RQL query |
|
21 from a sparql syntax tree |
|
22 """ |
19 def __init__(self, sparqlst): |
23 def __init__(self, sparqlst): |
20 self.sparqlst = sparqlst |
24 self.sparqlst = sparqlst |
21 if sparqlst.selected == ['*']: |
25 if sparqlst.selected == ['*']: |
22 self.selection = [var.upper() for var in sparqlst.variables] |
26 self.selection = [var.upper() for var in sparqlst.variables] |
23 else: |
27 else: |
24 self.selection = [var.name.upper() for var in sparqlst.selected] |
28 self.selection = [var.name.upper() for var in sparqlst.selected] |
25 self.possible_types = {} |
29 self.possible_types = {} |
|
30 self.infer_types_info = [] |
26 self.union_params = [] |
31 self.union_params = [] |
27 self.restrictions = [] |
32 self.restrictions = [] |
28 |
33 |
29 def finalize(self): |
34 def finalize(self): |
|
35 """return corresponding rql query""" |
30 for varname, ptypes in self.possible_types.iteritems(): |
36 for varname, ptypes in self.possible_types.iteritems(): |
31 if len(ptypes) == 1: |
37 if len(ptypes) == 1: |
32 self.restrictions.append('%s is %s' % (varname, iter(ptypes).next())) |
38 self.restrictions.append('%s is %s' % (varname, iter(ptypes).next())) |
33 unions = [] |
39 unions = [] |
34 for releq, subjvar, objvar in self.union_params: |
40 for releq, subjvar, objvar in self.union_params: |
52 rqls = ['(%s, %s)' % (baserql, ', '.join(unionrestrs)) |
58 rqls = ['(%s, %s)' % (baserql, ', '.join(unionrestrs)) |
53 for unionrestrs in unions] |
59 for unionrestrs in unions] |
54 return ' UNION '.join(rqls) |
60 return ' UNION '.join(rqls) |
55 |
61 |
56 def set_possible_types(self, var, varpossibletypes): |
62 def set_possible_types(self, var, varpossibletypes): |
|
63 """set/restrict possible types for the given variable. |
|
64 |
|
65 :return: True if something changed, else false. |
|
66 :raise: TypeResolverException if no more type allowed |
|
67 """ |
57 varpossibletypes = set(varpossibletypes) |
68 varpossibletypes = set(varpossibletypes) |
58 try: |
69 try: |
59 self.possible_types[var] &= varpossibletypes |
70 ctypes = self.possible_types[var] |
60 if not self.possible_types[var]: |
71 nbctypes = len(ctypes) |
|
72 ctypes &= varpossibletypes |
|
73 if not ctypes: |
61 raise TypeResolverException() |
74 raise TypeResolverException() |
|
75 return len(ctypes) != nbctypes |
62 except KeyError: |
76 except KeyError: |
63 self.possible_types[var] = varpossibletypes |
77 self.possible_types[var] = varpossibletypes |
|
78 return True |
|
79 |
|
80 def infer_types(self): |
|
81 # XXX should use something similar to rql.analyze for proper type inference |
|
82 modified = True |
|
83 # loop to infer types until nothing changed |
|
84 while modified: |
|
85 modified = False |
|
86 for yams_predicates, subjvar, obj in self.infer_types_info: |
|
87 nbchoices = len(yams_predicates) |
|
88 # get possible types for the subject variable, according to the |
|
89 # current predicate |
|
90 svptypes = set(s for s, r, o in yams_predicates) |
|
91 if not '*' in svptypes: |
|
92 if self.set_possible_types(subjvar, svptypes): |
|
93 modified = True |
|
94 # restrict predicates according to allowed subject var types |
|
95 if subjvar in self.possible_types: |
|
96 yams_predicates = [(s, r, o) for s, r, o in yams_predicates |
|
97 if s == '*' or s in self.possible_types[subjvar]] |
|
98 if isinstance(obj, ast.SparqlVar): |
|
99 # make a valid rql var name |
|
100 objvar = obj.name.upper() |
|
101 # get possible types for the object variable, according to |
|
102 # the current predicate |
|
103 ovptypes = set(o for s, r, o in yams_predicates) |
|
104 if not '*' in ovptypes: |
|
105 if self.set_possible_types(objvar, ovptypes): |
|
106 modified = True |
|
107 # restrict predicates according to allowed object var types |
|
108 if objvar in self.possible_types: |
|
109 yams_predicates = [(s, r, o) for s, r, o in yams_predicates |
|
110 if o == '*' or o in self.possible_types[objvar]] |
|
111 # ensure this still make sense |
|
112 if not yams_predicates: |
|
113 raise TypeResolverException() |
|
114 if len(yams_predicates) != nbchoices: |
|
115 modified = True |
|
116 # now, for each predicate |
|
117 for yams_predicates, subjvar, obj in self.infer_types_info: |
|
118 rel = yams_predicates[0] |
|
119 objvar = obj.name.upper() |
|
120 # if there are several yams relation type equivalences, we will have |
|
121 # to generate several unioned rql queries |
|
122 for s, r, o in yams_predicates[1:]: |
|
123 if r != rel[1]: |
|
124 self.union_params.append((yams_predicates, subjvar, objvar)) |
|
125 break |
|
126 else: |
|
127 # else we can simply add it to base rql restrictions |
|
128 self.restrictions.append('%s %s %s' % (subjvar, rel[1], objvar)) |
64 |
129 |
65 |
130 |
66 class Sparql2rqlTranslator(object): |
131 class Sparql2rqlTranslator(object): |
67 def __init__(self, yschema): |
132 def __init__(self, yschema): |
68 self.yschema = yschema |
133 self.yschema = yschema |
73 raise UnsupportedQuery() |
138 raise UnsupportedQuery() |
74 qi = QueryInfo(sparqlst) |
139 qi = QueryInfo(sparqlst) |
75 for subj, predicate, obj in sparqlst.where: |
140 for subj, predicate, obj in sparqlst.where: |
76 if not isinstance(subj, ast.SparqlVar): |
141 if not isinstance(subj, ast.SparqlVar): |
77 raise UnsupportedQuery() |
142 raise UnsupportedQuery() |
|
143 # make a valid rql var name |
78 subjvar = subj.name.upper() |
144 subjvar = subj.name.upper() |
79 if predicate == ('', 'a'): # special 'is' relation |
145 if predicate == ('', 'a'): |
|
146 # special 'is' relation |
80 if not isinstance(obj, tuple): |
147 if not isinstance(obj, tuple): |
81 raise UnsupportedQuery() |
148 raise UnsupportedQuery() |
|
149 # restrict possible types for the subject variable |
82 qi.set_possible_types( |
150 qi.set_possible_types( |
83 subjvar, xy.yeq(':'.join(obj), isentity=True)) |
151 subjvar, xy.yeq(':'.join(obj), isentity=True)) |
84 else: |
152 else: |
|
153 # 'regular' relation (eg not 'is') |
85 if not isinstance(predicate, tuple): |
154 if not isinstance(predicate, tuple): |
86 raise UnsupportedQuery() |
155 raise UnsupportedQuery() |
87 releq = xy.yeq(':'.join(predicate)) |
156 # list of 3-uple |
88 svptypes = set(s for s, r, o in releq) |
157 # (yams etype (subject), yams rtype, yams etype (object)) |
89 if not '*' in svptypes: |
158 # where subject / object entity type may '*' if not specified |
90 qi.set_possible_types(subjvar, svptypes) |
159 yams_predicates = xy.yeq(':'.join(predicate)) |
91 if subjvar in qi.possible_types: |
160 qi.infer_types_info.append((yams_predicates, subjvar, obj)) |
92 releq = [(s, r, o) for s, r, o in releq |
|
93 if s == '*' or s in qi.possible_types[subjvar]] |
|
94 if isinstance(obj, ast.SparqlVar): |
161 if isinstance(obj, ast.SparqlVar): |
|
162 # make a valid rql var name |
95 objvar = obj.name.upper() |
163 objvar = obj.name.upper() |
96 ovptypes = set(o for s, r, o in releq) |
|
97 if not '*' in ovptypes: |
|
98 qi.set_possible_types(objvar, ovptypes) |
|
99 if objvar in qi.possible_types: |
|
100 releq = [(s, r, o) for s, r, o in releq |
|
101 if o == '*' or o in qi.possible_types[objvar]] |
|
102 else: |
164 else: |
103 raise UnsupportedQuery() |
165 raise UnsupportedQuery() |
104 rel = releq[0] |
166 qi.infer_types() |
105 for s, r, o in releq[1:]: |
|
106 if r != rel[1]: |
|
107 qi.union_params.append((releq, subjvar, objvar)) |
|
108 break |
|
109 else: |
|
110 qi.restrictions.append('%s %s %s' % (subj.name.upper(), |
|
111 rel[1], |
|
112 obj.name.upper())) |
|
113 return qi |
167 return qi |