[PATCH cubicweb V3] [rql] Store selected variables for RQL select queries in ResultSet

Laurent Wouters lwouters at cenotelie.fr
Thu Jul 25 14:12:24 CEST 2019


# HG changeset patch
# User Laurent Wouters <lwouters at cenotelie.fr>
# Date 1563955833 -7200
#      Wed Jul 24 10:10:33 2019 +0200
# Node ID 9d1ffce720ea09d0babdff9e474524a25b4eddb0
# Parent  eb83daa6949545bb81bcac6b5fda41d73a37837a
[rql] Store selected variables for RQL select queries in ResultSet

By storing the name of the selected variables for RQL select queries in the
ResultSet, it should be possible to leverage this information to output symbolic
bindings for RQL queries when requested by certain protocol, e.g. rqlio.

For example, the query Any X WHERE X is CWEtype could be answered with
[{'X': 101}, {'X': 102}, {'X': 103}, ...]
instead of
[[101], [102], [103], ...]

This could, in turn, help clarify some client code for complex queries with many
selected variables.

diff -r eb83daa69495 -r 9d1ffce720ea cubicweb/rset.py
--- a/cubicweb/rset.py	Mon Jul 22 11:21:10 2019 +0200
+++ b/cubicweb/rset.py	Wed Jul 24 10:10:33 2019 +0200
@@ -42,7 +42,7 @@
     :param rql: the original RQL query string
     """
 
-    def __init__(self, results, rql, args=None, description=None):
+    def __init__(self, results, rql, args=None, description=None, variables=None):
         self.rows = results
         self.rowcount = results and len(results) or 0
         # original query and arguments
@@ -54,6 +54,7 @@
             self.description = []
         else:
             self.description = description
+        self.variables = variables if variables is not None else []
         # set to (limit, offset) when a result set is limited using the
         # .limit method
         self.limited = None
diff -r eb83daa69495 -r 9d1ffce720ea cubicweb/server/querier.py
--- a/cubicweb/server/querier.py	Mon Jul 22 11:21:10 2019 +0200
+++ b/cubicweb/server/querier.py	Wed Jul 24 10:10:33 2019 +0200
@@ -22,7 +22,7 @@
 
 from rql import RQLSyntaxError, CoercionError
 from rql.stmts import Union
-from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj, Relation, Exists, Not
+from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj, Relation, Exists, Not, VariableRef, Constant
 from yams import BASE_TYPES
 
 from cubicweb import ValidationError, Unauthorized, UnknownEid, QueryError
@@ -569,6 +569,7 @@
             raise
         # build a description for the results if necessary
         descr = ()
+        variables = None
         if build_descr:
             if rqlst.TYPE == 'select':
                 # sample selection
@@ -578,6 +579,8 @@
                     solution = rqlst.children[0].solutions[0]
                     description = _make_description(selected, args, solution)
                     descr = RepeatList(len(results), tuple(description))
+                    variables = [QuerierHelper._get_projected_name(projected, rqlst.children[0].stinfo)
+                                 for projected in selected]
                 else:
                     # hard, delegate the work :o)
                     descr = manual_build_descr(cnx, rqlst, args, results)
@@ -591,12 +594,25 @@
             # FIXME: get number of affected entities / relations on non
             # selection queries ?
         # return a result set object
-        return ResultSet(results, rql, args, descr)
+        return ResultSet(results, rql, args, descr, variables)
 
     # these are overridden by set_log_methods below
     # only defining here to prevent pylint from complaining
     info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
 
+    @staticmethod
+    def _get_projected_name(projected, stinfo):
+        if isinstance(projected, VariableRef):
+            return projected.name
+        elif isinstance(projected, Constant):
+            if stinfo['rewritten'] is None:
+                return ''
+            for name, value in stinfo['rewritten'].items():
+                if [projected] == value:
+                    return name
+            return ''
+        return ''
+
 
 class RQLCache(object):
 
diff -r eb83daa69495 -r 9d1ffce720ea cubicweb/server/test/unittest_querier.py
--- a/cubicweb/server/test/unittest_querier.py	Mon Jul 22 11:21:10 2019 +0200
+++ b/cubicweb/server/test/unittest_querier.py	Wed Jul 24 10:10:33 2019 +0200
@@ -1626,7 +1626,6 @@
                              'X in_state S, S name SN')
         self.assertEqual(rset.rows, [[peid]])
 
-
     def test_nonregr_sql_cache(self):
         # different SQL generated when 'name' is None or not (IS NULL).
         self.assertFalse(self.qexecute('Any X WHERE X is CWEType, X name %(name)s',
@@ -1634,6 +1633,16 @@
         self.assertTrue(self.qexecute('Any X WHERE X is CWEType, X name %(name)s',
                                       {'name': 'CWEType'}))
 
+    def test_variables_in_rset_for_select(self):
+        rset = self.qexecute('Any X WHERE X is CWUser, X eid %(x)s', {'x': self.ueid})
+        self.assertEqual(rset.variables, ['X'])
+
+    def test_only_selected_variables_in_rset(self):
+        rset = self.qexecute('Any X,Y WHERE X is Personne, Y is Personne, '
+                             'X nom XD, Y nom XD, X eid Z, Y eid > Z')
+        # Z is not selected
+        self.assertEqual(rset.variables, ['X', 'Y'])
+
 
 class NonRegressionTC(CubicWebTC):
 



More information about the cubicweb-devel mailing list