[rql2sql] fix bad sql generated when outer joining 'identity' relation and lhs var comes from a subquery. Closes #3099418
The generated SQL was attempting to access table.cw_eid which doesn't exist.
# copyright 2010-2012 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/>."""Specific views for data sources and related entities (eg CWSource,CWSourceHostConfig, CWSourceSchemaConfig)."""__docformat__="restructuredtext en"_=unicodeimportloggingfromitertoolsimportrepeatfromlogilab.mtconverterimportxml_escapefromlogilab.common.decoratorsimportcachedpropertyfromcubicwebimportUnauthorized,tagsfromcubicweb.utilsimportmake_uidfromcubicweb.predicatesimport(is_instance,score_entity,has_related_entities,match_user_groups,match_kwargs,match_view)fromcubicweb.viewimportEntityView,StartupViewfromcubicweb.schemaimportMETA_RTYPES,VIRTUAL_RTYPES,display_namefromcubicweb.webimportformwidgetsaswdgs,facetfromcubicweb.web.viewsimportadd_etype_buttonfromcubicweb.web.viewsimport(uicfg,tabs,actions,ibreadcrumbs,navigation,tableview,pyviews)_abaa=uicfg.actionbox_appearsin_addmenu# there are explicit 'add' buttons for those_abaa.tag_object_of(('CWSourceSchemaConfig','cw_schema','*'),False)_abaa.tag_object_of(('CWSourceSchemaConfig','cw_for_source','*'),False)_abaa.tag_object_of(('CWSourceSchemaConfig','cw_host_config_of','*'),False)_abaa.tag_object_of(('CWDataImport','cw_import_of','*'),False)_afs=uicfg.autoform_section_afs.tag_attribute(('CWSource','latest_retrieval'),'main','hidden')_afs.tag_attribute(('CWSource','in_synchronization'),'main','hidden')_afs.tag_object_of(('*','cw_for_source','CWSource'),'main','hidden')_affk=uicfg.autoform_field_kwargs_affk.tag_attribute(('CWSource','parser'),{'widget':wdgs.TextInput})# source primary views #########################################################_pvs=uicfg.primaryview_section_pvs.tag_attribute(('CWSource','name'),'hidden')_pvs.tag_object_of(('*','cw_for_source','CWSource'),'hidden')_pvs.tag_object_of(('*','cw_host_config_of','CWSource'),'hidden')_pvdc=uicfg.primaryview_display_ctrl_pvdc.tag_attribute(('CWSource','type'),{'vid':'attribute'})# disable reledit_rc=uicfg.reledit_ctrl_rc.tag_attribute(('CWSource','config'),{'rvid':'verbatimattr'})_rc.tag_attribute(('CWSourceHostConfig','config'),{'rvid':'verbatimattr'})_rc.tag_attribute(('CWSourceSchemaConfig','options'),{'rvid':'verbatimattr'})classCWSourcePrimaryView(tabs.TabbedPrimaryView):__select__=is_instance('CWSource')tabs=[_('cwsource-main'),_('cwsource-mapping'),_('cwsource-imports')]default_tab='cwsource-main'classCWSourceMainTab(tabs.PrimaryTab):__regid__='cwsource-main'__select__=is_instance('CWSource')defrender_entity_attributes(self,entity):super(CWSourceMainTab,self).render_entity_attributes(entity)self.w(add_etype_button(self._cw,'CWSourceHostConfig',__linkto='cw_host_config_of:%s:subject'%entity.eid,__redirectpath=entity.rest_path()))try:hostconfig=self._cw.execute('Any X, XC, XH WHERE X cw_host_config_of S, S eid %(s)s, ''X config XC, X match_host XH',{'s':entity.eid})exceptUnauthorized:passelse:ifhostconfig:self.w(u'<h3>%s</h3>'%self._cw._('CWSourceHostConfig_plural'))self._cw.view('table',hostconfig,w=self.w,displaycols=range(2),cellvids={1:'editable-final'})MAPPED_SOURCE_TYPES=set(('pyrorql','datafeed'))classCWSourceMappingTab(EntityView):__regid__='cwsource-mapping'__select__=(is_instance('CWSource')&match_user_groups('managers')&score_entity(lambdax:x.typeinMAPPED_SOURCE_TYPES))defentity_call(self,entity):_=self._cw._self.w('<h3>%s</h3>'%_('Entity and relation supported by this source'))self.w(add_etype_button(self._cw,'CWSourceSchemaConfig',__linkto='cw_for_source:%s:subject'%entity.eid))self.w(u'<div class="clear"></div>')rset=self._cw.execute('Any X, SCH, XO ORDERBY ET WHERE X options XO, X cw_for_source S, S eid %(s)s, ''X cw_schema SCH, SCH is ET',{'s':entity.eid})self.wview('table',rset,'noresult')# self.w('<h3>%s</h3>' % _('Relations that should not be crossed'))# self.w('<p>%s</p>' % _(# 'By default, when a relation is not supported by a source, it is '# 'supposed that a local relation may point to an entity from the '# 'external source. Relations listed here won\'t have this '# '"crossing" behaviour.'))# self.wview('list', entity.related('cw_dont_cross'), 'noresult')# self.w('<h3>%s</h3>' % _('Relations that can be crossed'))# self.w('<p>%s</p>' % _(# 'By default, when a relation is supported by a source, it is '# 'supposed that a local relation can\'t point to an entity from the '# 'external source. Relations listed here may have this '# '"crossing" behaviour anyway.'))# self.wview('list', entity.related('cw_may_cross'), 'noresult')checker=MAPPING_CHECKERS.get(entity.type,MappingChecker)(entity)checker.check()if(checker.errorsorchecker.warningsorchecker.infos):self.w('<h2>%s</h2>'%_('Detected problems'))errors=zip(repeat(_('error')),checker.errors)warnings=zip(repeat(_('warning')),checker.warnings)infos=zip(repeat(_('warning')),checker.infos)self.wview('pyvaltable',pyvalue=errors+warnings+infos)classMappingChecker(object):def__init__(self,cwsource):self.cwsource=cwsourceself.errors=[]self.warnings=[]self.infos=[]self.schema=cwsource._cw.vreg.schemadefinit(self):# supported entity typesself.sentities=set()# supported relationsself.srelations={}# avoid duplicated messagesself.seen=set()# first get mapping as dict/setsforschemacfginself.cwsource.reverse_cw_for_source:self.init_schemacfg(schemacfg)definit_schemacfg(self,schemacfg):cwerschema=schemacfg.schemaifcwerschema.__regid__=='CWEType':self.sentities.add(cwerschema.name)elifcwerschema.__regid__=='CWRType':assertnotcwerschema.nameinself.srelationsself.srelations[cwerschema.name]=Noneelse:# CWAttribute/CWRelationself.srelations.setdefault(cwerschema.rtype.name,[]).append((cwerschema.stype.name,cwerschema.otype.name))self.sentities.add(cwerschema.stype.name)self.sentities.add(cwerschema.otype.name)defcheck(self):self.init()error=self.errors.appendwarning=self.warnings.appendinfo=self.infos.appendforetypeinself.sentities:eschema=self.schema[etype]forrschema,ttypes,roleineschema.relation_definitions():ifrschemainMETA_RTYPES:continuettypes=[ttypeforttypeinttypesifttypeinself.sentities]ifnotrschemainself.srelations:forttypeinttypes:rdef=rschema.role_rdef(etype,ttype,role)self.seen.add(rdef)ifrdef.role_cardinality(role)in'1+':error(_('relation %(type)s with %(etype)s as %(role)s ''and target type %(target)s is mandatory but ''not supported')%{'rtype':rschema,'etype':etype,'role':role,'target':ttype})elifttypeinself.sentities:warning(_('%s could be supported')%rdef)elifnotttypes:warning(_('relation %(rtype)s with %(etype)s as %(role)s is ''supported but no target type supported')%{'rtype':rschema,'role':role,'etype':etype})forrtype,rdefsinself.srelations.iteritems():ifrdefsisNone:rschema=self.schema[rtype]forsubj,objinrschema.rdefs:ifsubjinself.sentitiesandobjinself.sentities:breakelse:error(_('relation %s is supported but none of its definitions ''matches supported entities')%rtype)self.custom_check()defcustom_check(self):passclassPyroRQLMappingChecker(MappingChecker):"""pyrorql source mapping checker"""definit(self):self.dontcross=set()self.maycross=set()super(PyroRQLMappingChecker,self).init()definit_schemacfg(self,schemacfg):options=schemacfg.optionsor()if'dontcross'inoptions:self.dontcross.add(schemacfg.schema.name)else:super(PyroRQLMappingChecker,self).init_schemacfg(schemacfg)if'maycross'inoptions:self.maycross.add(schemacfg.schema.name)defcustom_check(self):error=self.errors.appendinfo=self.infos.appendforetypeinself.sentities:eschema=self.schema[etype]forrschema,ttypes,roleineschema.relation_definitions():ifrschemainMETA_RTYPES:continueifnotrschemainself.srelations:ifrschemanotinself.dontcross:ifrole=='subject'andrschema.inlined:error(_('inlined relation %(rtype)s of %(etype)s ''should be supported')%{'rtype':rschema,'etype':etype})elif(rschemanotinself.seenandrschemanotinself.maycross):info(_('you may want to specify something for %s')%rschema)self.seen.add(rschema)elifrschemainself.maycrossandrschema.inlined:error(_('you should un-inline relation %s which is ''supported and may be crossed ')%rschema)MAPPING_CHECKERS={'pyrorql':PyroRQLMappingChecker,}classCWSourceImportsTab(EntityView):__regid__='cwsource-imports'__select__=(is_instance('CWSource')&has_related_entities('cw_import_of','object'))defentity_call(self,entity):rset=self._cw.execute('Any X, XST, XET, XS ORDERBY XST DESC WHERE ''X cw_import_of S, S eid %(s)s, X status XS, ''X start_timestamp XST, X end_timestamp XET',{'s':entity.eid})self._cw.view('cw.imports-table',rset,w=self.w)classCWImportsTable(tableview.EntityTableView):__regid__='cw.imports-table'__select__=is_instance('CWDataImport')columns=['import','start_timestamp','end_timestamp']column_renderers={'import':tableview.MainEntityColRenderer()}layout_args={'display_filter':'top'}# sources management view ######################################################classManageSourcesAction(actions.ManagersAction):__regid__='cwsource'title=_('data sources')category='manage'classCWSourcesManagementView(StartupView):__regid__='cw.sources-management'rql=('Any S,ST,SP,SD,SN ORDERBY SN WHERE S is CWSource, S name SN, S type ST, ''S latest_retrieval SD, S parser SP')title=_('data sources management')defcall(self,**kwargs):self.w('<h1>%s</h1>'%self._cw._(self.title))self.w(add_etype_button(self._cw,'CWSource'))self.w(u'<div class="clear"></div>')self.wview('cw.sources-table',self._cw.execute(self.rql))classCWSourcesTable(tableview.EntityTableView):__regid__='cw.sources-table'__select__=is_instance('CWSource')columns=['source','type','parser','latest_retrieval','latest_import']classLatestImportColRenderer(tableview.EntityTableColRenderer):defrender_cell(self,w,rownum):entity=self.entity(rownum)rset=self._cw.execute('Any X,XS,XST ORDERBY XST DESC LIMIT 1 WHERE ''X cw_import_of S, S eid %(s)s, X status XS, ''X start_timestamp XST',{'s':entity.eid})ifrset:self._cw.view('incontext',rset,row=0,w=w)else:w(self.empty_cell_content)column_renderers={'source':tableview.MainEntityColRenderer(),'latest_import':LatestImportColRenderer(header=_('latest import'),sortable=False)}# datafeed source import #######################################################REVERSE_SEVERITIES={logging.DEBUG:_('DEBUG'),logging.INFO:_('INFO'),logging.WARNING:_('WARNING'),logging.ERROR:_('ERROR'),logging.FATAL:_('FATAL')}deflog_to_table(req,rawdata):data=[]formsg_idx,msginenumerate(rawdata.split('<br/>')):record=msg.strip()ifnotrecord:continuetry:severity,url,line,msg=record.split('\t',3)exceptValueError:req.warning('badly formated log %s'%record)url=line=u''severity=logging.DEBUGmsg=recorddata.append((severity,url,line,msg))returndataclassLogTableLayout(tableview.TableLayout):__select__=match_view('cw.log.table')needs_js=tableview.TableLayout.needs_js+('cubicweb.log.js',)needs_css=tableview.TableLayout.needs_css+('cubicweb.log.css',)columns_css={0:'logSeverity',1:'logPath',2:'logLine',3:'logMsg',}defrender_table(self,w,actions,paginate):default_level=self.view.cw_extra_kwargs['default_level']ifdefault_level!='Debug':self._cw.add_onload('$("select.logFilter").val("%s").change();'%self._cw.form.get('logLevel',default_level))w(u'\n<form action="#"><fieldset>')w(u'<label>%s</label>'%self._cw._(u'Message threshold'))w(u'<select class="log_filter" onchange="filterLog(\'%s\', this.options[this.selectedIndex].value)">'%self.view.domid)forlevelin('DEBUG','INFO','WARNING','ERROR','FATAL'):w('<option value="%s">%s</option>'%(level.capitalize(),self._cw._(level)))w(u'</select>')w(u'</fieldset></form>')super(LogTableLayout,self).render_table(w,actions,paginate)deftable_attributes(self):attrs=super(LogTableLayout,self).table_attributes()attrs['id']='table'+self.view.domidreturnattrsdefrow_attributes(self,rownum):attrs=super(LogTableLayout,self).row_attributes(rownum)attrs['id']='log_msg_%i'%rownumseverityname=REVERSE_SEVERITIES[int(self.view.pyvalue[rownum][0])]attrs['class']='log%s'%severityname.capitalize()returnattrsdefcell_attributes(self,rownum,colnum,colid):attrs=super(LogTableLayout,self).cell_attributes(rownum,colnum,colid)attrs['class']=self.columns_css[colnum]returnattrsclassLogTable(pyviews.PyValTableView):__regid__='cw.log.table'headers=[_('severity'),_('url'),_('line'),_('message')]@cachedpropertydefdomid(self):returnmake_uid('logTable')classSeverityRenderer(pyviews.PyValTableColRenderer):defrender_cell(self,w,rownum):severity=self.data[rownum][0]w(u'<a class="internallink" href="javascript:;" title="%(title)s" 'u'''onclick="document.location.hash='%(msg_id)s';">¶</a>'''u' %(severity)s'%{'severity':self._cw._(REVERSE_SEVERITIES[int(severity)]),'title':self._cw._('permalink to this message'),'msg_id':'log_msg_%i'%rownum,})defsortvalue(self,rownum):returnint(self.data[rownum][0])classURLRenderer(pyviews.PyValTableColRenderer):defrender_cell(self,w,rownum):url=self.data[rownum][1]ifurlandurl.startswith('http'):url=tags.a(url,href=url)w(urloru' ')classLineRenderer(pyviews.PyValTableColRenderer):defrender_cell(self,w,rownum):line=self.data[rownum][2]w(lineoru' ')classMessageRenderer(pyviews.PyValTableColRenderer):snip_over=7defrender_cell(self,w,rownum):msg=self.data[rownum][3]lines=msg.splitlines()iflen(lines)<=self.snip_over:w(u'<pre class="rawtext">%s</pre>'%msg)else:# The make_uid argument has no specific meaning here.div_snip_id=make_uid(u'log_snip_')div_full_id=make_uid(u'log_full_')divs_id=(div_snip_id,div_full_id)snip=u'\n'.join((lines[0],lines[1],u' ...',u' %i more lines [double click to expand]'%(len(lines)-4),u' ...',lines[-2],lines[-1]))divs=((div_snip_id,snip,u'expand',"class='collapsed'"),(div_full_id,msg,u'collapse',"class='hidden'"))fordiv_id,content,button,h_classindivs:text=self._cw._(button)js=u"toggleVisibility('%s'); toggleVisibility('%s');"%divs_idw(u'<div id="%s" %s>'%(div_id,h_class))w(u'<pre class="raw_test" ondblclick="javascript: %s" 'u'title="%s" style="display: block;">'%(js,text))w(content)w(u'</pre>')w(u'</div>')column_renderers={0:SeverityRenderer(),1:URLRenderer(sortable=False),2:LineRenderer(sortable=False),3:MessageRenderer(sortable=False),}classDataFeedSourceDataImport(EntityView):__select__=EntityView.__select__&match_kwargs('rtype')__regid__='cw.formated_log'defcell_call(self,row,col,rtype,loglevel='Info',**kwargs):if'dispctrl'inself.cw_extra_kwargs:loglevel=self.cw_extra_kwargs['dispctrl'].get('loglevel',loglevel)entity=self.cw_rset.get_entity(row,col)value=getattr(entity,rtype)ifvalue:self._cw.view('cw.log.table',pyvalue=log_to_table(self._cw,value),default_level=loglevel,w=self.w)else:self.w(self._cw._('no log to display'))_pvs.tag_attribute(('CWDataImport','log'),'relations')_pvdc.tag_attribute(('CWDataImport','log'),{'vid':'cw.formated_log'})_pvs.tag_subject_of(('CWDataImport','cw_import_of','*'),'hidden')# in breadcrumbs_pvs.tag_object_of(('*','cw_import_of','CWSource'),'hidden')# in dedicated tabclassCWDataImportIPrevNextAdapter(navigation.IPrevNextAdapter):__select__=is_instance('CWDataImport')defnext_entity(self):ifself.entity.start_timestampisnotNone:# add NOT X eid %(e)s because > may not be enoughrset=self._cw.execute('Any X,XSTS ORDERBY 2 LIMIT 1 WHERE X is CWDataImport, ''X cw_import_of S, S eid %(s)s, NOT X eid %(e)s, ''X start_timestamp XSTS, X start_timestamp > %(sts)s',{'sts':self.entity.start_timestamp,'e':self.entity.eid,'s':self.entity.cwsource.eid})ifrset:returnrset.get_entity(0,0)defprevious_entity(self):ifself.entity.start_timestampisnotNone:# add NOT X eid %(e)s because < may not be enoughrset=self._cw.execute('Any X,XSTS ORDERBY 2 DESC LIMIT 1 WHERE X is CWDataImport, ''X cw_import_of S, S eid %(s)s, NOT X eid %(e)s, ''X start_timestamp XSTS, X start_timestamp < %(sts)s',{'sts':self.entity.start_timestamp,'e':self.entity.eid,'s':self.entity.cwsource.eid})ifrset:returnrset.get_entity(0,0)classCWDataImportStatusFacet(facet.AttributeFacet):__regid__='datafeed.dataimport.status'__select__=is_instance('CWDataImport')rtype='status'# breadcrumbs configuration ####################################################classCWsourceConfigIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):__select__=is_instance('CWSourceHostConfig','CWSourceSchemaConfig')defparent_entity(self):returnself.entity.cwsourceclassCWDataImportIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):__select__=is_instance('CWDataImport')defparent_entity(self):returnself.entity.cw_import_of[0]