# HG changeset patch # User Laurent Wouters # Date 1584711247 -3600 # Node ID 6eae252361e50b9f9b969bb62a6c4975286d47ca # Parent 665f66e57fc6f61b2177eb9b68dc72a139bffe4d [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. diff -r 665f66e57fc6 -r 6eae252361e5 cubicweb/rset.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 diff -r 665f66e57fc6 -r 6eae252361e5 cubicweb/server/querier.py --- 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): diff -r 665f66e57fc6 -r 6eae252361e5 cubicweb/server/test/unittest_querier.py --- 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):