quick and dirty support from simple sparql queries + base ui
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 22 Jul 2009 18:35:08 +0200
changeset 2422 96da7dc42eb5
parent 2418 8f06e4f02733
child 2423 f6757021018f
quick and dirty support from simple sparql queries + base ui
spa2rql.py
test/unittest_spa2rql.py
web/views/sparql.py
web/views/urlrewrite.py
xy.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
--- /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: <http://usefulinc.com/ns/doap#>
+    SELECT ?project
+    WHERE  {
+      ?project a doap:Project;
+    }''', 'Any PROJECT WHERE PROJECT is Project')
+
+
+    def test_base_attr_sel(self):
+        self._test('''
+    PREFIX doap: <http://usefulinc.com/ns/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: <http://purl.org/dc/elements/1.1/>
+    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: <http://purl.org/dc/elements/1.1/>
+    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 :  <http://example.org/ns#>
+# SELECT *
+# { :p :q :r  OPTIONAL { :a :b :c }
+#   :p :q :r  OPTIONAL { :a :b :c }
+# }
+
+# PREFIX : <http://example.org/ns#>
+# SELECT *
+# {
+#   { ?s ?p ?o } UNION { ?a ?b ?c }
+# }
+
+# PREFIX dob: <http://placetime.com/interval/gregorian/1977-01-18T04:00:00Z/P>
+# PREFIX time: <http://www.ai.sri.com/daml/ontologies/time/Time.daml#>
+# PREFIX dc: <http://purl.org/dc/elements/1.1/>
+# SELECT ?desc
+# WHERE  {
+#   dob:1D a time:ProperInterval;
+#          dc:description ?desc.
+# }
+
+if __name__ == '__main__':
+    unittest_main()
--- /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')
--- 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')),
--- /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')