[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.
--- 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):