# HG changeset patch # User Sylvain Thénault # Date 1248280508 -7200 # Node ID 96da7dc42eb599420cb06c05a0a67194311a346e # Parent 8f06e4f02733f797815cf89d40b6c0fea65952cd quick and dirty support from simple sparql queries + base ui diff -r 8f06e4f02733 -r 96da7dc42eb5 spa2rql.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/spa2rql.py Wed Jul 22 18:35:08 2009 +0200 @@ -0,0 +1,111 @@ +"""SPARQL -> RQL translator + +:organization: Logilab +:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +""" +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 + +class QueryInfo(object): + 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.union_params = [] + self.restrictions = [] + + def finalize(self): + for varname, ptypes in self.possible_types.iteritems(): + if len(ptypes) == 1: + self.restrictions.append('%s is %s' % (varname, iter(ptypes).next())) + unions = [] + for releq, subjvar, objvar in self.union_params: + thisunions = [] + for st, rt, ot in releq: + thisunions.append(['%s %s %s' % (subjvar, rt, objvar)]) + if st != '*': + thisunions[-1].append('%s is %s' % (subjvar, st)) + if ot != '*': + thisunions[-1].append('%s is %s' % (objvar, ot)) + if not unions: + unions = thisunions + else: + unions = zip(*make_domains([unions, thisunions])) + baserql = 'Any %s WHERE %s' % (', '.join(self.selection), + ', '.join(self.restrictions)) + if not unions: + return baserql + rqls = ['(%s, %s)' % (baserql, ', '.join(unionrestrs)) + for unionrestrs in unions] + return ' UNION '.join(rqls) + + def set_possible_types(self, var, varpossibletypes): + varpossibletypes = set(varpossibletypes) + try: + self.possible_types[var] &= varpossibletypes + if not self.possible_types[var]: + raise TypeResolverException() + except KeyError: + self.possible_types[var] = varpossibletypes + + +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() + subjvar = subj.name.upper() + if predicate == ('', 'a'): # special 'is' relation + if not isinstance(obj, tuple): + raise UnsupportedQuery() + qi.set_possible_types( + subjvar, xy.yeq(':'.join(obj), isentity=True)) + else: + if not isinstance(predicate, tuple): + raise UnsupportedQuery() + releq = xy.yeq(':'.join(predicate)) + svptypes = set(s for s, r, o in releq) + if not '*' in svptypes: + qi.set_possible_types(subjvar, svptypes) + if subjvar in qi.possible_types: + releq = [(s, r, o) for s, r, o in releq + if s == '*' or s in qi.possible_types[subjvar]] + if isinstance(obj, ast.SparqlVar): + objvar = obj.name.upper() + ovptypes = set(o for s, r, o in releq) + if not '*' in ovptypes: + qi.set_possible_types(objvar, ovptypes) + if objvar in qi.possible_types: + releq = [(s, r, o) for s, r, o in releq + if o == '*' or o in qi.possible_types[objvar]] + else: + raise UnsupportedQuery() + rel = releq[0] + for s, r, o in releq[1:]: + if r != rel[1]: + qi.union_params.append((releq, subjvar, objvar)) + break + else: + qi.restrictions.append('%s %s %s' % (subj.name.upper(), + rel[1], + obj.name.upper())) + return qi diff -r 8f06e4f02733 -r 96da7dc42eb5 test/unittest_spa2rql.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/unittest_spa2rql.py Wed Jul 22 18:35:08 2009 +0200 @@ -0,0 +1,88 @@ +from logilab.common.testlib import TestCase, unittest_main +from cubicweb.devtools import TestServerConfiguration +from cubicweb.xy import xy +from cubicweb.spa2rql import Sparql2rqlTranslator + +xy.add_equivalence('Project', 'doap:Project') +xy.add_equivalence('Project.creation_date', 'doap:Project.doap:created') + + +config = TestServerConfiguration('data') +config.bootstrap_cubes() +schema = config.load_schema() + + +class XYTC(TestCase): + def setUp(self): + self.tr = Sparql2rqlTranslator(schema) + + def _test(self, sparql, rql): + qi = self.tr.translate(sparql) + self.assertEquals(qi.finalize(), rql) + + def XXX_test_base_01(self): + self._test('SELECT * WHERE { }', 'Any X') + + def test_base_is(self): + self._test(''' + PREFIX doap: + SELECT ?project + WHERE { + ?project a doap:Project; + }''', 'Any PROJECT WHERE PROJECT is Project') + + + def test_base_attr_sel(self): + self._test(''' + PREFIX doap: + SELECT ?created + WHERE { + ?project a doap:Project; + doap:created ?created. + }''', 'Any CREATED WHERE PROJECT creation_date CREATED, PROJECT is Project') + + + def test_base_any_attr_sel(self): + self._test(''' + PREFIX dc: + SELECT ?x ?cd + WHERE { + ?x dc:date ?cd; + }''', 'Any X, CD WHERE X creation_date CD') + + def test_base_any_attr_sel_amb(self): + xy.add_equivalence('Version.publication_date', 'doap:Version.dc:date') + try: + self._test(''' + PREFIX dc: + SELECT ?x ?cd + WHERE { + ?x dc:date ?cd; + }''', '(Any X, CD WHERE , X creation_date CD) UNION (Any X, CD WHERE , X publication_date CD, X is Version)') + finally: + xy.remove_equivalence('Version.publication_date', 'doap:Version.dc:date') + +# # Two elements in the group +# PREFIX : +# SELECT * +# { :p :q :r OPTIONAL { :a :b :c } +# :p :q :r OPTIONAL { :a :b :c } +# } + +# PREFIX : +# SELECT * +# { +# { ?s ?p ?o } UNION { ?a ?b ?c } +# } + +# PREFIX dob: +# PREFIX time: +# PREFIX dc: +# SELECT ?desc +# WHERE { +# dob:1D a time:ProperInterval; +# dc:description ?desc. +# } + +if __name__ == '__main__': + unittest_main() diff -r 8f06e4f02733 -r 96da7dc42eb5 web/views/sparql.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/views/sparql.py Wed Jul 22 18:35:08 2009 +0200 @@ -0,0 +1,43 @@ +"""SPARQL integration + +:organization: Logilab +:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +""" +import rql +from yams import xy + +from cubicweb.view import StartupView +from cubicweb.web import form, formfields, formwidgets as fwdgs +from cubicweb.web.views import forms, urlrewrite +from cubicweb.spa2rql import Sparql2rqlTranslator + + +class SparqlForm(forms.FieldsForm): + id = 'sparql' + sparql = formfields.StringField(help=_('type here a sparql qyery')) + vid = formfields.StringField(initial='sparql', widget=fwdgs.HiddenInput) + form_buttons = [fwdgs.SubmitButton()] + @property + def action(self): + return self.req.url() + + +class SparqlFormView(form.FormViewMixIn, StartupView): + id = 'sparql' + def call(self): + form = self.vreg.select('forms', 'sparql', self.req) + self.w(form.form_render()) + sparql = self.req.form.get('sparql') + if sparql: + try: + qi = Sparql2rqlTranslator(self.schema).translate(sparql) + rset = self.req.execute(qi.finalize()) + except rql.TypeResolverException, ex: + self.w(self.req._('can not resolve entity types:') + u' ' + unicode('ex')) + except UnsupportedQuery: + self.w(self.req._('we are not yet ready to handle this query')) + except xy.UnsupportedVocabulary, ex: + self.w(self.req._('unknown vocabulary:') + u' ' + unicode('ex')) + self.wview('table', rset, 'null') diff -r 8f06e4f02733 -r 96da7dc42eb5 web/views/urlrewrite.py --- a/web/views/urlrewrite.py Wed Jul 22 18:34:00 2009 +0200 +++ b/web/views/urlrewrite.py Wed Jul 22 18:35:08 2009 +0200 @@ -79,6 +79,7 @@ ('/manage', dict(vid='manage')), ('/notfound', dict(vid='404')), ('/error', dict(vid='error')), + ('/sparql', dict(vid='sparql')), (rgx('/schema/([^/]+?)/?'), dict(vid='eschema', rql=r'Any X WHERE X is CWEType, X name "\1"')), (rgx('/add/([^/]+?)/?'), dict(vid='creation', etype=r'\1')), (rgx('/doc/images/(.+?)/?'), dict(vid='wdocimages', fid=r'\1')), diff -r 8f06e4f02733 -r 96da7dc42eb5 xy.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/xy.py Wed Jul 22 18:35:08 2009 +0200 @@ -0,0 +1,20 @@ +"""map standard cubicweb schema to xml vocabularies + +:organization: Logilab +:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +""" + +from yams import xy + +xy.register_prefix('http://purl.org/dc/elements/1.1/', 'dc') +xy.register_prefix('http://xmlns.com/foaf/0.1/', 'foaf') +xy.register_prefix('http://usefulinc.com/ns/doap#', 'doap') + +xy.add_equivalence('creation_date', 'dc:date') +xy.add_equivalence('created_by', 'dc:creator') +xy.add_equivalence('description', 'dc:description') +xy.add_equivalence('CWUser', 'foaf:Person') +xy.add_equivalence('CWUser.login', 'dc:title') +xy.add_equivalence('CWUser.surname', 'foaf:Person.foaf:name')