cubicweb/server/ssplanner.py
changeset 12227 dc333e9104c9
parent 11767 432f87a63057
child 12242 68ca7fe0ca29
equal deleted inserted replaced
12226:2e2425d2d54d 12227:dc333e9104c9
     1 # copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     1 # copyright 2003 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     3 #
     3 #
     4 # This file is part of CubicWeb.
     4 # This file is part of CubicWeb.
     5 #
     5 #
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
    29 
    29 
    30 READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity'))
    30 READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity'))
    31 
    31 
    32 _CONSTANT = object()
    32 _CONSTANT = object()
    33 _FROM_SUBSTEP = object()
    33 _FROM_SUBSTEP = object()
       
    34 
    34 
    35 
    35 def _extract_const_attributes(plan, rqlst, to_build):
    36 def _extract_const_attributes(plan, rqlst, to_build):
    36     """add constant values to entity def, mark variables to be selected
    37     """add constant values to entity def, mark variables to be selected
    37     """
    38     """
    38     to_select = {}
    39     to_select = {}
    59                 edef.edited_attribute(rtype, value)
    60                 edef.edited_attribute(rtype, value)
    60             elif str(rhs) in to_build:
    61             elif str(rhs) in to_build:
    61                 # create a relation between two newly created variables
    62                 # create a relation between two newly created variables
    62                 plan.add_relation_def((edef, rtype, to_build[rhs.name]))
    63                 plan.add_relation_def((edef, rtype, to_build[rhs.name]))
    63             else:
    64             else:
    64                 to_select.setdefault(edef, []).append( (rtype, rhs, 0) )
    65                 to_select.setdefault(edef, []).append((rtype, rhs, 0))
    65     return to_select
    66     return to_select
       
    67 
    66 
    68 
    67 def _extract_eid_consts(plan, rqlst):
    69 def _extract_eid_consts(plan, rqlst):
    68     """return a dict mapping rqlst variable object to their eid if specified in
    70     """return a dict mapping rqlst variable object to their eid if specified in
    69     the syntax tree
    71     the syntax tree
    70     """
    72     """
    76     checkread = cnx.read_security
    78     checkread = cnx.read_security
    77     eschema = cnx.vreg.schema.eschema
    79     eschema = cnx.vreg.schema.eschema
    78     for rel in rqlst.where.get_nodes(Relation):
    80     for rel in rqlst.where.get_nodes(Relation):
    79         # only care for 'eid' relations ...
    81         # only care for 'eid' relations ...
    80         if (rel.r_type == 'eid'
    82         if (rel.r_type == 'eid'
    81             # ... that are not part of a NOT clause ...
    83                 # ... that are not part of a NOT clause ...
    82             and not rel.neged(strict=True)
    84                 and not rel.neged(strict=True)
    83             # ... and where eid is specified by '=' operator.
    85                 # ... and where eid is specified by '=' operator.
    84             and rel.children[1].operator == '='):
    86                 and rel.children[1].operator == '='):
    85             lhs, rhs = rel.get_variable_parts()
    87             lhs, rhs = rel.get_variable_parts()
    86             if isinstance(rhs, Constant):
    88             if isinstance(rhs, Constant):
    87                 eid = int(rhs.eval(plan.args))
    89                 eid = int(rhs.eval(plan.args))
    88                 # check read permission here since it may not be done by
    90                 # check read permission here since it may not be done by
    89                 # the generated select substep if not emited (eg nothing
    91                 # the generated select substep if not emited (eg nothing
    92                     with cnx.security_enabled(read=False):
    94                     with cnx.security_enabled(read=False):
    93                         eschema(cnx.entity_type(eid)).check_perm(
    95                         eschema(cnx.entity_type(eid)).check_perm(
    94                             cnx, 'read', eid=eid)
    96                             cnx, 'read', eid=eid)
    95                 eidconsts[lhs.variable] = eid
    97                 eidconsts[lhs.variable] = eid
    96     return eidconsts
    98     return eidconsts
       
    99 
    97 
   100 
    98 def _build_substep_query(select, origrqlst):
   101 def _build_substep_query(select, origrqlst):
    99     """Finalize substep select query that should be executed to get proper
   102     """Finalize substep select query that should be executed to get proper
   100     selection of stuff to insert/update.
   103     selection of stuff to insert/update.
   101 
   104 
   117         if getattr(origrqlst, 'having', None):
   120         if getattr(origrqlst, 'having', None):
   118             select.set_having([sq.copy(select) for sq in origrqlst.having])
   121             select.set_having([sq.copy(select) for sq in origrqlst.having])
   119         return select
   122         return select
   120     return None
   123     return None
   121 
   124 
       
   125 
   122 class SSPlanner(object):
   126 class SSPlanner(object):
   123     """SingleSourcePlanner: build execution plan for rql queries
   127     """SingleSourcePlanner: build execution plan for rql queries
   124 
   128 
   125     optimized for single source repositories
   129     optimized for single source repositories
   126     """
   130     """
   158             to_build[var.name] = EditedEntity(etype_class(etype)(cnx))
   162             to_build[var.name] = EditedEntity(etype_class(etype)(cnx))
   159             plan.add_entity_def(to_build[var.name])
   163             plan.add_entity_def(to_build[var.name])
   160         # add constant values to entity def, mark variables to be selected
   164         # add constant values to entity def, mark variables to be selected
   161         to_select = _extract_const_attributes(plan, rqlst, to_build)
   165         to_select = _extract_const_attributes(plan, rqlst, to_build)
   162         # add necessary steps to add relations and update attributes
   166         # add necessary steps to add relations and update attributes
   163         step = InsertStep(plan) # insert each entity and its relations
   167         step = InsertStep(plan)  # insert each entity and its relations
   164         step.children += self._compute_relation_steps(plan, rqlst, to_select)
   168         step.children += self._compute_relation_steps(plan, rqlst, to_select)
   165         return (step,)
   169         return (step,)
   166 
   170 
   167     def _compute_relation_steps(self, plan, rqlst, to_select):
   171     def _compute_relation_steps(self, plan, rqlst, to_select):
   168         """handle the selection of relations for an insert query"""
   172         """handle the selection of relations for an insert query"""
   235     def build_set_plan(self, plan, rqlst):
   239     def build_set_plan(self, plan, rqlst):
   236         """get an execution plan from an SET RQL query"""
   240         """get an execution plan from an SET RQL query"""
   237         getrschema = self.schema.rschema
   241         getrschema = self.schema.rschema
   238         select = Select()   # potential substep query
   242         select = Select()   # potential substep query
   239         selectedidx = {}    # local state
   243         selectedidx = {}    # local state
   240         attributes = set()  # edited attributes
       
   241         updatedefs = []     # definition of update attributes/relations
   244         updatedefs = []     # definition of update attributes/relations
   242         selidx = residx = 0 # substep selection / resulting rset indexes
   245         selidx = residx = 0  # substep selection / resulting rset indexes
   243         # search for eid const in the WHERE clause
   246         # search for eid const in the WHERE clause
   244         eidconsts = _extract_eid_consts(plan, rqlst)
   247         eidconsts = _extract_eid_consts(plan, rqlst)
   245         # build `updatedefs` describing things to update and add necessary
   248         # build `updatedefs` describing things to update and add necessary
   246         # variables to the substep selection
   249         # variables to the substep selection
   247         for i, relation in enumerate(rqlst.main_relations):
   250         for i, relation in enumerate(rqlst.main_relations):
   248             if relation.r_type in VIRTUAL_RTYPES:
   251             if relation.r_type in VIRTUAL_RTYPES:
   249                 raise QueryError('can not assign to %r relation'
   252                 raise QueryError('can not assign to %r relation'
   250                                  % relation.r_type)
   253                                  % relation.r_type)
   251             lhs, rhs = relation.get_variable_parts()
   254             lhs, rhs = relation.get_variable_parts()
   252             lhskey = lhs.as_string()
   255             lhskey = lhs.as_string()
   253             if not lhskey in selectedidx:
   256             if lhskey not in selectedidx:
   254                 if lhs.variable in eidconsts:
   257                 if lhs.variable in eidconsts:
   255                     eid = eidconsts[lhs.variable]
   258                     eid = eidconsts[lhs.variable]
   256                     lhsinfo = (_CONSTANT, eid, residx)
   259                     lhsinfo = (_CONSTANT, eid, residx)
   257                 else:
   260                 else:
   258                     select.append_selected(lhs.copy(select))
   261                     select.append_selected(lhs.copy(select))
   261                 residx += 1
   264                 residx += 1
   262                 selectedidx[lhskey] = lhsinfo
   265                 selectedidx[lhskey] = lhsinfo
   263             else:
   266             else:
   264                 lhsinfo = selectedidx[lhskey][:-1] + (None,)
   267                 lhsinfo = selectedidx[lhskey][:-1] + (None,)
   265             rhskey = rhs.as_string()
   268             rhskey = rhs.as_string()
   266             if not rhskey in selectedidx:
   269             if rhskey not in selectedidx:
   267                 if isinstance(rhs, Constant):
   270                 if isinstance(rhs, Constant):
   268                     rhsinfo = (_CONSTANT, rhs.eval(plan.args), residx)
   271                     rhsinfo = (_CONSTANT, rhs.eval(plan.args), residx)
   269                 elif getattr(rhs, 'variable', None) in eidconsts:
   272                 elif getattr(rhs, 'variable', None) in eidconsts:
   270                     eid = eidconsts[rhs.variable]
   273                     eid = eidconsts[rhs.variable]
   271                     rhsinfo = (_CONSTANT, eid, residx)
   274                     rhsinfo = (_CONSTANT, eid, residx)
   276                 residx += 1
   279                 residx += 1
   277                 selectedidx[rhskey] = rhsinfo
   280                 selectedidx[rhskey] = rhsinfo
   278             else:
   281             else:
   279                 rhsinfo = selectedidx[rhskey][:-1] + (None,)
   282                 rhsinfo = selectedidx[rhskey][:-1] + (None,)
   280             rschema = getrschema(relation.r_type)
   283             rschema = getrschema(relation.r_type)
   281             updatedefs.append( (lhsinfo, rhsinfo, rschema) )
   284             updatedefs.append((lhsinfo, rhsinfo, rschema))
   282         # the update step
   285         # the update step
   283         step = UpdateStep(plan, updatedefs)
   286         step = UpdateStep(plan, updatedefs)
   284         # when necessary add substep to fetch yet unknown values
   287         # when necessary add substep to fetch yet unknown values
   285         select = _build_substep_query(select, rqlst)
   288         select = _build_substep_query(select, rqlst)
   286         if select is not None:
   289         if select is not None:
   357         else:
   360         else:
   358             cachekey = union.as_string()
   361             cachekey = union.as_string()
   359         # get results for query
   362         # get results for query
   360         source = cnx.repo.system_source
   363         source = cnx.repo.system_source
   361         result = source.syntax_tree_search(cnx, union, args, cachekey)
   364         result = source.syntax_tree_search(cnx, union, args, cachekey)
   362         #print 'ONEFETCH RESULT %s' % (result)
       
   363         return result
   365         return result
   364 
   366 
   365     def mytest_repr(self):
   367     def mytest_repr(self):
   366         """return a representation of this step suitable for test"""
   368         """return a representation of this step suitable for test"""
   367         return (self.__class__.__name__,
   369         return (self.__class__.__name__,
   411                     value = row[index]
   413                     value = row[index]
   412                     index += 1
   414                     index += 1
   413                 if rorder == InsertRelationsStep.FINAL:
   415                 if rorder == InsertRelationsStep.FINAL:
   414                     edef.edited_attribute(rtype, value)
   416                     edef.edited_attribute(rtype, value)
   415                 elif rorder == InsertRelationsStep.RELATION:
   417                 elif rorder == InsertRelationsStep.RELATION:
   416                     self.plan.add_relation_def( (edef, rtype, value) )
   418                     self.plan.add_relation_def((edef, rtype, value))
   417                     edef.querier_pending_relations[(rtype, 'subject')] = value
   419                     edef.querier_pending_relations[(rtype, 'subject')] = value
   418                 else:
   420                 else:
   419                     self.plan.add_relation_def( (value, rtype, edef) )
   421                     self.plan.add_relation_def((value, rtype, edef))
   420                     edef.querier_pending_relations[(rtype, 'object')] = value
   422                     edef.querier_pending_relations[(rtype, 'object')] = value
   421             edefs.append(edef)
   423             edefs.append(edef)
   422         self.plan.substitute_entity_def(base_edef, edefs)
   424         self.plan.substitute_entity_def(base_edef, edefs)
   423         return result
   425         return result
   424 
   426 
   449         if results:
   451         if results:
   450             todelete = frozenset(int(eid) for eid, in results)
   452             todelete = frozenset(int(eid) for eid, in results)
   451             cnx = self.plan.cnx
   453             cnx = self.plan.cnx
   452             cnx.repo.glob_delete_entities(cnx, todelete)
   454             cnx.repo.glob_delete_entities(cnx, todelete)
   453         return results
   455         return results
       
   456 
   454 
   457 
   455 class DeleteRelationsStep(Step):
   458 class DeleteRelationsStep(Step):
   456     """step consisting in deleting relations"""
   459     """step consisting in deleting relations"""
   457 
   460 
   458     def __init__(self, plan, rtype):
   461     def __init__(self, plan, rtype):
   511         repo.glob_add_relations(cnx, relations)
   514         repo.glob_add_relations(cnx, relations)
   512         for eid, edited in edefs.items():
   515         for eid, edited in edefs.items():
   513             repo.glob_update_entity(cnx, edited)
   516             repo.glob_update_entity(cnx, edited)
   514         return result
   517         return result
   515 
   518 
       
   519 
   516 def _handle_relterm(info, row, newrow):
   520 def _handle_relterm(info, row, newrow):
   517     if info[0] is _CONSTANT:
   521     if info[0] is _CONSTANT:
   518         val = info[1]
   522         val = info[1]
   519     else: # _FROM_SUBSTEP
   523     else:  # _FROM_SUBSTEP
   520         val = row[info[1]]
   524         val = row[info[1]]
   521     if info[-1] is not None:
   525     if info[-1] is not None:
   522         newrow.append(val)
   526         newrow.append(val)
   523     return val
   527     return val