[web templates] fix invalid html in main-no-top template; closes #2174806
# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr## This file is part of CubicWeb.## CubicWeb is free software: you can redistribute it and/or modify it under the# terms of the GNU Lesser General Public License as published by the Free# Software Foundation, either version 2.1 of the License, or (at your option)# any later version.## CubicWeb is distributed in the hope that it will be useful, but WITHOUT# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more# details.## You should have received a copy of the GNU Lesser General Public License along# with CubicWeb. If not, see <http://www.gnu.org/licenses/>."""plan execution of rql queries on a single source"""from__future__importwith_statement__docformat__="restructuredtext en"fromrql.stmtsimportUnion,Selectfromrql.nodesimportConstant,RelationfromcubicwebimportQueryError,typed_eidfromcubicweb.schemaimportVIRTUAL_RTYPESfromcubicweb.rqlrewriteimportadd_types_restrictionfromcubicweb.server.sessionimportsecurity_enabledfromcubicweb.server.editionimportEditedEntityREAD_ONLY_RTYPES=set(('eid','has_text','is','is_instance_of','identity'))_CONSTANT=object()_FROM_SUBSTEP=object()def_extract_const_attributes(plan,rqlst,to_build):"""add constant values to entity def, mark variables to be selected """to_select={}forrelationinrqlst.main_relations:lhs,rhs=relation.get_variable_parts()rtype=relation.r_typeifrtypeinREAD_ONLY_RTYPES:raiseQueryError("can't assign to %s"%rtype)try:edef=to_build[str(lhs)]exceptKeyError:# lhs var is not to build, should be selected and added as an# object relationedef=to_build[str(rhs)]to_select.setdefault(edef,[]).append((rtype,lhs,1))else:ifisinstance(rhs,Constant)andnotrhs.uid:# add constant values to entity defvalue=rhs.eval(plan.args)eschema=edef.entity.e_schemaattrtype=eschema.subjrels[rtype].objects(eschema)[0]ifattrtype=='Password'andisinstance(value,unicode):value=value.encode('UTF8')edef.edited_attribute(rtype,value)elifto_build.has_key(str(rhs)):# create a relation between two newly created variablesplan.add_relation_def((edef,rtype,to_build[rhs.name]))else:to_select.setdefault(edef,[]).append((rtype,rhs,0))returnto_selectdef_extract_eid_consts(plan,rqlst):"""return a dict mapping rqlst variable object to their eid if specified in the syntax tree """session=plan.sessionifrqlst.whereisNone:return{}eidconsts={}neweids=session.transaction_data.get('neweids',())checkread=session.read_securityeschema=session.vreg.schema.eschemaforrelinrqlst.where.get_nodes(Relation):ifrel.r_type=='eid'andnotrel.neged(strict=True):lhs,rhs=rel.get_variable_parts()ifisinstance(rhs,Constant):eid=typed_eid(rhs.eval(plan.args))# check read permission here since it may not be done by# the generated select substep if not emited (eg nothing# to be selected)ifcheckreadandeidnotinneweids:withsecurity_enabled(session,read=False):eschema(session.describe(eid)[0]).check_perm(session,'read',eid=eid)eidconsts[lhs.variable]=eidreturneidconstsdef_build_substep_query(select,origrqlst):"""Finalize substep select query that should be executed to get proper selection of stuff to insert/update. Return None when no query actually needed, else the given select node that will be used as substep query. When select has nothing selected, search in origrqlst for restriction that should be considered. """iforigrqlst.whereisnotNoneandnotselect.selection:# no selection, append one randomly by searching for a relation which is# neither a type restriction (is) nor an eid specification (not neged# eid with constant node)forrelinorigrqlst.where.iget_nodes(Relation):ifrel.neged(strict=True)ornot(rel.is_types_restriction()or(rel.r_type=='eid'andisinstance(rel.get_variable_parts()[1],Constant))):select.append_selected(rel.children[0].copy(select))breakelse:returnifselect.selection:iforigrqlst.whereisnotNone:select.set_where(origrqlst.where.copy(select))ifgetattr(origrqlst,'having',None):select.set_having([sq.copy(select)forsqinorigrqlst.having])returnselectreturnNoneclassSSPlanner(object):"""SingleSourcePlanner: build execution plan for rql queries optimized for single source repositories """def__init__(self,schema,rqlhelper):self.schema=schemaself.rqlhelper=rqlhelperdefbuild_plan(self,plan):"""build an execution plan from a RQL query do nothing here, dispatch according to the statement type """build_plan=getattr(self,'build_%s_plan'%plan.rqlst.TYPE)forstepinbuild_plan(plan,plan.rqlst):plan.add_step(step)defbuild_select_plan(self,plan,rqlst):"""build execution plan for a SELECT RQL query. Suppose only one source is available and so avoid work need for query decomposition among sources the rqlst should not be tagged at this point. """plan.preprocess(rqlst)return(OneFetchStep(plan,rqlst,plan.session.repo.sources),)defbuild_insert_plan(self,plan,rqlst):"""get an execution plan from an INSERT RQL query"""# each variable in main variables is a new entity to insertto_build={}session=plan.sessionetype_class=session.vreg['etypes'].etype_classforetype,varinrqlst.main_variables:# need to do this since entity class is shared w. web client code !to_build[var.name]=EditedEntity(etype_class(etype)(session))plan.add_entity_def(to_build[var.name])# add constant values to entity def, mark variables to be selectedto_select=_extract_const_attributes(plan,rqlst,to_build)# add necessary steps to add relations and update attributesstep=InsertStep(plan)# insert each entity and its relationsstep.children+=self._compute_relation_steps(plan,rqlst,to_select)return(step,)def_compute_relation_steps(self,plan,rqlst,to_select):"""handle the selection of relations for an insert query"""eidconsts=_extract_eid_consts(plan,rqlst)foredef,rdefsinto_select.items():# create a select rql st to fetch needed dataselect=Select()eschema=edef.entity.e_schemafori,(rtype,term,reverse)inenumerate(rdefs):ifgetattr(term,'variable',None)ineidconsts:value=eidconsts[term.variable]else:select.append_selected(term.copy(select))value=_FROM_SUBSTEPifreverse:rdefs[i]=(rtype,InsertRelationsStep.REVERSE_RELATION,value)else:rschema=eschema.subjrels[rtype]ifrschema.finalorrschema.inlined:rdefs[i]=(rtype,InsertRelationsStep.FINAL,value)else:rdefs[i]=(rtype,InsertRelationsStep.RELATION,value)step=InsertRelationsStep(plan,edef,rdefs)select=_build_substep_query(select,rqlst)ifselectisnotNone:step.children+=self._select_plan(plan,select,rqlst.solutions)yieldstepdefbuild_delete_plan(self,plan,rqlst):"""get an execution plan from a DELETE RQL query"""# build a select query to fetch entities to deletesteps=[]foretype,varinrqlst.main_variables:step=DeleteEntitiesStep(plan)step.children+=self._sel_variable_step(plan,rqlst,etype,var)steps.append(step)forrelationinrqlst.main_relations:step=DeleteRelationsStep(plan,relation.r_type)step.children+=self._sel_relation_steps(plan,rqlst,relation)steps.append(step)returnstepsdef_sel_variable_step(self,plan,rqlst,etype,varref):"""handle the selection of variables for a delete query"""select=Select()varref=varref.copy(select)select.defined_vars={varref.name:varref.variable}select.append_selected(varref)ifrqlst.whereisnotNone:select.set_where(rqlst.where.copy(select))ifgetattr(rqlst,'having',None):select.set_having([x.copy(select)forxinrqlst.having])ifetype!='Any':select.add_type_restriction(varref.variable,etype)returnself._select_plan(plan,select,rqlst.solutions)def_sel_relation_steps(self,plan,rqlst,relation):"""handle the selection of relations for a delete query"""select=Select()lhs,rhs=relation.get_variable_parts()select.append_selected(lhs.copy(select))select.append_selected(rhs.copy(select))select.set_where(relation.copy(select))ifrqlst.whereisnotNone:select.add_restriction(rqlst.where.copy(select))ifgetattr(rqlst,'having',None):select.set_having([x.copy(select)forxinrqlst.having])returnself._select_plan(plan,select,rqlst.solutions)defbuild_set_plan(self,plan,rqlst):"""get an execution plan from an SET RQL query"""getrschema=self.schema.rschemaselect=Select()# potential substep queryselectedidx={}# local stateattributes=set()# edited attributesupdatedefs=[]# definition of update attributes/relationsselidx=residx=0# substep selection / resulting rset indexes# search for eid const in the WHERE clauseeidconsts=_extract_eid_consts(plan,rqlst)# build `updatedefs` describing things to update and add necessary# variables to the substep selectionfori,relationinenumerate(rqlst.main_relations):ifrelation.r_typeinVIRTUAL_RTYPES:raiseQueryError('can not assign to %r relation'%relation.r_type)lhs,rhs=relation.get_variable_parts()lhskey=lhs.as_string('utf-8')ifnotlhskeyinselectedidx:iflhs.variableineidconsts:eid=eidconsts[lhs.variable]lhsinfo=(_CONSTANT,eid,residx)else:select.append_selected(lhs.copy(select))lhsinfo=(_FROM_SUBSTEP,selidx,residx)selidx+=1residx+=1selectedidx[lhskey]=lhsinfoelse:lhsinfo=selectedidx[lhskey][:-1]+(None,)rhskey=rhs.as_string('utf-8')ifnotrhskeyinselectedidx:ifisinstance(rhs,Constant):rhsinfo=(_CONSTANT,rhs.eval(plan.args),residx)elifgetattr(rhs,'variable',None)ineidconsts:eid=eidconsts[rhs.variable]rhsinfo=(_CONSTANT,eid,residx)else:select.append_selected(rhs.copy(select))rhsinfo=(_FROM_SUBSTEP,selidx,residx)selidx+=1residx+=1selectedidx[rhskey]=rhsinfoelse:rhsinfo=selectedidx[rhskey][:-1]+(None,)rschema=getrschema(relation.r_type)updatedefs.append((lhsinfo,rhsinfo,rschema))# the update stepstep=UpdateStep(plan,updatedefs)# when necessary add substep to fetch yet unknown valuesselect=_build_substep_query(select,rqlst)ifselectisnotNone:# set distinct to avoid potential duplicate key errorselect.distinct=Truestep.children+=self._select_plan(plan,select,rqlst.solutions)return(step,)# internal methods ########################################################def_select_plan(self,plan,select,solutions):union=Union()union.append(select)select.clean_solutions(solutions)add_types_restriction(self.schema,select)self.rqlhelper.annotate(union)returnself.build_select_plan(plan,union)# execution steps and helper functions ########################################defvarmap_test_repr(varmap,tablesinorder):ifvarmapisNone:returnvarmapmaprepr={}forvar,sqlinvarmap.iteritems():table,col=sql.split('.')maprepr[var]='%s.%s'%(tablesinorder[table],col)returnmapreprdefoffset_result(offset,result):offset-=len(result)ifoffset<0:result=result[offset:]offset=Noneelifoffset==0:offset=Noneresult=()returnoffset,resultclassLimitOffsetMixIn(object):limit=offset=Nonedefset_limit_offset(self,limit,offset):self.limit=limitself.offset=offsetorNoneclassStep(object):"""base abstract class for execution step"""def__init__(self,plan):self.plan=planself.children=[]defexecute_child(self):assertlen(self.children)==1returnself.children[0].execute()defexecute_children(self):forstepinself.children:step.execute()defexecute(self):"""execute this step and store partial (eg this step) results"""raiseNotImplementedError()defmytest_repr(self):"""return a representation of this step suitable for test"""return(self.__class__.__name__,)deftest_repr(self):"""return a representation of this step suitable for test"""returnself.mytest_repr()+([step.test_repr()forstepinself.children],)classOneFetchStep(LimitOffsetMixIn,Step):"""step consisting in fetching data from sources and directly returning results """def__init__(self,plan,union,sources,inputmap=None):Step.__init__(self,plan)self.union=unionself.sources=sourcesself.inputmap=inputmapself.set_limit_offset(union.children[-1].limit,union.children[-1].offset)defset_limit_offset(self,limit,offset):LimitOffsetMixIn.set_limit_offset(self,limit,offset)forselectinself.union.children:select.limit=limitselect.offset=offsetdefexecute(self):"""call .syntax_tree_search with the given syntax tree on each source for each solution """self.execute_children()session=self.plan.sessionargs=self.plan.argsinputmap=self.inputmapunion=self.union# do we have to use a inputmap from a previous step ? If so disable# cachekeyifinputmaporself.plan.cache_keyisNone:cachekey=None# union may have been splited into subqueries, in which case we can't# use plan.cache_key, rebuild a cache keyelifisinstance(self.plan.cache_key,tuple):cachekey=list(self.plan.cache_key)cachekey[0]=union.as_string()cachekey=tuple(cachekey)else:cachekey=union.as_string()result=[]# limit / offset processinglimit=self.limitoffset=self.offsetifoffsetisnotNone:iflen(self.sources)>1:# we'll have to deal with limit/offset by ourselfifunion.children[-1].limit:union.children[-1].limit=limit+offsetunion.children[-1].offset=Noneelse:offset,limit=None,Noneforsourceinself.sources:ifoffsetisNoneandlimitisnotNone:# modifying the sample rqlst is enough since sql generation# will pick it here as wellunion.children[-1].limit=limit-len(result)result_=source.syntax_tree_search(session,union,args,cachekey,inputmap)ifoffsetisnotNone:offset,result_=offset_result(offset,result_)result+=result_iflimitisnotNone:iflen(result)>=limit:returnresult[:limit]#print 'ONEFETCH RESULT %s' % (result)returnresultdefmytest_repr(self):"""return a representation of this step suitable for test"""try:inputmap=varmap_test_repr(self.inputmap,self.plan.tablesinorder)exceptAttributeError:inputmap=self.inputmapreturn(self.__class__.__name__,sorted((r.as_string(kwargs=self.plan.args),r.solutions)forrinself.union.children),self.limit,self.offset,sorted(self.sources),inputmap)# UPDATE/INSERT/DELETE steps ##################################################classInsertRelationsStep(Step):"""step consisting in adding attributes/relations to entity defs from a previous FetchStep relations values comes from the latest result, with one columns for each relation defined in self.rdefs for one entity definition, we'll construct N entity, where N is the number of the latest result """FINAL=0RELATION=1REVERSE_RELATION=2def__init__(self,plan,edef,rdefs):Step.__init__(self,plan)# partial entity definition to expandself.edef=edef# definition of relations to completeself.rdefs=rdefsdefexecute(self):"""execute this step"""base_edef=self.edefedefs=[]ifself.children:result=self.execute_child()else:result=[[]]forrowinresult:# get a new entity definition for this rowedef=base_edef.clone()# complete this entity def using row valuesindex=0forrtype,rorder,valueinself.rdefs:ifvalueis_FROM_SUBSTEP:value=row[index]index+=1ifrorder==InsertRelationsStep.FINAL:edef.edited_attribute(rtype,value)elifrorder==InsertRelationsStep.RELATION:self.plan.add_relation_def((edef,rtype,value))edef.querier_pending_relations[(rtype,'subject')]=valueelse:self.plan.add_relation_def((value,rtype,edef))edef.querier_pending_relations[(rtype,'object')]=valueedefs.append(edef)self.plan.substitute_entity_def(base_edef,edefs)returnresultclassInsertStep(Step):"""step consisting in inserting new entities / relations"""defexecute(self):"""execute this step"""forstepinself.children:assertisinstance(step,InsertRelationsStep)step.plan=self.planstep.execute()# insert entities firstresult=self.plan.insert_entity_defs()# then relationself.plan.insert_relation_defs()# return eids of inserted entitiesreturnresultclassDeleteEntitiesStep(Step):"""step consisting in deleting entities"""defexecute(self):"""execute this step"""results=self.execute_child()ifresults:todelete=frozenset(typed_eid(eid)foreid,inresults)session=self.plan.sessionsession.repo.glob_delete_entities(session,todelete)returnresultsclassDeleteRelationsStep(Step):"""step consisting in deleting relations"""def__init__(self,plan,rtype):Step.__init__(self,plan)self.rtype=rtypedefexecute(self):"""execute this step"""session=self.plan.sessiondelete=session.repo.glob_delete_relationforsubj,objinself.execute_child():delete(session,subj,self.rtype,obj)classUpdateStep(Step):"""step consisting in updating entities / adding relations from relations definitions and from results fetched in previous step """def__init__(self,plan,updatedefs):Step.__init__(self,plan)self.updatedefs=updatedefsdefexecute(self):"""execute this step"""session=self.plan.sessionrepo=session.repoedefs={}relations={}# insert relationsifself.children:result=self.execute_child()else:result=[[]]fori,rowinenumerate(result):newrow=[]for(lhsinfo,rhsinfo,rschema)inself.updatedefs:lhsval=_handle_relterm(lhsinfo,row,newrow)rhsval=_handle_relterm(rhsinfo,row,newrow)ifrschema.finalorrschema.inlined:eid=typed_eid(lhsval)try:edited=edefs[eid]exceptKeyError:edef=session.entity_from_eid(eid)edefs[eid]=edited=EditedEntity(edef)edited.edited_attribute(str(rschema),rhsval)else:str_rschema=str(rschema)ifstr_rschemainrelations:relations[str_rschema].append((lhsval,rhsval))else:relations[str_rschema]=[(lhsval,rhsval)]result[i]=newrow# update entitiesrepo.glob_add_relations(session,relations)foreid,editedinedefs.iteritems():repo.glob_update_entity(session,edited)returnresultdef_handle_relterm(info,row,newrow):ifinfo[0]is_CONSTANT:val=info[1]else:# _FROM_SUBSTEPval=row[info[1]]ifinfo[-1]isnotNone:newrow.append(val)returnval