spa2rql.py
changeset 2430 7d9ed6c740ec
parent 2427 3e0ef847a546
child 2431 93c061eac647
equal deleted inserted replaced
2429:d3f4bffb57d0 2430:7d9ed6c740ec
    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