[schema] React to yams improvement of metadata attribute handling.
* Change the way RichString is deprecated.
* Add documentation about metadata attribute
# 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/>."""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)"""from__future__importwith_statement__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'}classremove_and_restore_clauses(object):def__init__(self,union,keepgroup):self.union=unionself.keepgroup=keepgroupself.clauses=Nonedef__enter__(self):self.clauses=clauses=[]forselectinself.union.children:ifself.keepgroup:having,orderby=select.having,select.orderbyselect.having,select.orderby=(),()clauses.append((having,orderby))else:groupby,having,orderby=select.groupby,select.having,select.orderbyselect.groupby,select.having,select.orderby=(),(),()clauses.append((groupby,having,orderby))def__exit__(self,exctype,exc,traceback):fori,selectinenumerate(self.union.children):ifself.keepgroup:select.having,select.orderby=self.clauses[i]else:select.groupby,select.having,select.orderby=self.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.get('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.unionwithremove_and_restore_clauses(union,self.keepgroup):forsourceinself.sources:source.flying_insert(self.table,plan.session,union,plan.args,self.inputmap)defmytest_repr(self):"""return a representation of this step suitable for test"""withremove_and_restore_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.outputmapreturn(self.__class__.__name__,sorted((r.as_string(kwargs=self.plan.args),r.solutions)forrinself.union.children),sorted(self.sources),inputmap,outputmap)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"""try:# 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.outputtablesql=self.get_sql().replace(self.table,table)return(self.__class__.__name__,sql,outputtable)defexecute(self):"""execute this step"""self.execute_children()sql=self.get_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)defget_sql(self):self.inputmap=inputmap=self.children[-1].outputmapdbhelper=self.plan.syssource.dbhelper# 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=dbhelper.sql_add_order_by(' '.join(sql),clause,None,False,self.limitorself.offset)else:sql=' '.join(sql)clause=Nonesql=dbhelper.sql_add_limit_offset(sql,self.limit,self.offset,clause)returnsqldefvisit_function(self,function):"""generate SQL name for a function"""try:returnself.children[0].outputmap[str(function)]exceptKeyError: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')