Commit 13fc8ab6 authored by Laurent Wouters's avatar Laurent Wouters
Browse files

[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.
parent 665f66e57fc6
...@@ -42,7 +42,7 @@ class ResultSet(object): ...@@ -42,7 +42,7 @@ class ResultSet(object):
:param rql: the original RQL query string :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.rows = results
self.rowcount = results and len(results) or 0 self.rowcount = results and len(results) or 0
# original query and arguments # original query and arguments
...@@ -54,6 +54,7 @@ class ResultSet(object): ...@@ -54,6 +54,7 @@ class ResultSet(object):
self.description = [] self.description = []
else: else:
self.description = description self.description = description
self.variables = variables if variables is not None else []
# set to (limit, offset) when a result set is limited using the # set to (limit, offset) when a result set is limited using the
# .limit method # .limit method
self.limited = None self.limited = None
......
...@@ -25,7 +25,8 @@ from itertools import repeat ...@@ -25,7 +25,8 @@ from itertools import repeat
from rql import RQLSyntaxError, CoercionError from rql import RQLSyntaxError, CoercionError
from rql.stmts import Union 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 yams import BASE_TYPES
from cubicweb import ValidationError, Unauthorized, UnknownEid, QueryError from cubicweb import ValidationError, Unauthorized, UnknownEid, QueryError
...@@ -579,6 +580,7 @@ class QuerierHelper(object): ...@@ -579,6 +580,7 @@ class QuerierHelper(object):
# build a description for the results if necessary # build a description for the results if necessary
descr = () descr = ()
variables = None
if build_descr: if build_descr:
if rqlst.TYPE == 'select': if rqlst.TYPE == 'select':
# sample selection # sample selection
...@@ -588,6 +590,8 @@ class QuerierHelper(object): ...@@ -588,6 +590,8 @@ class QuerierHelper(object):
solution = rqlst.children[0].solutions[0] solution = rqlst.children[0].solutions[0]
description = _make_description(selected, args, solution) description = _make_description(selected, args, solution)
descr = RepeatList(len(results), tuple(description)) descr = RepeatList(len(results), tuple(description))
variables = [self._get_projected_name(projected, rqlst.children[0].stinfo)
for projected in selected]
else: else:
# hard, delegate the work :o) # hard, delegate the work :o)
descr = manual_build_descr(cnx, rqlst, args, results) descr = manual_build_descr(cnx, rqlst, args, results)
...@@ -605,12 +609,24 @@ class QuerierHelper(object): ...@@ -605,12 +609,24 @@ class QuerierHelper(object):
emit_to_debug_channel("rql", query_debug_informations) emit_to_debug_channel("rql", query_debug_informations)
# return a result set object # 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 # these are overridden by set_log_methods below
# only defining here to prevent pylint from complaining # only defining here to prevent pylint from complaining
info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None 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): class RQLCache(object):
......
...@@ -1626,7 +1626,6 @@ Any P1,B,E WHERE P1 identity P2 WITH ...@@ -1626,7 +1626,6 @@ Any P1,B,E WHERE P1 identity P2 WITH
'X in_state S, S name SN') 'X in_state S, S name SN')
self.assertEqual(rset.rows, [[peid]]) self.assertEqual(rset.rows, [[peid]])
def test_nonregr_sql_cache(self): def test_nonregr_sql_cache(self):
# different SQL generated when 'name' is None or not (IS NULL). # 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', self.assertFalse(self.qexecute('Any X WHERE X is CWEType, X name %(name)s',
...@@ -1634,6 +1633,24 @@ Any P1,B,E WHERE P1 identity P2 WITH ...@@ -1634,6 +1633,24 @@ Any P1,B,E WHERE P1 identity P2 WITH
self.assertTrue(self.qexecute('Any X WHERE X is CWEType, X name %(name)s', self.assertTrue(self.qexecute('Any X WHERE X is CWEType, X name %(name)s',
{'name': 'CWEType'})) {'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): class NonRegressionTC(CubicWebTC):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment