server/querier.py
changeset 9508 1263f1258796
parent 9469 032825bbacab
child 9770 112c884b2d8d
equal deleted inserted replaced
9507:540cb068a7f9 9508:1263f1258796
    62     for solution in rqlst.solutions:
    62     for solution in rqlst.solutions:
    63         for var, etype in solution.iteritems():
    63         for var, etype in solution.iteritems():
    64             if etype == 'Password':
    64             if etype == 'Password':
    65                 raise Unauthorized('Password selection is not allowed (%s)' % var)
    65                 raise Unauthorized('Password selection is not allowed (%s)' % var)
    66 
    66 
    67 def term_etype(session, term, solution, args):
    67 def term_etype(cnx, term, solution, args):
    68     """return the entity type for the given term (a VariableRef or a Constant
    68     """return the entity type for the given term (a VariableRef or a Constant
    69     node)
    69     node)
    70     """
    70     """
    71     try:
    71     try:
    72         return solution[term.name]
    72         return solution[term.name]
    73     except AttributeError:
    73     except AttributeError:
    74         return session.entity_metas(term.eval(args))['type']
    74         return cnx.entity_metas(term.eval(args))['type']
    75 
    75 
    76 def check_read_access(session, rqlst, solution, args):
    76 def check_read_access(cnx, rqlst, solution, args):
    77     """Check that the given user has credentials to access data read by the
    77     """Check that the given user has credentials to access data read by the
    78     query and return a dict defining necessary "local checks" (i.e. rql
    78     query and return a dict defining necessary "local checks" (i.e. rql
    79     expression in read permission defined in the schema) where no group grants
    79     expression in read permission defined in the schema) where no group grants
    80     him the permission.
    80     him the permission.
    81 
    81 
    84     """
    84     """
    85     # use `term_etype` since we've to deal with rewritten constants here,
    85     # use `term_etype` since we've to deal with rewritten constants here,
    86     # when used as an external source by another repository.
    86     # when used as an external source by another repository.
    87     # XXX what about local read security w/ those rewritten constants...
    87     # XXX what about local read security w/ those rewritten constants...
    88     DBG = (server.DEBUG & server.DBG_SEC) and 'read' in server._SECURITY_CAPS
    88     DBG = (server.DEBUG & server.DBG_SEC) and 'read' in server._SECURITY_CAPS
    89     schema = session.repo.schema
    89     schema = cnx.repo.schema
    90     if rqlst.where is not None:
    90     if rqlst.where is not None:
    91         for rel in rqlst.where.iget_nodes(Relation):
    91         for rel in rqlst.where.iget_nodes(Relation):
    92             # XXX has_text may have specific perm ?
    92             # XXX has_text may have specific perm ?
    93             if rel.r_type in READ_ONLY_RTYPES:
    93             if rel.r_type in READ_ONLY_RTYPES:
    94                 continue
    94                 continue
    95             rschema = schema.rschema(rel.r_type)
    95             rschema = schema.rschema(rel.r_type)
    96             if rschema.final:
    96             if rschema.final:
    97                 eschema = schema.eschema(term_etype(session, rel.children[0],
    97                 eschema = schema.eschema(term_etype(cnx, rel.children[0],
    98                                                     solution, args))
    98                                                     solution, args))
    99                 rdef = eschema.rdef(rschema)
    99                 rdef = eschema.rdef(rschema)
   100             else:
   100             else:
   101                 rdef = rschema.rdef(term_etype(session, rel.children[0],
   101                 rdef = rschema.rdef(term_etype(cnx, rel.children[0],
   102                                                solution, args),
   102                                                solution, args),
   103                                     term_etype(session, rel.children[1].children[0],
   103                                     term_etype(cnx, rel.children[1].children[0],
   104                                                solution, args))
   104                                                solution, args))
   105             if not session.user.matching_groups(rdef.get_groups('read')):
   105             if not cnx.user.matching_groups(rdef.get_groups('read')):
   106                 if DBG:
   106                 if DBG:
   107                     print ('check_read_access: %s %s does not match %s' %
   107                     print ('check_read_access: %s %s does not match %s' %
   108                            (rdef, session.user.groups, rdef.get_groups('read')))
   108                            (rdef, cnx.user.groups, rdef.get_groups('read')))
   109                 # XXX rqlexpr not allowed
   109                 # XXX rqlexpr not allowed
   110                 raise Unauthorized('read', rel.r_type)
   110                 raise Unauthorized('read', rel.r_type)
   111             if DBG:
   111             if DBG:
   112                 print ('check_read_access: %s %s matches %s' %
   112                 print ('check_read_access: %s %s matches %s' %
   113                        (rdef, session.user.groups, rdef.get_groups('read')))
   113                        (rdef, cnx.user.groups, rdef.get_groups('read')))
   114     localchecks = {}
   114     localchecks = {}
   115     # iterate on defined_vars and not on solutions to ignore column aliases
   115     # iterate on defined_vars and not on solutions to ignore column aliases
   116     for varname in rqlst.defined_vars:
   116     for varname in rqlst.defined_vars:
   117         eschema = schema.eschema(solution[varname])
   117         eschema = schema.eschema(solution[varname])
   118         if eschema.final:
   118         if eschema.final:
   119             continue
   119             continue
   120         if not session.user.matching_groups(eschema.get_groups('read')):
   120         if not cnx.user.matching_groups(eschema.get_groups('read')):
   121             erqlexprs = eschema.get_rqlexprs('read')
   121             erqlexprs = eschema.get_rqlexprs('read')
   122             if not erqlexprs:
   122             if not erqlexprs:
   123                 ex = Unauthorized('read', solution[varname])
   123                 ex = Unauthorized('read', solution[varname])
   124                 ex.var = varname
   124                 ex.var = varname
   125                 if DBG:
   125                 if DBG:
   126                     print ('check_read_access: %s %s %s %s' %
   126                     print ('check_read_access: %s %s %s %s' %
   127                            (varname, eschema, session.user.groups, eschema.get_groups('read')))
   127                            (varname, eschema, cnx.user.groups, eschema.get_groups('read')))
   128                 raise ex
   128                 raise ex
   129             # don't insert security on variable only referenced by 'NOT X relation Y' or
   129             # don't insert security on variable only referenced by 'NOT X relation Y' or
   130             # 'NOT EXISTS(X relation Y)'
   130             # 'NOT EXISTS(X relation Y)'
   131             varinfo = rqlst.defined_vars[varname].stinfo
   131             varinfo = rqlst.defined_vars[varname].stinfo
   132             if varinfo['selected'] or (
   132             if varinfo['selected'] or (
   142 # Plans #######################################################################
   142 # Plans #######################################################################
   143 
   143 
   144 class ExecutionPlan(object):
   144 class ExecutionPlan(object):
   145     """the execution model of a rql query, composed of querier steps"""
   145     """the execution model of a rql query, composed of querier steps"""
   146 
   146 
   147     def __init__(self, querier, rqlst, args, session):
   147     def __init__(self, querier, rqlst, args, cnx):
   148         # original rql syntax tree
   148         # original rql syntax tree
   149         self.rqlst = rqlst
   149         self.rqlst = rqlst
   150         self.args = args or {}
   150         self.args = args or {}
   151         # session executing the query
   151         # cnx executing the query
   152         self.session = session
   152         self.cnx = cnx
   153         # quick reference to the system source
   153         # quick reference to the system source
   154         self.syssource = session.repo.system_source
   154         self.syssource = cnx.repo.system_source
   155         # execution steps
   155         # execution steps
   156         self.steps = []
   156         self.steps = []
   157         # various resource accesors
   157         # various resource accesors
   158         self.querier = querier
   158         self.querier = querier
   159         self.schema = querier.schema
   159         self.schema = querier.schema
   160         self.sqlannotate = querier.sqlgen_annotate
   160         self.sqlannotate = querier.sqlgen_annotate
   161         self.rqlhelper = session.vreg.rqlhelper
   161         self.rqlhelper = cnx.vreg.rqlhelper
   162 
   162 
   163     def annotate_rqlst(self):
   163     def annotate_rqlst(self):
   164         if not self.rqlst.annotated:
   164         if not self.rqlst.annotated:
   165             self.rqlhelper.annotate(self.rqlst)
   165             self.rqlhelper.annotate(self.rqlst)
   166 
   166 
   167     def add_step(self, step):
   167     def add_step(self, step):
   168         """add a step to the plan"""
   168         """add a step to the plan"""
   169         self.steps.append(step)
   169         self.steps.append(step)
   170 
   170 
   171     def sqlexec(self, sql, args=None):
   171     def sqlexec(self, sql, args=None):
   172         return self.syssource.sqlexec(self.session, sql, args)
   172         return self.syssource.sqlexec(self.cnx, sql, args)
   173 
   173 
   174     def execute(self):
   174     def execute(self):
   175         """execute a plan and return resulting rows"""
   175         """execute a plan and return resulting rows"""
   176         for step in self.steps:
   176         for step in self.steps:
   177             result = step.execute()
   177             result = step.execute()
   182         """insert security when necessary then annotate rql st for sql generation
   182         """insert security when necessary then annotate rql st for sql generation
   183 
   183 
   184         return rqlst to actually execute
   184         return rqlst to actually execute
   185         """
   185         """
   186         cached = None
   186         cached = None
   187         if security and self.session.read_security:
   187         if security and self.cnx.read_security:
   188             # ensure security is turned of when security is inserted,
   188             # ensure security is turned of when security is inserted,
   189             # else we may loop for ever...
   189             # else we may loop for ever...
   190             if self.session.transaction_data.get('security-rqlst-cache'):
   190             if self.cnx.transaction_data.get('security-rqlst-cache'):
   191                 key = self.cache_key
   191                 key = self.cache_key
   192             else:
   192             else:
   193                 key = None
   193                 key = None
   194             if key is not None and key in self.session.transaction_data:
   194             if key is not None and key in self.cnx.transaction_data:
   195                 cachedunion, args = self.session.transaction_data[key]
   195                 cachedunion, args = self.cnx.transaction_data[key]
   196                 union.children[:] = []
   196                 union.children[:] = []
   197                 for select in cachedunion.children:
   197                 for select in cachedunion.children:
   198                     union.append(select)
   198                     union.append(select)
   199                 union.has_text_query = cachedunion.has_text_query
   199                 union.has_text_query = cachedunion.has_text_query
   200                 args.update(self.args)
   200                 args.update(self.args)
   201                 self.args = args
   201                 self.args = args
   202                 cached = True
   202                 cached = True
   203             else:
   203             else:
   204                 with self.session.security_enabled(read=False):
   204                 with self.cnx.security_enabled(read=False):
   205                     noinvariant = self._insert_security(union)
   205                     noinvariant = self._insert_security(union)
   206                 if key is not None:
   206                 if key is not None:
   207                     self.session.transaction_data[key] = (union, self.args)
   207                     self.cnx.transaction_data[key] = (union, self.args)
   208         else:
   208         else:
   209             noinvariant = ()
   209             noinvariant = ()
   210         if cached is None:
   210         if cached is None:
   211             self.rqlhelper.simplify(union)
   211             self.rqlhelper.simplify(union)
   212             self.sqlannotate(union)
   212             self.sqlannotate(union)
   219         for select in union.children[:]:
   219         for select in union.children[:]:
   220             for subquery in select.with_:
   220             for subquery in select.with_:
   221                 self._insert_security(subquery.query)
   221                 self._insert_security(subquery.query)
   222             localchecks, restricted = self._check_permissions(select)
   222             localchecks, restricted = self._check_permissions(select)
   223             if any(localchecks):
   223             if any(localchecks):
   224                 self.session.rql_rewriter.insert_local_checks(
   224                 self.cnx.rql_rewriter.insert_local_checks(
   225                     select, self.args, localchecks, restricted, noinvariant)
   225                     select, self.args, localchecks, restricted, noinvariant)
   226         return noinvariant
   226         return noinvariant
   227 
   227 
   228     def _check_permissions(self, rqlst):
   228     def _check_permissions(self, rqlst):
   229         """Return a dict defining "local checks", i.e. RQLExpression defined in
   229         """Return a dict defining "local checks", i.e. RQLExpression defined in
   241         variable which has to be checked. Solutions which don't require local
   241         variable which has to be checked. Solutions which don't require local
   242         checks will be associated to the empty tuple key.
   242         checks will be associated to the empty tuple key.
   243 
   243 
   244         Note rqlst should not have been simplified at this point.
   244         Note rqlst should not have been simplified at this point.
   245         """
   245         """
   246         session = self.session
   246         cnx = self.cnx
   247         msgs = []
   247         msgs = []
   248         # dict(varname: eid), allowing to check rql expression for variables
   248         # dict(varname: eid), allowing to check rql expression for variables
   249         # which have a known eid
   249         # which have a known eid
   250         varkwargs = {}
   250         varkwargs = {}
   251         if not session.transaction_data.get('security-rqlst-cache'):
   251         if not cnx.transaction_data.get('security-rqlst-cache'):
   252             for var in rqlst.defined_vars.itervalues():
   252             for var in rqlst.defined_vars.itervalues():
   253                 if var.stinfo['constnode'] is not None:
   253                 if var.stinfo['constnode'] is not None:
   254                     eid = var.stinfo['constnode'].eval(self.args)
   254                     eid = var.stinfo['constnode'].eval(self.args)
   255                     varkwargs[var.name] = int(eid)
   255                     varkwargs[var.name] = int(eid)
   256         # dictionary of variables restricted for security reason
   256         # dictionary of variables restricted for security reason
   257         localchecks = {}
   257         localchecks = {}
   258         restricted_vars = set()
   258         restricted_vars = set()
   259         newsolutions = []
   259         newsolutions = []
   260         for solution in rqlst.solutions:
   260         for solution in rqlst.solutions:
   261             try:
   261             try:
   262                 localcheck = check_read_access(session, rqlst, solution, self.args)
   262                 localcheck = check_read_access(cnx, rqlst, solution, self.args)
   263             except Unauthorized as ex:
   263             except Unauthorized as ex:
   264                 msg = 'remove %s from solutions since %s has no %s access to %s'
   264                 msg = 'remove %s from solutions since %s has no %s access to %s'
   265                 msg %= (solution, session.user.login, ex.args[0], ex.args[1])
   265                 msg %= (solution, cnx.user.login, ex.args[0], ex.args[1])
   266                 msgs.append(msg)
   266                 msgs.append(msg)
   267                 LOGGER.info(msg)
   267                 LOGGER.info(msg)
   268             else:
   268             else:
   269                 newsolutions.append(solution)
   269                 newsolutions.append(solution)
   270                 # try to benefit of rqlexpr.check cache for entities which
   270                 # try to benefit of rqlexpr.check cache for entities which
   275                     except KeyError:
   275                     except KeyError:
   276                         continue
   276                         continue
   277                     # if entity has been added in the current transaction, the
   277                     # if entity has been added in the current transaction, the
   278                     # user can read it whatever rql expressions are associated
   278                     # user can read it whatever rql expressions are associated
   279                     # to its type
   279                     # to its type
   280                     if session.added_in_transaction(eid):
   280                     if cnx.added_in_transaction(eid):
   281                         continue
   281                         continue
   282                     for rqlexpr in rqlexprs:
   282                     for rqlexpr in rqlexprs:
   283                         if rqlexpr.check(session, eid):
   283                         if rqlexpr.check(cnx, eid):
   284                             break
   284                             break
   285                     else:
   285                     else:
   286                         raise Unauthorized('No read acces on %r with eid %i.' % (var, eid))
   286                         raise Unauthorized('No read acces on %r with eid %i.' % (var, eid))
   287                 # mark variables protected by an rql expression
   287                 # mark variables protected by an rql expression
   288                 restricted_vars.update(localcheck)
   288                 restricted_vars.update(localcheck)
   314 
   314 
   315 class InsertPlan(ExecutionPlan):
   315 class InsertPlan(ExecutionPlan):
   316     """an execution model specific to the INSERT rql query
   316     """an execution model specific to the INSERT rql query
   317     """
   317     """
   318 
   318 
   319     def __init__(self, querier, rqlst, args, session):
   319     def __init__(self, querier, rqlst, args, cnx):
   320         ExecutionPlan.__init__(self, querier, rqlst, args, session)
   320         ExecutionPlan.__init__(self, querier, rqlst, args, cnx)
   321         # save originaly selected variable, we may modify this
   321         # save originaly selected variable, we may modify this
   322         # dictionary for substitution (query parameters)
   322         # dictionary for substitution (query parameters)
   323         self.selected = rqlst.selection
   323         self.selected = rqlst.selection
   324         # list of rows of entities definition (ssplanner.EditedEntity)
   324         # list of rows of entities definition (ssplanner.EditedEntity)
   325         self.e_defs = [[]]
   325         self.e_defs = [[]]
   413           WHERE U login 'admin', U login N
   413           WHERE U login 'admin', U login N
   414 
   414 
   415         if there is two entities matching U, the result set will look like
   415         if there is two entities matching U, the result set will look like
   416         [(eidX1, eidY1), (eidX2, eidY2)]
   416         [(eidX1, eidY1), (eidX2, eidY2)]
   417         """
   417         """
   418         session = self.session
   418         cnx = self.cnx
   419         repo = session.repo
   419         repo = cnx.repo
   420         results = []
   420         results = []
   421         for row in self.e_defs:
   421         for row in self.e_defs:
   422             results.append([repo.glob_add_entity(session, edef)
   422             results.append([repo.glob_add_entity(cnx, edef)
   423                             for edef in row])
   423                             for edef in row])
   424         return results
   424         return results
   425 
   425 
   426     def insert_relation_defs(self):
   426     def insert_relation_defs(self):
   427         session = self.session
   427         cnx = self.cnx
   428         repo = session.repo
   428         repo = cnx.repo
   429         edited_entities = {}
   429         edited_entities = {}
   430         relations = {}
   430         relations = {}
   431         for subj, rtype, obj in self.relation_defs():
   431         for subj, rtype, obj in self.relation_defs():
   432             # if a string is given into args instead of an int, we get it here
   432             # if a string is given into args instead of an int, we get it here
   433             if isinstance(subj, basestring):
   433             if isinstance(subj, basestring):
   438                 obj = int(obj)
   438                 obj = int(obj)
   439             elif not isinstance(obj, (int, long)):
   439             elif not isinstance(obj, (int, long)):
   440                 obj = obj.entity.eid
   440                 obj = obj.entity.eid
   441             if repo.schema.rschema(rtype).inlined:
   441             if repo.schema.rschema(rtype).inlined:
   442                 if subj not in edited_entities:
   442                 if subj not in edited_entities:
   443                     entity = session.entity_from_eid(subj)
   443                     entity = cnx.entity_from_eid(subj)
   444                     edited = EditedEntity(entity)
   444                     edited = EditedEntity(entity)
   445                     edited_entities[subj] = edited
   445                     edited_entities[subj] = edited
   446                 else:
   446                 else:
   447                     edited = edited_entities[subj]
   447                     edited = edited_entities[subj]
   448                 edited.edited_attribute(rtype, obj)
   448                 edited.edited_attribute(rtype, obj)
   449             else:
   449             else:
   450                 if rtype in relations:
   450                 if rtype in relations:
   451                     relations[rtype].append((subj, obj))
   451                     relations[rtype].append((subj, obj))
   452                 else:
   452                 else:
   453                     relations[rtype] = [(subj, obj)]
   453                     relations[rtype] = [(subj, obj)]
   454         repo.glob_add_relations(session, relations)
   454         repo.glob_add_relations(cnx, relations)
   455         for edited in edited_entities.itervalues():
   455         for edited in edited_entities.itervalues():
   456             repo.glob_update_entity(session, edited)
   456             repo.glob_update_entity(cnx, edited)
   457 
   457 
   458 
   458 
   459 class QuerierHelper(object):
   459 class QuerierHelper(object):
   460     """helper class to execute rql queries, putting all things together"""
   460     """helper class to execute rql queries, putting all things together"""
   461 
   461 
   493         try:
   493         try:
   494             return self._parse(unicode(rql), annotate=annotate)
   494             return self._parse(unicode(rql), annotate=annotate)
   495         except UnicodeError:
   495         except UnicodeError:
   496             raise RQLSyntaxError(rql)
   496             raise RQLSyntaxError(rql)
   497 
   497 
   498     def plan_factory(self, rqlst, args, session):
   498     def plan_factory(self, rqlst, args, cnx):
   499         """create an execution plan for an INSERT RQL query"""
   499         """create an execution plan for an INSERT RQL query"""
   500         if rqlst.TYPE == 'insert':
   500         if rqlst.TYPE == 'insert':
   501             return InsertPlan(self, rqlst, args, session)
   501             return InsertPlan(self, rqlst, args, cnx)
   502         return ExecutionPlan(self, rqlst, args, session)
   502         return ExecutionPlan(self, rqlst, args, cnx)
   503 
   503 
   504     def execute(self, session, rql, args=None, build_descr=True):
   504     def execute(self, cnx, rql, args=None, build_descr=True):
   505         """execute a rql query, return resulting rows and their description in
   505         """execute a rql query, return resulting rows and their description in
   506         a `ResultSet` object
   506         a `ResultSet` object
   507 
   507 
   508         * `rql` should be an Unicode string or a plain ASCII string
   508         * `rql` should be an Unicode string or a plain ASCII string
   509         * `args` the optional parameters dictionary associated to the query
   509         * `args` the optional parameters dictionary associated to the query
   533                 eidkeys = self._rql_ck_cache[rql]
   533                 eidkeys = self._rql_ck_cache[rql]
   534                 if eidkeys:
   534                 if eidkeys:
   535                     # if there are some, we need a better cache key, eg (rql +
   535                     # if there are some, we need a better cache key, eg (rql +
   536                     # entity type of each eid)
   536                     # entity type of each eid)
   537                     try:
   537                     try:
   538                         cachekey = self._repo.querier_cache_key(session, rql,
   538                         cachekey = self._repo.querier_cache_key(cnx, rql,
   539                                                                 args, eidkeys)
   539                                                                 args, eidkeys)
   540                     except UnknownEid:
   540                     except UnknownEid:
   541                         # we want queries such as "Any X WHERE X eid 9999"
   541                         # we want queries such as "Any X WHERE X eid 9999"
   542                         # return an empty result instead of raising UnknownEid
   542                         # return an empty result instead of raising UnknownEid
   543                         return empty_rset(rql, args)
   543                         return empty_rset(rql, args)
   549             try:
   549             try:
   550                 # compute solutions for rqlst and return named args in query
   550                 # compute solutions for rqlst and return named args in query
   551                 # which are eids. Notice that if you may not need `eidkeys`, we
   551                 # which are eids. Notice that if you may not need `eidkeys`, we
   552                 # have to compute solutions anyway (kept as annotation on the
   552                 # have to compute solutions anyway (kept as annotation on the
   553                 # tree)
   553                 # tree)
   554                 eidkeys = self.solutions(session, rqlst, args)
   554                 eidkeys = self.solutions(cnx, rqlst, args)
   555             except UnknownEid:
   555             except UnknownEid:
   556                 # we want queries such as "Any X WHERE X eid 9999" return an
   556                 # we want queries such as "Any X WHERE X eid 9999" return an
   557                 # empty result instead of raising UnknownEid
   557                 # empty result instead of raising UnknownEid
   558                 return empty_rset(rql, args, rqlst)
   558                 return empty_rset(rql, args, rqlst)
   559             if args and rql not in self._rql_ck_cache:
   559             if args and rql not in self._rql_ck_cache:
   560                 self._rql_ck_cache[rql] = eidkeys
   560                 self._rql_ck_cache[rql] = eidkeys
   561                 if eidkeys:
   561                 if eidkeys:
   562                     cachekey = self._repo.querier_cache_key(session, rql, args,
   562                     cachekey = self._repo.querier_cache_key(cnx, rql, args,
   563                                                             eidkeys)
   563                                                             eidkeys)
   564             self._rql_cache[cachekey] = rqlst
   564             self._rql_cache[cachekey] = rqlst
   565         orig_rqlst = rqlst
   565         orig_rqlst = rqlst
   566         if rqlst.TYPE != 'select':
   566         if rqlst.TYPE != 'select':
   567             if session.read_security:
   567             if cnx.read_security:
   568                 check_no_password_selected(rqlst)
   568                 check_no_password_selected(rqlst)
   569             # write query, ensure session's mode is 'write' so connections won't
   569             # write query, ensure connection's mode is 'write' so connections
   570             # be released until commit/rollback
   570             # won't be released until commit/rollback
   571             session.mode = 'write'
   571             cnx.mode = 'write'
   572             cachekey = None
   572             cachekey = None
   573         else:
   573         else:
   574             if session.read_security:
   574             if cnx.read_security:
   575                 for select in rqlst.children:
   575                 for select in rqlst.children:
   576                     check_no_password_selected(select)
   576                     check_no_password_selected(select)
   577             # on select query, always copy the cached rqlst so we don't have to
   577             # on select query, always copy the cached rqlst so we don't have to
   578             # bother modifying it. This is not necessary on write queries since
   578             # bother modifying it. This is not necessary on write queries since
   579             # a new syntax tree is built from them.
   579             # a new syntax tree is built from them.
   583                 # different SQL generated when some argument is None or not (IS
   583                 # different SQL generated when some argument is None or not (IS
   584                 # NULL). This should be considered when computing sql cache key
   584                 # NULL). This should be considered when computing sql cache key
   585                 cachekey += tuple(sorted([k for k, v in args.iteritems()
   585                 cachekey += tuple(sorted([k for k, v in args.iteritems()
   586                                           if v is None]))
   586                                           if v is None]))
   587         # make an execution plan
   587         # make an execution plan
   588         plan = self.plan_factory(rqlst, args, session)
   588         plan = self.plan_factory(rqlst, args, cnx)
   589         plan.cache_key = cachekey
   589         plan.cache_key = cachekey
   590         self._planner.build_plan(plan)
   590         self._planner.build_plan(plan)
   591         # execute the plan
   591         # execute the plan
   592         try:
   592         try:
   593             results = plan.execute()
   593             results = plan.execute()
   595             # getting an Unauthorized/ValidationError exception means the
   595             # getting an Unauthorized/ValidationError exception means the
   596             # transaction must be rolled back
   596             # transaction must be rolled back
   597             #
   597             #
   598             # notes:
   598             # notes:
   599             # * we should not reset the connections set here, since we don't want the
   599             # * we should not reset the connections set here, since we don't want the
   600             #   session to loose it during processing
   600             #   connection to loose it during processing
   601             # * don't rollback if we're in the commit process, will be handled
   601             # * don't rollback if we're in the commit process, will be handled
   602             #   by the session
   602             #   by the connection
   603             if session.commit_state is None:
   603             if cnx.commit_state is None:
   604                 session.commit_state = 'uncommitable'
   604                 cnx.commit_state = 'uncommitable'
   605             raise
   605             raise
   606         # build a description for the results if necessary
   606         # build a description for the results if necessary
   607         descr = ()
   607         descr = ()
   608         if build_descr:
   608         if build_descr:
   609             if rqlst.TYPE == 'select':
   609             if rqlst.TYPE == 'select':
   614                     solution = rqlst.children[0].solutions[0]
   614                     solution = rqlst.children[0].solutions[0]
   615                     description = _make_description(selected, args, solution)
   615                     description = _make_description(selected, args, solution)
   616                     descr = RepeatList(len(results), tuple(description))
   616                     descr = RepeatList(len(results), tuple(description))
   617                 else:
   617                 else:
   618                     # hard, delegate the work :o)
   618                     # hard, delegate the work :o)
   619                     descr = manual_build_descr(session, rqlst, args, results)
   619                     descr = manual_build_descr(cnx, rqlst, args, results)
   620             elif rqlst.TYPE == 'insert':
   620             elif rqlst.TYPE == 'insert':
   621                 # on insert plan, some entities may have been auto-casted,
   621                 # on insert plan, some entities may have been auto-casted,
   622                 # so compute description manually even if there is only
   622                 # so compute description manually even if there is only
   623                 # one solution
   623                 # one solution
   624                 basedescr = [None] * len(plan.selected)
   624                 basedescr = [None] * len(plan.selected)
   625                 todetermine = zip(xrange(len(plan.selected)), repeat(False))
   625                 todetermine = zip(xrange(len(plan.selected)), repeat(False))
   626                 descr = _build_descr(session, results, basedescr, todetermine)
   626                 descr = _build_descr(cnx, results, basedescr, todetermine)
   627             # FIXME: get number of affected entities / relations on non
   627             # FIXME: get number of affected entities / relations on non
   628             # selection queries ?
   628             # selection queries ?
   629         # return a result set object
   629         # return a result set object
   630         return ResultSet(results, rql, args, descr, orig_rqlst)
   630         return ResultSet(results, rql, args, descr, orig_rqlst)
   631 
   631 
   637 from cubicweb import set_log_methods
   637 from cubicweb import set_log_methods
   638 LOGGER = getLogger('cubicweb.querier')
   638 LOGGER = getLogger('cubicweb.querier')
   639 set_log_methods(QuerierHelper, LOGGER)
   639 set_log_methods(QuerierHelper, LOGGER)
   640 
   640 
   641 
   641 
   642 def manual_build_descr(tx, rqlst, args, result):
   642 def manual_build_descr(cnx, rqlst, args, result):
   643     """build a description for a given result by analysing each row
   643     """build a description for a given result by analysing each row
   644 
   644 
   645     XXX could probably be done more efficiently during execution of query
   645     XXX could probably be done more efficiently during execution of query
   646     """
   646     """
   647     # not so easy, looks for variable which changes from one solution
   647     # not so easy, looks for variable which changes from one solution
   661             todetermine.append( (i, isfinal) )
   661             todetermine.append( (i, isfinal) )
   662         else:
   662         else:
   663             basedescr.append(ttype)
   663             basedescr.append(ttype)
   664     if not todetermine:
   664     if not todetermine:
   665         return RepeatList(len(result), tuple(basedescr))
   665         return RepeatList(len(result), tuple(basedescr))
   666     return _build_descr(tx, result, basedescr, todetermine)
   666     return _build_descr(cnx, result, basedescr, todetermine)
   667 
   667 
   668 def _build_descr(tx, result, basedescription, todetermine):
   668 def _build_descr(cnx, result, basedescription, todetermine):
   669     description = []
   669     description = []
   670     entity_metas = tx.entity_metas
   670     entity_metas = cnx.entity_metas
   671     todel = []
   671     todel = []
   672     for i, row in enumerate(result):
   672     for i, row in enumerate(result):
   673         row_descr = basedescription[:]
   673         row_descr = basedescription[:]
   674         for index, isfinal in todetermine:
   674         for index, isfinal in todetermine:
   675             value = row[index]
   675             value = row[index]
   681                 row_descr[index] = etype_from_pyobj(value)
   681                 row_descr[index] = etype_from_pyobj(value)
   682             else:
   682             else:
   683                 try:
   683                 try:
   684                     row_descr[index] = entity_metas(value)['type']
   684                     row_descr[index] = entity_metas(value)['type']
   685                 except UnknownEid:
   685                 except UnknownEid:
   686                     tx.error('wrong eid %s in repository, you should '
   686                     cnx.error('wrong eid %s in repository, you should '
   687                              'db-check the database' % value)
   687                              'db-check the database' % value)
   688                     todel.append(i)
   688                     todel.append(i)
   689                     break
   689                     break
   690         else:
   690         else:
   691             description.append(tuple(row_descr))
   691             description.append(tuple(row_descr))