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 |