[entity] small optimization: once an entity has been completed, don't redo it (for nothing)
"""Defines the diferent querier steps usable in plans.FIXME : this code needs refactoring. Some problems :* get data from the parent plan, the latest step, temporary table...* each step has is own members (this is not necessarily bad, but a bit messy for now):organization: Logilab:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses"""__docformat__="restructuredtext en"fromrql.nodesimportVariableRef,Variable,Functionfromcubicweb.server.ssplannerimport(LimitOffsetMixIn,Step,OneFetchStep,varmap_test_repr,offset_result)AGGR_TRANSFORMS={'COUNT':'SUM','MIN':'MIN','MAX':'MAX','SUM':'SUM'}defremove_clauses(union,keepgroup):clauses=[]forselectinunion.children:ifkeepgroup:having,orderby=select.having,select.orderbyselect.having,select.orderby=None,Noneclauses.append((having,orderby))else:groupby,having,orderby=select.groupby,select.having,select.orderbyselect.groupby,select.having,select.orderby=None,None,Noneclauses.append((groupby,having,orderby))returnclausesdefrestore_clauses(union,keepgroup,clauses):fori,selectinenumerate(union.children):ifkeepgroup:select.having,select.orderby=clauses[i]else:select.groupby,select.having,select.orderby=clauses[i]classFetchStep(OneFetchStep):"""step consisting in fetching data from sources, and storing result in a temporary table """def__init__(self,plan,union,sources,table,keepgroup,inputmap=None):OneFetchStep.__init__(self,plan,union,sources)# temporary table to store step resultself.table=table# should groupby clause be kept or notself.keepgroup=keepgroup# variables mapping to use as inputself.inputmap=inputmap# output variable mappingsrqlst=union.children[0]# sample select node# add additional information to the output mappingself.outputmap=plan.init_temp_table(table,srqlst.selection,srqlst.solutions[0])forvrefinsrqlst.selection:ifnotisinstance(vref,VariableRef):continuevar=vref.variableifvar.stinfo['attrvars']:forlhsvar,rtypeinvar.stinfo['attrvars']:iflhsvar.nameinsrqlst.defined_vars:key='%s.%s'%(lhsvar.name,rtype)self.outputmap[key]=self.outputmap[var.name]else:rschema=self.plan.schema.rschemaforrelinvar.stinfo['rhsrelations']:ifrschema(rel.r_type).inlined:lhsvar=rel.children[0]iflhsvar.nameinsrqlst.defined_vars:key='%s.%s'%(lhsvar.name,rel.r_type)self.outputmap[key]=self.outputmap[var.name]defexecute(self):"""execute this step"""self.execute_children()plan=self.planplan.create_temp_table(self.table)union=self.union# XXX 2.5 use "with"clauses=remove_clauses(union,self.keepgroup)forsourceinself.sources:source.flying_insert(self.table,plan.session,union,plan.args,self.inputmap)restore_clauses(union,self.keepgroup,clauses)defmytest_repr(self):"""return a representation of this step suitable for test"""clauses=remove_clauses(self.union,self.keepgroup)try:inputmap=varmap_test_repr(self.inputmap,self.plan.tablesinorder)outputmap=varmap_test_repr(self.outputmap,self.plan.tablesinorder)exceptAttributeError:inputmap=self.inputmapoutputmap=self.outputmaptry:return(self.__class__.__name__,sorted((r.as_string(kwargs=self.plan.args),r.solutions)forrinself.union.children),sorted(self.sources),inputmap,outputmap)finally:restore_clauses(self.union,self.keepgroup,clauses)classAggrStep(LimitOffsetMixIn,Step):"""step consisting in making aggregat from temporary data in the system source """def__init__(self,plan,selection,select,table,outputtable=None):Step.__init__(self,plan)# original selectionself.selection=selection# original Select RQL treeself.select=select# table where are located temporary resultsself.table=table# optional table where to write resultsself.outputtable=outputtableifoutputtableisnotNone:plan.init_temp_table(outputtable,selection,select.solutions[0])#self.inputmap = inputmapdefmytest_repr(self):"""return a representation of this step suitable for test"""sel=self.select.selectionrestr=self.select.whereself.select.selection=self.selectionself.select.where=Nonerql=self.select.as_string(kwargs=self.plan.args)self.select.selection=selself.select.where=restrtry:# rely on a monkey patch (cf unittest_querier)table=self.plan.tablesinorder[self.table]outputtable=self.outputtableandself.plan.tablesinorder[self.outputtable]exceptAttributeError:# not monkey patchedtable=self.tableoutputtable=self.outputtablereturn(self.__class__.__name__,rql,self.limit,self.offset,table,outputtable)defexecute(self):"""execute this step"""self.execute_children()self.inputmap=inputmap=self.children[-1].outputmap# get the select clauseclause=[]fori,terminenumerate(self.selection):try:var_name=inputmap[term.as_string()]exceptKeyError:var_name='C%s'%iifisinstance(term,Function):# we have to translate some aggregat function# (for instance COUNT -> SUM)orig_name=term.nametry:term.name=AGGR_TRANSFORMS[term.name]# backup and reduce childrenorig_children=term.childrenterm.children=[VariableRef(Variable(var_name))]clause.append(term.accept(self))# restaure the tree XXX necessary?term.name=orig_nameterm.children=orig_childrenexceptKeyError:clause.append(var_name)else:clause.append(var_name)forvrefinterm.iget_nodes(VariableRef):inputmap[vref.name]=var_name# XXX handle distinct with non selected sort termifself.select.distinct:sql=['SELECT DISTINCT %s'%', '.join(clause)]else:sql=['SELECT %s'%', '.join(clause)]sql.append("FROM %s"%self.table)# get the group/having clausesifself.select.groupby:clause=[inputmap[var.name]forvarinself.select.groupby]grouped=set(var.nameforvarinself.select.groupby)sql.append('GROUP BY %s'%', '.join(clause))else:grouped=Noneifself.select.having:clause=[term.accept(self)forterminself.select.having]sql.append('HAVING %s'%', '.join(clause))# get the orderby clauseifself.select.orderby:clause=[]forsortterminself.select.orderby:sqlterm=sortterm.term.accept(self)ifsortterm.asc:clause.append(sqlterm)else:clause.append('%s DESC'%sqlterm)ifgroupedisnotNone:forvrefinsortterm.iget_nodes(VariableRef):ifnotvref.nameingrouped:sql[-1]+=', '+self.inputmap[vref.name]grouped.add(vref.name)sql.append('ORDER BY %s'%', '.join(clause))ifself.limit:sql.append('LIMIT %s'%self.limit)ifself.offset:sql.append('OFFSET %s'%self.offset)#print 'DATA', plan.sqlexec('SELECT * FROM %s' % self.table, None)sql=' '.join(sql)ifself.outputtable:self.plan.create_temp_table(self.outputtable)sql='INSERT INTO %s%s'%(self.outputtable,sql)returnself.plan.sqlexec(sql,self.plan.args)defvisit_function(self,function):"""generate SQL name for a function"""return'%s(%s)'%(function.name,','.join(c.accept(self)forcinfunction.children))defvisit_variableref(self,variableref):"""get the sql name for a variable reference"""try:returnself.inputmap[variableref.name]exceptKeyError:# XXX duh? explainreturnvariableref.variable.namedefvisit_constant(self,constant):"""generate SQL name for a constant"""assertconstant.type=='Int'returnstr(constant.value)classUnionStep(LimitOffsetMixIn,Step):"""union results of child in-memory steps (e.g. OneFetchStep / AggrStep)"""defexecute(self):"""execute this step"""result=[]limit=olimit=self.limitoffset=self.offsetassertoffset!=0ifoffsetisnotNone:limit=limit+offsetforstepinself.children:iflimitisnotNone:ifoffsetisNone:limit=olimit-len(result)step.set_limit_offset(limit,None)result_=step.execute()ifoffsetisnotNone:offset,result_=offset_result(offset,result_)result+=result_iflimitisnotNone:iflen(result)>=olimit:returnresult[:olimit]returnresultdefmytest_repr(self):"""return a representation of this step suitable for test"""return(self.__class__.__name__,self.limit,self.offset)classIntersectStep(UnionStep):"""return intersection of results of child in-memory steps (e.g. OneFetchStep / AggrStep)"""defexecute(self):"""execute this step"""result=set()forstepinself.children:result&=frozenset(step.execute())result=list(result)ifself.offset:result=result[self.offset:]ifself.limit:result=result[:self.limit]returnresultclassUnionFetchStep(Step):"""union results of child steps using temporary tables (e.g. FetchStep)"""defexecute(self):"""execute this step"""self.execute_children()__all__=('FetchStep','AggrStep','UnionStep','UnionFetchStep','IntersectStep')