--- 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 <http://www.gnu.org/licenses/>.
-"""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