[rql] Store selected variables for RQL select queries in ResultSet (#17218476)
authorLaurent Wouters <lwouters@cenotelie.fr>
Fri, 20 Mar 2020 14:34:07 +0100
changeset 12931 6eae252361e5
parent 12930 665f66e57fc6
child 12957 0c973204033a
[rql] Store selected variables for RQL select queries in ResultSet (#17218476) By storing the name of the selected variables for RQL select queries in the ResultSet (within the "variables" attribute), the information can be passed down to specific protocols, e.g. rqlio that may wish to pass is down further to clients. In turn, clients can then choose to present the results of RQL select queries as symbolic bindings using the names used in the query's projection, instead of ordinal arrays.
cubicweb/rset.py
cubicweb/server/querier.py
cubicweb/server/test/unittest_querier.py
--- a/cubicweb/rset.py	Thu Mar 12 15:38:51 2020 +0100
+++ b/cubicweb/rset.py	Fri Mar 20 14:34:07 2020 +0100
@@ -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
--- a/cubicweb/server/querier.py	Thu Mar 12 15:38:51 2020 +0100
+++ b/cubicweb/server/querier.py	Fri Mar 20 14:34:07 2020 +0100
@@ -25,7 +25,8 @@
 
 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
@@ -579,6 +580,7 @@
 
         # build a description for the results if necessary
         descr = ()
+        variables = None
         if build_descr:
             if rqlst.TYPE == 'select':
                 # sample selection
@@ -588,6 +590,8 @@
                     solution = rqlst.children[0].solutions[0]
                     description = _make_description(selected, args, solution)
                     descr = RepeatList(len(results), tuple(description))
+                    variables = [self._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)
@@ -605,12 +609,24 @@
         emit_to_debug_channel("rql", query_debug_informations)
 
         # 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 str(projected)
+            for name, value in stinfo['rewritten'].items():
+                if [projected] == value:
+                    return name
+        return str(projected)
+
 
 class RQLCache(object):
 
--- a/cubicweb/server/test/unittest_querier.py	Thu Mar 12 15:38:51 2020 +0100
+++ b/cubicweb/server/test/unittest_querier.py	Fri Mar 20 14:34:07 2020 +0100
@@ -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,24 @@
         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'])
+
+    def test_no_variables_in_rset(self):
+        rset = self.qexecute('Any COUNT(X) WHERE X is CWUser')
+        self.assertEqual(rset.variables, ['COUNT(X)'])
+
+    def test_mixed_projection_in_rset(self):
+        rset = self.qexecute('Any X,COUNT(X) WHERE X is CWUser')
+        self.assertEqual(rset.variables, ['X', 'COUNT(X)'])
+
 
 class NonRegressionTC(CubicWebTC):