Commit 5ee1a5b8 authored by Sylvain Thénault's avatar Sylvain Thénault
Browse files

[session / querier] reorganize code to building result set descriptions

parent 5b6bc27ece6e
......@@ -26,22 +26,28 @@ __docformat__ = "restructuredtext en"
from itertools import repeat
from logilab.common.compat import any
from rql import RQLSyntaxError
from rql import RQLSyntaxError, CoercionError
from rql.stmts import Union, Select
from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj
from rql.nodes import (Relation, VariableRef, Constant, SubQuery, Function,
Exists, Not)
from yams import BASE_TYPES
from cubicweb import ValidationError, Unauthorized, QueryError, UnknownEid
from cubicweb import server, typed_eid
from cubicweb import Binary, server, typed_eid
from cubicweb.rset import ResultSet
from cubicweb.utils import QueryCache
from cubicweb.utils import QueryCache, RepeatList
from cubicweb.server.utils import cleanup_solutions
from cubicweb.server.rqlannotation import SQLGenAnnotator, set_qdata
from cubicweb.server.ssplanner import READ_ONLY_RTYPES, add_types_restriction
from cubicweb.server.edition import EditedEntity
from cubicweb.server.session import security_enabled
ETYPE_PYOBJ_MAP[Binary] = 'Bytes'
def empty_rset(rql, args, rqlst=None):
"""build an empty result set object"""
return ResultSet([], rql, args, rqlst=rqlst)
......@@ -751,14 +757,22 @@ class QuerierHelper(object):
if build_descr:
if rqlst.TYPE == 'select':
# sample selection
descr = session.build_description(orig_rqlst, args, results)
if len(rqlst.children) == 1 and len(rqlst.children[0].solutions) == 1:
# easy, all lines are identical
selected = rqlst.children[0].selection
solution = rqlst.children[0].solutions[0]
description = _make_description(selected, args, solution)
descr = RepeatList(len(results), tuple(description))
else:
# hard, delegate the work :o)
descr = manual_build_descr(session, rqlst, args, results)
elif rqlst.TYPE == 'insert':
# on insert plan, some entities may have been auto-casted,
# so compute description manually even if there is only
# one solution
basedescr = [None] * len(plan.selected)
todetermine = zip(xrange(len(plan.selected)), repeat(False))
descr = session._build_descr(results, basedescr, todetermine)
descr = _build_descr(session, results, basedescr, todetermine)
# FIXME: get number of affected entities / relations on non
# selection queries ?
# return a result set object
......@@ -772,3 +786,77 @@ from logging import getLogger
from cubicweb import set_log_methods
LOGGER = getLogger('cubicweb.querier')
set_log_methods(QuerierHelper, LOGGER)
def manual_build_descr(tx, rqlst, args, result):
"""build a description for a given result by analysing each row
XXX could probably be done more efficiently during execution of query
"""
# not so easy, looks for variable which changes from one solution
# to another
unstables = rqlst.get_variable_indices()
basedescr = []
todetermine = []
for i in xrange(len(rqlst.children[0].selection)):
ttype = _selection_idx_type(i, rqlst, args)
if ttype is None or ttype == 'Any':
ttype = None
isfinal = True
else:
isfinal = ttype in BASE_TYPES
if ttype is None or i in unstables:
basedescr.append(None)
todetermine.append( (i, isfinal) )
else:
basedescr.append(ttype)
if not todetermine:
return RepeatList(len(result), tuple(basedescr))
return _build_descr(tx, result, basedescr, todetermine)
def _build_descr(tx, result, basedescription, todetermine):
description = []
etype_from_eid = tx.describe
todel = []
for i, row in enumerate(result):
row_descr = basedescription[:]
for index, isfinal in todetermine:
value = row[index]
if value is None:
# None value inserted by an outer join, no type
row_descr[index] = None
continue
if isfinal:
row_descr[index] = etype_from_pyobj(value)
else:
try:
row_descr[index] = etype_from_eid(value)[0]
except UnknownEid:
tx.error('wrong eid %s in repository, you should '
'db-check the database' % value)
todel.append(i)
break
else:
description.append(tuple(row_descr))
for i in reversed(todel):
del result[i]
return description
def _make_description(selected, args, solution):
"""return a description for a result set"""
description = []
for term in selected:
description.append(term.get_type(solution, args))
return description
def _selection_idx_type(i, rqlst, args):
"""try to return type of term at index `i` of the rqlst's selection"""
for select in rqlst.children:
term = select.selection[i]
for solution in select.solutions:
try:
ttype = term.get_type(solution, args)
if ttype is not None:
return ttype
except CoercionError:
return None
......@@ -30,21 +30,16 @@ from warnings import warn
from logilab.common.deprecation import deprecated
from logilab.common.textutils import unormalize
from logilab.common.registry import objectify_predicate
from rql import CoercionError
from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj
from yams import BASE_TYPES
from cubicweb import Binary, UnknownEid, QueryError, schema
from cubicweb import UnknownEid, QueryError, schema
from cubicweb.req import RequestSessionBase
from cubicweb.dbapi import ConnectionProperties
from cubicweb.utils import make_uid, RepeatList
from cubicweb.utils import make_uid
from cubicweb.rqlrewrite import RQLRewriter
from cubicweb.server import ShuttingDown
from cubicweb.server.edition import EditedEntity
ETYPE_PYOBJ_MAP[Binary] = 'Bytes'
NO_UNDO_TYPES = schema.SCHEMA_TYPES.copy()
NO_UNDO_TYPES.add('CWCache')
# is / is_instance_of are usually added by sql hooks except when using
......@@ -55,25 +50,6 @@ NO_UNDO_TYPES.add('is_instance_of')
NO_UNDO_TYPES.add('cw_source')
# XXX rememberme,forgotpwd,apycot,vcsfile
def _make_description(selected, args, solution):
"""return a description for a result set"""
description = []
for term in selected:
description.append(term.get_type(solution, args))
return description
def selection_idx_type(i, rqlst, args):
"""try to return type of term at index `i` of the rqlst's selection"""
for select in rqlst.children:
term = select.selection[i]
for solution in select.solutions:
try:
ttype = term.get_type(solution, args)
if ttype is not None:
return ttype
except CoercionError:
return None
@objectify_predicate
def is_user_session(cls, req, **kwargs):
"""repository side only predicate returning 1 if the session is a regular
......
......@@ -29,10 +29,10 @@ from cubicweb import QueryError, Unauthorized, Binary
from cubicweb.server.sqlutils import SQL_PREFIX
from cubicweb.server.utils import crypt_password
from cubicweb.server.sources.native import make_schema
from cubicweb.server.querier import manual_build_descr, _make_description
from cubicweb.devtools import get_test_db_handler, TestServerConfiguration
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.devtools.repotest import tuplify, BaseQuerierTC
from unittest_session import Variable
class FixedOffset(tzinfo):
def __init__(self, hours=0):
......@@ -87,6 +87,30 @@ def tearDownClass(cls, *args):
del repo, cnx
class Variable:
def __init__(self, name):
self.name = name
self.children = []
def get_type(self, solution, args=None):
return solution[self.name]
def as_string(self):
return self.name
class Function:
def __init__(self, name, varname):
self.name = name
self.children = [Variable(varname)]
def get_type(self, solution, args=None):
return 'Int'
class MakeDescriptionTC(TestCase):
def test_known_values(self):
solution = {'A': 'Int', 'B': 'CWUser'}
self.assertEqual(_make_description((Function('max', 'A'), Variable('B')), {}, solution),
['Int','CWUser'])
class UtilsTC(BaseQuerierTC):
setUpClass = classmethod(setUpClass)
tearDownClass = classmethod(tearDownClass)
......@@ -242,6 +266,28 @@ class UtilsTC(BaseQuerierTC):
rset = self.execute('Any %(x)s', {'x': u'str'})
self.assertEqual(rset.description[0][0], 'String')
def test_build_descr1(self):
rset = self.execute('(Any U,L WHERE U login L) UNION (Any G,N WHERE G name N, G is CWGroup)')
rset.req = self.transaction
orig_length = len(rset)
rset.rows[0][0] = 9999999
description = manual_build_descr(rset.req, rset.syntax_tree(), None, rset.rows)
self.assertEqual(len(description), orig_length - 1)
self.assertEqual(len(rset.rows), orig_length - 1)
self.assertNotEqual(rset.rows[0][0], 9999999)
def test_build_descr2(self):
rset = self.execute('Any X,Y WITH X,Y BEING ((Any G,NULL WHERE G is CWGroup) UNION (Any U,G WHERE U in_group G))')
for x, y in rset.description:
if y is not None:
self.assertEqual(y, 'CWGroup')
def test_build_descr3(self):
rset = self.execute('(Any G,NULL WHERE G is CWGroup) UNION (Any U,G WHERE U in_group G)')
for x, y in rset.description:
if y is not None:
self.assertEqual(y, 'CWGroup')
class QuerierTC(BaseQuerierTC):
setUpClass = classmethod(setUpClass)
......
......@@ -17,33 +17,8 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
from __future__ import with_statement
from logilab.common.testlib import TestCase, unittest_main, mock_object
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.server.session import _make_description, hooks_control
class Variable:
def __init__(self, name):
self.name = name
self.children = []
def get_type(self, solution, args=None):
return solution[self.name]
def as_string(self):
return self.name
class Function:
def __init__(self, name, varname):
self.name = name
self.children = [Variable(varname)]
def get_type(self, solution, args=None):
return 'Int'
class MakeDescriptionTC(TestCase):
def test_known_values(self):
solution = {'A': 'Int', 'B': 'CWUser'}
self.assertEqual(_make_description((Function('max', 'A'), Variable('B')), {}, solution),
['Int','CWUser'])
from cubicweb.server.session import hooks_control
class InternalSessionTC(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