server/ssplanner.py
changeset 4764 ec9c20c6b9f7
parent 4252 6c4f109c2b03
child 4795 f1c8bc628b45
equal deleted inserted replaced
4763:81b0df087375 4764:ec9c20c6b9f7
     8 __docformat__ = "restructuredtext en"
     8 __docformat__ = "restructuredtext en"
     9 
     9 
    10 from copy import copy
    10 from copy import copy
    11 
    11 
    12 from rql.stmts import Union, Select
    12 from rql.stmts import Union, Select
    13 from rql.nodes import Constant
    13 from rql.nodes import Constant, Relation
    14 
    14 
    15 from cubicweb import QueryError, typed_eid
    15 from cubicweb import QueryError, typed_eid
    16 from cubicweb.schema import VIRTUAL_RTYPES
    16 from cubicweb.schema import VIRTUAL_RTYPES
    17 from cubicweb.rqlrewrite import add_types_restriction
    17 from cubicweb.rqlrewrite import add_types_restriction
       
    18 
       
    19 READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity'))
       
    20 
       
    21 _CONSTANT = object()
       
    22 _FROM_SUBSTEP = object()
       
    23 
       
    24 def _extract_const_attributes(plan, rqlst, to_build):
       
    25     """add constant values to entity def, mark variables to be selected
       
    26     """
       
    27     to_select = {}
       
    28     for relation in rqlst.main_relations:
       
    29         lhs, rhs = relation.get_variable_parts()
       
    30         rtype = relation.r_type
       
    31         if rtype in READ_ONLY_RTYPES:
       
    32             raise QueryError("can't assign to %s" % rtype)
       
    33         try:
       
    34             edef = to_build[str(lhs)]
       
    35         except KeyError:
       
    36             # lhs var is not to build, should be selected and added as an
       
    37             # object relation
       
    38             edef = to_build[str(rhs)]
       
    39             to_select.setdefault(edef, []).append((rtype, lhs, 1))
       
    40         else:
       
    41             if isinstance(rhs, Constant) and not rhs.uid:
       
    42                 # add constant values to entity def
       
    43                 value = rhs.eval(plan.args)
       
    44                 eschema = edef.e_schema
       
    45                 attrtype = eschema.subjrels[rtype].objects(eschema)[0]
       
    46                 if attrtype == 'Password' and isinstance(value, unicode):
       
    47                     value = value.encode('UTF8')
       
    48                 edef[rtype] = value
       
    49             elif to_build.has_key(str(rhs)):
       
    50                 # create a relation between two newly created variables
       
    51                 plan.add_relation_def((edef, rtype, to_build[rhs.name]))
       
    52             else:
       
    53                 to_select.setdefault(edef, []).append( (rtype, rhs, 0) )
       
    54     return to_select
       
    55 
       
    56 def _extract_eid_consts(plan, rqlst):
       
    57     """return a dict mapping rqlst variable object to their eid if specified in
       
    58     the syntax tree
       
    59     """
       
    60     session = plan.session
       
    61     eschema = session.vreg.schema.eschema
       
    62     if rqlst.where is None:
       
    63         return {}
       
    64     eidconsts = {}
       
    65     neweids = session.transaction_data.get('neweids', ())
       
    66     for rel in rqlst.where.get_nodes(Relation):
       
    67         if rel.r_type == 'eid' and not rel.neged(strict=True):
       
    68             lhs, rhs = rel.get_variable_parts()
       
    69             if isinstance(rhs, Constant):
       
    70                 eid = typed_eid(rhs.eval(plan.args))
       
    71                 # check read permission here since it may not be done by
       
    72                 # the generated select substep if not emited (eg nothing
       
    73                 # to be selected)
       
    74                 if eid not in neweids:
       
    75                     eschema(session.describe(eid)[0]).check_perm(session, 'read')
       
    76                 eidconsts[lhs.variable] = eid
       
    77     return eidconsts
    18 
    78 
    19 
    79 
    20 class SSPlanner(object):
    80 class SSPlanner(object):
    21     """SingleSourcePlanner: build execution plan for rql queries
    81     """SingleSourcePlanner: build execution plan for rql queries
    22 
    82 
    54         for etype, var in rqlst.main_variables:
   114         for etype, var in rqlst.main_variables:
    55             # need to do this since entity class is shared w. web client code !
   115             # need to do this since entity class is shared w. web client code !
    56             to_build[var.name] = etype_class(etype)(session)
   116             to_build[var.name] = etype_class(etype)(session)
    57             plan.add_entity_def(to_build[var.name])
   117             plan.add_entity_def(to_build[var.name])
    58         # add constant values to entity def, mark variables to be selected
   118         # add constant values to entity def, mark variables to be selected
    59         to_select = plan.relation_definitions(rqlst, to_build)
   119         to_select = _extract_const_attributes(plan, rqlst, to_build)
    60         # add necessary steps to add relations and update attributes
   120         # add necessary steps to add relations and update attributes
    61         step = InsertStep(plan) # insert each entity and its relations
   121         step = InsertStep(plan) # insert each entity and its relations
    62         step.children += self._compute_relation_steps(plan, rqlst.solutions,
   122         step.children += self._compute_relation_steps(plan, rqlst, to_select)
    63                                                       rqlst.where, to_select)
       
    64         return (step,)
   123         return (step,)
    65 
   124 
    66     def _compute_relation_steps(self, plan, solutions, restriction, to_select):
   125     def _compute_relation_steps(self, plan, rqlst, to_select):
    67         """handle the selection of relations for an insert query"""
   126         """handle the selection of relations for an insert query"""
       
   127         eidconsts = _extract_eid_consts(plan, rqlst)
    68         for edef, rdefs in to_select.items():
   128         for edef, rdefs in to_select.items():
    69             # create a select rql st to fetch needed data
   129             # create a select rql st to fetch needed data
    70             select = Select()
   130             select = Select()
    71             eschema = edef.e_schema
   131             eschema = edef.e_schema
    72             for i in range(len(rdefs)):
   132             for i, (rtype, term, reverse) in enumerate(rdefs):
    73                 rtype, term, reverse = rdefs[i]
   133                 if getattr(term, 'variable', None) in eidconsts:
    74                 select.append_selected(term.copy(select))
   134                     value = eidconsts[term.variable]
       
   135                 else:
       
   136                     select.append_selected(term.copy(select))
       
   137                     value = _FROM_SUBSTEP
    75                 if reverse:
   138                 if reverse:
    76                     rdefs[i] = rtype, RelationsStep.REVERSE_RELATION
   139                     rdefs[i] = (rtype, InsertRelationsStep.REVERSE_RELATION, value)
    77                 else:
   140                 else:
    78                     rschema = eschema.subjrels[rtype]
   141                     rschema = eschema.subjrels[rtype]
    79                     if rschema.final or rschema.inlined:
   142                     if rschema.final or rschema.inlined:
    80                         rdefs[i] = rtype, RelationsStep.FINAL
   143                         rdefs[i] = (rtype, InsertRelationsStep.FINAL, value)
    81                     else:
   144                     else:
    82                         rdefs[i] = rtype, RelationsStep.RELATION
   145                         rdefs[i] = (rtype, InsertRelationsStep.RELATION, value)
    83             if restriction is not None:
   146             step = InsertRelationsStep(plan, edef, rdefs)
    84                 select.set_where(restriction.copy(select))
   147             if select.selection:
    85             step = RelationsStep(plan, edef, rdefs)
   148                 if rqlst.where is not None:
    86             step.children += self._select_plan(plan, select, solutions)
   149                     select.set_where(rqlst.where.copy(select))
       
   150                 step.children += self._select_plan(plan, select, rqlst.solutions)
    87             yield step
   151             yield step
    88 
   152 
    89     def build_delete_plan(self, plan, rqlst):
   153     def build_delete_plan(self, plan, rqlst):
    90         """get an execution plan from a DELETE RQL query"""
   154         """get an execution plan from a DELETE RQL query"""
    91         # build a select query to fetch entities to delete
   155         # build a select query to fetch entities to delete
   125             select.add_restriction(restriction.copy(select))
   189             select.add_restriction(restriction.copy(select))
   126         return self._select_plan(plan, select, solutions)
   190         return self._select_plan(plan, select, solutions)
   127 
   191 
   128     def build_set_plan(self, plan, rqlst):
   192     def build_set_plan(self, plan, rqlst):
   129         """get an execution plan from an SET RQL query"""
   193         """get an execution plan from an SET RQL query"""
   130         select = Select()
       
   131         # extract variables to add to the selection
       
   132         selected_index = {}
       
   133         index = 0
       
   134         relations, attrrelations = [], []
       
   135         getrschema = self.schema.rschema
   194         getrschema = self.schema.rschema
   136         for relation in rqlst.main_relations:
   195         select = Select()   # potential substep query
       
   196         selectedidx = {}    # local state
       
   197         attributes = set()  # edited attributes
       
   198         updatedefs = []     # definition of update attributes/relations
       
   199         selidx = residx = 0 # substep selection / resulting rset indexes
       
   200         # search for eid const in the WHERE clause
       
   201         eidconsts = _extract_eid_consts(plan, rqlst)
       
   202         # build `updatedefs` describing things to update and add necessary
       
   203         # variables to the substep selection
       
   204         for i, relation in enumerate(rqlst.main_relations):
   137             if relation.r_type in VIRTUAL_RTYPES:
   205             if relation.r_type in VIRTUAL_RTYPES:
   138                 raise QueryError('can not assign to %r relation'
   206                 raise QueryError('can not assign to %r relation'
   139                                  % relation.r_type)
   207                                  % relation.r_type)
   140             lhs, rhs = relation.get_variable_parts()
   208             lhs, rhs = relation.get_variable_parts()
   141             if not lhs.as_string('utf-8') in selected_index:
   209             lhskey = lhs.as_string('utf-8')
   142                 select.append_selected(lhs.copy(select))
   210             if not lhskey in selectedidx:
   143                 selected_index[lhs.as_string('utf-8')] = index
   211                 if lhs.variable in eidconsts:
   144                 index += 1
   212                     eid = eidconsts[lhs.variable]
   145             if not rhs.as_string('utf-8') in selected_index:
   213                     lhsinfo = (_CONSTANT, eid, residx)
   146                 select.append_selected(rhs.copy(select))
   214                 else:
   147                 selected_index[rhs.as_string('utf-8')] = index
   215                     select.append_selected(lhs.copy(select))
   148                 index += 1
   216                     lhsinfo = (_FROM_SUBSTEP, selidx, residx)
       
   217                     selidx += 1
       
   218                 residx += 1
       
   219                 selectedidx[lhskey] = lhsinfo
       
   220             else:
       
   221                 lhsinfo = selectedidx[lhskey][:-1] + (None,)
       
   222             rhskey = rhs.as_string('utf-8')
       
   223             if not rhskey in selectedidx:
       
   224                 if isinstance(rhs, Constant):
       
   225                     rhsinfo = (_CONSTANT, rhs.eval(plan.args), residx)
       
   226                 elif getattr(rhs, 'variable', None) in eidconsts:
       
   227                     eid = eidconsts[rhs.variable]
       
   228                     rhsinfo = (_CONSTANT, eid, residx)
       
   229                 else:
       
   230                     select.append_selected(rhs.copy(select))
       
   231                     rhsinfo = (_FROM_SUBSTEP, selidx, residx)
       
   232                     selidx += 1
       
   233                 residx += 1
       
   234                 selectedidx[rhskey] = rhsinfo
       
   235             else:
       
   236                 rhsinfo = selectedidx[rhskey][:-1] + (None,)
   149             rschema = getrschema(relation.r_type)
   237             rschema = getrschema(relation.r_type)
       
   238             updatedefs.append( (lhsinfo, rhsinfo, rschema) )
   150             if rschema.final or rschema.inlined:
   239             if rschema.final or rschema.inlined:
   151                 attrrelations.append(relation)
   240                 attributes.add(relation.r_type)
   152             else:
   241         # the update step
   153                 relations.append(relation)
   242         step = UpdateStep(plan, updatedefs, attributes)
   154         # add step necessary to fetch all selected variables values
   243         # when necessary add substep to fetch yet unknown values
   155         if rqlst.where is not None:
   244         if select.selection:
   156             select.set_where(rqlst.where.copy(select))
   245             if rqlst.where is not None:
   157         # set distinct to avoid potential duplicate key error
   246                 select.set_where(rqlst.where.copy(select))
   158         select.distinct = True
   247             # set distinct to avoid potential duplicate key error
   159         step = UpdateStep(plan, attrrelations, relations, selected_index)
   248             select.distinct = True
   160         step.children += self._select_plan(plan, select, rqlst.solutions)
   249             step.children += self._select_plan(plan, select, rqlst.solutions)
   161         return (step,)
   250         return (step,)
   162 
   251 
   163     # internal methods ########################################################
   252     # internal methods ########################################################
   164 
   253 
   165     def _select_plan(self, plan, select, solutions):
   254     def _select_plan(self, plan, select, solutions):
   306                 sorted(self.sources), inputmap)
   395                 sorted(self.sources), inputmap)
   307 
   396 
   308 
   397 
   309 # UPDATE/INSERT/DELETE steps ##################################################
   398 # UPDATE/INSERT/DELETE steps ##################################################
   310 
   399 
   311 class RelationsStep(Step):
   400 class InsertRelationsStep(Step):
   312     """step consisting in adding attributes/relations to entity defs from a
   401     """step consisting in adding attributes/relations to entity defs from a
   313     previous FetchStep
   402     previous FetchStep
   314 
   403 
   315     relations values comes from the latest result, with one columns for
   404     relations values comes from the latest result, with one columns for
   316     each relation defined in self.rdefs
   405     each relation defined in self.rdefs
   332 
   421 
   333     def execute(self):
   422     def execute(self):
   334         """execute this step"""
   423         """execute this step"""
   335         base_edef = self.edef
   424         base_edef = self.edef
   336         edefs = []
   425         edefs = []
   337         result = self.execute_child()
   426         if self.children:
       
   427             result = self.execute_child()
       
   428         else:
       
   429             result = [[]]
   338         for row in result:
   430         for row in result:
   339             # get a new entity definition for this row
   431             # get a new entity definition for this row
   340             edef = copy(base_edef)
   432             edef = copy(base_edef)
   341             # complete this entity def using row values
   433             # complete this entity def using row values
   342             for i in range(len(self.rdefs)):
   434             index = 0
   343                 rtype, rorder = self.rdefs[i]
   435             for rtype, rorder, value in self.rdefs:
   344                 if rorder == RelationsStep.FINAL:
   436                 if value is _FROM_SUBSTEP:
   345                     edef[rtype] = row[i]
   437                     value = row[index]
   346                 elif rorder == RelationsStep.RELATION:
   438                     index += 1
   347                     self.plan.add_relation_def( (edef, rtype, row[i]) )
   439                 if rorder == InsertRelationsStep.FINAL:
   348                     edef.querier_pending_relations[(rtype, 'subject')] = row[i]
   440                     edef[rtype] = value
       
   441                 elif rorder == InsertRelationsStep.RELATION:
       
   442                     self.plan.add_relation_def( (edef, rtype, value) )
       
   443                     edef.querier_pending_relations[(rtype, 'subject')] = value
   349                 else:
   444                 else:
   350                     self.plan.add_relation_def( (row[i], rtype, edef) )
   445                     self.plan.add_relation_def( (value, rtype, edef) )
   351                     edef.querier_pending_relations[(rtype, 'object')] = row[i]
   446                     edef.querier_pending_relations[(rtype, 'object')] = value
   352             edefs.append(edef)
   447             edefs.append(edef)
   353         self.plan.substitute_entity_def(base_edef, edefs)
   448         self.plan.substitute_entity_def(base_edef, edefs)
   354         return result
   449         return result
   355 
   450 
   356 
       
   357 class InsertStep(Step):
   451 class InsertStep(Step):
   358     """step consisting in inserting new entities / relations"""
   452     """step consisting in inserting new entities / relations"""
   359 
   453 
   360     def execute(self):
   454     def execute(self):
   361         """execute this step"""
   455         """execute this step"""
   362         for step in self.children:
   456         for step in self.children:
   363             assert isinstance(step, RelationsStep)
   457             assert isinstance(step, InsertRelationsStep)
   364             step.plan = self.plan
   458             step.plan = self.plan
   365             step.execute()
   459             step.execute()
   366         # insert entities first
   460         # insert entities first
   367         result = self.plan.insert_entity_defs()
   461         result = self.plan.insert_entity_defs()
   368         # then relation
   462         # then relation
   406 class UpdateStep(Step):
   500 class UpdateStep(Step):
   407     """step consisting in updating entities / adding relations from relations
   501     """step consisting in updating entities / adding relations from relations
   408     definitions and from results fetched in previous step
   502     definitions and from results fetched in previous step
   409     """
   503     """
   410 
   504 
   411     def __init__(self, plan, attribute_relations, relations, selected_index):
   505     def __init__(self, plan, updatedefs, attributes):
   412         Step.__init__(self, plan)
   506         Step.__init__(self, plan)
   413         self.attribute_relations = attribute_relations
   507         self.updatedefs = updatedefs
   414         self.relations = relations
   508         self.attributes = attributes
   415         self.selected_index = selected_index
       
   416 
   509 
   417     def execute(self):
   510     def execute(self):
   418         """execute this step"""
   511         """execute this step"""
   419         plan = self.plan
       
   420         session = self.plan.session
   512         session = self.plan.session
   421         repo = session.repo
   513         repo = session.repo
   422         edefs = {}
   514         edefs = {}
   423         # insert relations
   515         # insert relations
   424         attributes = set([relation.r_type for relation in self.attribute_relations])
   516         if self.children:
   425         result = self.execute_child()
   517             result = self.execute_child()
   426         for row in result:
   518         else:
   427             for relation in self.attribute_relations:
   519             result = [[]]
   428                 lhs, rhs = relation.get_variable_parts()
   520         for i, row in enumerate(result):
   429                 eid = typed_eid(row[self.selected_index[str(lhs)]])
   521             newrow = []
   430                 try:
   522             for (lhsinfo, rhsinfo, rschema) in self.updatedefs:
   431                     edef = edefs[eid]
   523                 lhsval = _handle_relterm(lhsinfo, row, newrow)
   432                 except KeyError:
   524                 rhsval = _handle_relterm(rhsinfo, row, newrow)
   433                     edefs[eid] = edef = session.entity_from_eid(eid)
   525                 if rschema.final or rschema.inlined:
   434                 if isinstance(rhs, Constant):
   526                     eid = typed_eid(lhsval)
   435                     # add constant values to entity def
   527                     try:
   436                     value = rhs.eval(plan.args)
   528                         edef = edefs[eid]
   437                     edef[relation.r_type] = value
   529                     except KeyError:
       
   530                         edefs[eid] = edef = session.entity_from_eid(eid)
       
   531                     edef[str(rschema)] = rhsval
   438                 else:
   532                 else:
   439                     edef[relation.r_type] = row[self.selected_index[str(rhs)]]
   533                     repo.glob_add_relation(session, lhsval, str(rschema), rhsval)
   440             for relation in self.relations:
   534             result[i] = newrow
   441                 subj = row[self.selected_index[str(relation.children[0])]]
       
   442                 obj = row[self.selected_index[str(relation.children[1])]]
       
   443                 repo.glob_add_relation(session, subj, relation.r_type, obj)
       
   444         # update entities
   535         # update entities
   445         for eid, edef in edefs.iteritems():
   536         for eid, edef in edefs.iteritems():
   446             repo.glob_update_entity(session, edef, attributes)
   537             repo.glob_update_entity(session, edef, self.attributes)
   447         return result
   538         return result
       
   539 
       
   540 def _handle_relterm(info, row, newrow):
       
   541     if info[0] is _CONSTANT:
       
   542         val = info[1]
       
   543     else: # _FROM_SUBSTEP
       
   544         val = row[info[1]]
       
   545     if info[-1] is not None:
       
   546         newrow.append(val)
       
   547     return val