server/querier.py
changeset 8542 7e264ce34cd4
parent 8342 7a5271182ef0
child 8562 0d2fb4604265
equal deleted inserted replaced
8541:5b6bc27ece6e 8542:7e264ce34cd4
    24 __docformat__ = "restructuredtext en"
    24 __docformat__ = "restructuredtext en"
    25 
    25 
    26 from itertools import repeat
    26 from itertools import repeat
    27 
    27 
    28 from logilab.common.compat import any
    28 from logilab.common.compat import any
    29 from rql import RQLSyntaxError
    29 from rql import RQLSyntaxError, CoercionError
    30 from rql.stmts import Union, Select
    30 from rql.stmts import Union, Select
       
    31 from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj
    31 from rql.nodes import (Relation, VariableRef, Constant, SubQuery, Function,
    32 from rql.nodes import (Relation, VariableRef, Constant, SubQuery, Function,
    32                        Exists, Not)
    33                        Exists, Not)
       
    34 from yams import BASE_TYPES
    33 
    35 
    34 from cubicweb import ValidationError, Unauthorized, QueryError, UnknownEid
    36 from cubicweb import ValidationError, Unauthorized, QueryError, UnknownEid
    35 from cubicweb import server, typed_eid
    37 from cubicweb import Binary, server, typed_eid
    36 from cubicweb.rset import ResultSet
    38 from cubicweb.rset import ResultSet
    37 
    39 
    38 from cubicweb.utils import QueryCache
    40 from cubicweb.utils import QueryCache, RepeatList
    39 from cubicweb.server.utils import cleanup_solutions
    41 from cubicweb.server.utils import cleanup_solutions
    40 from cubicweb.server.rqlannotation import SQLGenAnnotator, set_qdata
    42 from cubicweb.server.rqlannotation import SQLGenAnnotator, set_qdata
    41 from cubicweb.server.ssplanner import READ_ONLY_RTYPES, add_types_restriction
    43 from cubicweb.server.ssplanner import READ_ONLY_RTYPES, add_types_restriction
    42 from cubicweb.server.edition import EditedEntity
    44 from cubicweb.server.edition import EditedEntity
    43 from cubicweb.server.session import security_enabled
    45 from cubicweb.server.session import security_enabled
       
    46 
       
    47 
       
    48 ETYPE_PYOBJ_MAP[Binary] = 'Bytes'
       
    49 
    44 
    50 
    45 def empty_rset(rql, args, rqlst=None):
    51 def empty_rset(rql, args, rqlst=None):
    46     """build an empty result set object"""
    52     """build an empty result set object"""
    47     return ResultSet([], rql, args, rqlst=rqlst)
    53     return ResultSet([], rql, args, rqlst=rqlst)
    48 
    54 
   749         # build a description for the results if necessary
   755         # build a description for the results if necessary
   750         descr = ()
   756         descr = ()
   751         if build_descr:
   757         if build_descr:
   752             if rqlst.TYPE == 'select':
   758             if rqlst.TYPE == 'select':
   753                 # sample selection
   759                 # sample selection
   754                 descr = session.build_description(orig_rqlst, args, results)
   760                 if len(rqlst.children) == 1 and len(rqlst.children[0].solutions) == 1:
       
   761                     # easy, all lines are identical
       
   762                     selected = rqlst.children[0].selection
       
   763                     solution = rqlst.children[0].solutions[0]
       
   764                     description = _make_description(selected, args, solution)
       
   765                     descr = RepeatList(len(results), tuple(description))
       
   766                 else:
       
   767                     # hard, delegate the work :o)
       
   768                     descr = manual_build_descr(session, rqlst, args, results)
   755             elif rqlst.TYPE == 'insert':
   769             elif rqlst.TYPE == 'insert':
   756                 # on insert plan, some entities may have been auto-casted,
   770                 # on insert plan, some entities may have been auto-casted,
   757                 # so compute description manually even if there is only
   771                 # so compute description manually even if there is only
   758                 # one solution
   772                 # one solution
   759                 basedescr = [None] * len(plan.selected)
   773                 basedescr = [None] * len(plan.selected)
   760                 todetermine = zip(xrange(len(plan.selected)), repeat(False))
   774                 todetermine = zip(xrange(len(plan.selected)), repeat(False))
   761                 descr = session._build_descr(results, basedescr, todetermine)
   775                 descr = _build_descr(session, results, basedescr, todetermine)
   762             # FIXME: get number of affected entities / relations on non
   776             # FIXME: get number of affected entities / relations on non
   763             # selection queries ?
   777             # selection queries ?
   764         # return a result set object
   778         # return a result set object
   765         return ResultSet(results, rql, args, descr, orig_rqlst)
   779         return ResultSet(results, rql, args, descr, orig_rqlst)
   766 
   780 
   770 
   784 
   771 from logging import getLogger
   785 from logging import getLogger
   772 from cubicweb import set_log_methods
   786 from cubicweb import set_log_methods
   773 LOGGER = getLogger('cubicweb.querier')
   787 LOGGER = getLogger('cubicweb.querier')
   774 set_log_methods(QuerierHelper, LOGGER)
   788 set_log_methods(QuerierHelper, LOGGER)
       
   789 
       
   790 
       
   791 def manual_build_descr(tx, rqlst, args, result):
       
   792     """build a description for a given result by analysing each row
       
   793 
       
   794     XXX could probably be done more efficiently during execution of query
       
   795     """
       
   796     # not so easy, looks for variable which changes from one solution
       
   797     # to another
       
   798     unstables = rqlst.get_variable_indices()
       
   799     basedescr = []
       
   800     todetermine = []
       
   801     for i in xrange(len(rqlst.children[0].selection)):
       
   802         ttype = _selection_idx_type(i, rqlst, args)
       
   803         if ttype is None or ttype == 'Any':
       
   804             ttype = None
       
   805             isfinal = True
       
   806         else:
       
   807             isfinal = ttype in BASE_TYPES
       
   808         if ttype is None or i in unstables:
       
   809             basedescr.append(None)
       
   810             todetermine.append( (i, isfinal) )
       
   811         else:
       
   812             basedescr.append(ttype)
       
   813     if not todetermine:
       
   814         return RepeatList(len(result), tuple(basedescr))
       
   815     return _build_descr(tx, result, basedescr, todetermine)
       
   816 
       
   817 def _build_descr(tx, result, basedescription, todetermine):
       
   818     description = []
       
   819     etype_from_eid = tx.describe
       
   820     todel = []
       
   821     for i, row in enumerate(result):
       
   822         row_descr = basedescription[:]
       
   823         for index, isfinal in todetermine:
       
   824             value = row[index]
       
   825             if value is None:
       
   826                 # None value inserted by an outer join, no type
       
   827                 row_descr[index] = None
       
   828                 continue
       
   829             if isfinal:
       
   830                 row_descr[index] = etype_from_pyobj(value)
       
   831             else:
       
   832                 try:
       
   833                     row_descr[index] = etype_from_eid(value)[0]
       
   834                 except UnknownEid:
       
   835                     tx.error('wrong eid %s in repository, you should '
       
   836                              'db-check the database' % value)
       
   837                     todel.append(i)
       
   838                     break
       
   839         else:
       
   840             description.append(tuple(row_descr))
       
   841     for i in reversed(todel):
       
   842         del result[i]
       
   843     return description
       
   844 
       
   845 def _make_description(selected, args, solution):
       
   846     """return a description for a result set"""
       
   847     description = []
       
   848     for term in selected:
       
   849         description.append(term.get_type(solution, args))
       
   850     return description
       
   851 
       
   852 def _selection_idx_type(i, rqlst, args):
       
   853     """try to return type of term at index `i` of the rqlst's selection"""
       
   854     for select in rqlst.children:
       
   855         term = select.selection[i]
       
   856         for solution in select.solutions:
       
   857             try:
       
   858                 ttype = term.get_type(solution, args)
       
   859                 if ttype is not None:
       
   860                     return ttype
       
   861             except CoercionError:
       
   862                 return None