server/ssplanner.py
changeset 7141 ef29d3ea3909
parent 7118 e094b3d4eb95
child 7237 9f619715665b
equal deleted inserted replaced
7109:611663348158 7141:ef29d3ea3909
     1 # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     1 # copyright 2003-2011 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
    19 
    19 
    20 from __future__ import with_statement
    20 from __future__ import with_statement
    21 
    21 
    22 __docformat__ = "restructuredtext en"
    22 __docformat__ = "restructuredtext en"
    23 
    23 
    24 from copy import copy
       
    25 
       
    26 from rql.stmts import Union, Select
    24 from rql.stmts import Union, Select
    27 from rql.nodes import Constant, Relation
    25 from rql.nodes import Constant, Relation
    28 
    26 
    29 from cubicweb import QueryError, typed_eid
    27 from cubicweb import QueryError, typed_eid
    30 from cubicweb.schema import VIRTUAL_RTYPES
    28 from cubicweb.schema import VIRTUAL_RTYPES
    31 from cubicweb.rqlrewrite import add_types_restriction
    29 from cubicweb.rqlrewrite import add_types_restriction
    32 from cubicweb.server.session import security_enabled
    30 from cubicweb.server.session import security_enabled
    33 from cubicweb.server.hook import CleanupDeletedEidsCacheOp
    31 from cubicweb.server.hook import CleanupDeletedEidsCacheOp
       
    32 from cubicweb.server.edition import EditedEntity
    34 
    33 
    35 READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity'))
    34 READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity'))
    36 
    35 
    37 _CONSTANT = object()
    36 _CONSTANT = object()
    38 _FROM_SUBSTEP = object()
    37 _FROM_SUBSTEP = object()
   124     select.set_where(origrqlst.where.copy(select))
   123     select.set_where(origrqlst.where.copy(select))
   125     if not select.selection:
   124     if not select.selection:
   126         # no selection, append one randomly
   125         # no selection, append one randomly
   127         select.append_selected(rel.children[0].copy(select))
   126         select.append_selected(rel.children[0].copy(select))
   128     return select
   127     return select
   129 
       
   130 
       
   131 _MARKER = object()
       
   132 
       
   133 class dict_protocol_catcher(object):
       
   134     def __init__(self, entity):
       
   135         self.__entity = entity
       
   136     def __getitem__(self, attr):
       
   137         return self.__entity.cw_edited[attr]
       
   138     def __setitem__(self, attr, value):
       
   139         self.__entity.cw_edited[attr] = value
       
   140     def __getattr__(self, attr):
       
   141         return getattr(self.__entity, attr)
       
   142 
       
   143 
       
   144 class EditedEntity(dict):
       
   145     """encapsulate entities attributes being written by an RQL query"""
       
   146     def __init__(self, entity, **kwargs):
       
   147         dict.__init__(self, **kwargs)
       
   148         self.entity = entity
       
   149         self.skip_security = set()
       
   150         self.querier_pending_relations = {}
       
   151         self.saved = False
       
   152 
       
   153     def __hash__(self):
       
   154         # dict|set keyable
       
   155         return hash(id(self))
       
   156 
       
   157     def __cmp__(self, other):
       
   158         # we don't want comparison by value inherited from dict
       
   159         return cmp(id(self), id(other))
       
   160 
       
   161     def __setitem__(self, attr, value):
       
   162         assert attr != 'eid'
       
   163         # don't add attribute into skip_security if already in edited
       
   164         # attributes, else we may accidentaly skip a desired security check
       
   165         if attr not in self:
       
   166             self.skip_security.add(attr)
       
   167         self.edited_attribute(attr, value)
       
   168 
       
   169     def __delitem__(self, attr):
       
   170         assert not self.saved, 'too late to modify edited attributes'
       
   171         super(EditedEntity, self).__delitem__(attr)
       
   172         self.entity.cw_attr_cache.pop(attr, None)
       
   173 
       
   174     def pop(self, attr, *args):
       
   175         # don't update skip_security by design (think to storage api)
       
   176         assert not self.saved, 'too late to modify edited attributes'
       
   177         value = super(EditedEntity, self).pop(attr, *args)
       
   178         self.entity.cw_attr_cache.pop(attr, *args)
       
   179         return value
       
   180 
       
   181     def setdefault(self, attr, default):
       
   182         assert attr != 'eid'
       
   183         # don't add attribute into skip_security if already in edited
       
   184         # attributes, else we may accidentaly skip a desired security check
       
   185         if attr not in self:
       
   186             self[attr] = default
       
   187         return self[attr]
       
   188 
       
   189     def update(self, values, skipsec=True):
       
   190         if skipsec:
       
   191             setitem = self.__setitem__
       
   192         else:
       
   193             setitem = self.edited_attribute
       
   194         for attr, value in values.iteritems():
       
   195             setitem(attr, value)
       
   196 
       
   197     def edited_attribute(self, attr, value):
       
   198         """attribute being edited by a rql query: should'nt be added to
       
   199         skip_security
       
   200         """
       
   201         assert not self.saved, 'too late to modify edited attributes'
       
   202         super(EditedEntity, self).__setitem__(attr, value)
       
   203         self.entity.cw_attr_cache[attr] = value
       
   204 
       
   205     def oldnewvalue(self, attr):
       
   206         """returns the couple (old attr value, new attr value)
       
   207 
       
   208         NOTE: will only work in a before_update_entity hook
       
   209         """
       
   210         assert not self.saved, 'too late to get the old value'
       
   211         # get new value and remove from local dict to force a db query to
       
   212         # fetch old value
       
   213         newvalue = self.entity.cw_attr_cache.pop(attr, _MARKER)
       
   214         oldvalue = getattr(self.entity, attr)
       
   215         if newvalue is not _MARKER:
       
   216             self.entity.cw_attr_cache[attr] = newvalue
       
   217         else:
       
   218             newvalue = oldvalue
       
   219         return oldvalue, newvalue
       
   220 
       
   221     def set_defaults(self):
       
   222         """set default values according to the schema"""
       
   223         for attr, value in self.entity.e_schema.defaults():
       
   224             if not attr in self:
       
   225                 self[str(attr)] = value
       
   226 
       
   227     def check(self, creation=False):
       
   228         """check the entity edition against its schema. Only final relation
       
   229         are checked here, constraint on actual relations are checked in hooks
       
   230         """
       
   231         entity = self.entity
       
   232         if creation:
       
   233             # on creations, we want to check all relations, especially
       
   234             # required attributes
       
   235             relations = [rschema for rschema in entity.e_schema.subject_relations()
       
   236                          if rschema.final and rschema.type != 'eid']
       
   237         else:
       
   238             relations = [entity._cw.vreg.schema.rschema(rtype)
       
   239                          for rtype in self]
       
   240         from yams import ValidationError
       
   241         try:
       
   242             entity.e_schema.check(dict_protocol_catcher(entity),
       
   243                                   creation=creation, _=entity._cw._,
       
   244                                   relations=relations)
       
   245         except ValidationError, ex:
       
   246             ex.entity = self.entity
       
   247             raise
       
   248 
       
   249     def clone(self):
       
   250         thecopy = EditedEntity(copy(self.entity))
       
   251         thecopy.entity.cw_attr_cache = copy(self.entity.cw_attr_cache)
       
   252         thecopy.entity._cw_related_cache = {}
       
   253         thecopy.update(self, skipsec=False)
       
   254         return thecopy
       
   255 
   128 
   256 
   129 
   257 class SSPlanner(object):
   130 class SSPlanner(object):
   258     """SingleSourcePlanner: build execution plan for rql queries
   131     """SingleSourcePlanner: build execution plan for rql queries
   259 
   132