"""Functions to add additional annotations on a rql syntax tree to ease latercode generation.:organization: Logilab:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr"""__docformat__="restructuredtext en"fromlogilab.common.compatimportanyfromrql.nodesimportRelation,VariableRef,Constant,Variable,Orfromrql.utilsimportcommon_parentdef_annotate_select(annotator,rqlst):forsubqueryinrqlst.with_:annotator._annotate_union(subquery.query)#if server.DEBUG:# print '-------- sql annotate', repr(rqlst)getrschema=annotator.schema.rschemahas_text_query=Falseneed_distinct=rqlst.distinctforrelinrqlst.iget_nodes(Relation):ifrel.neged(strict=True):ifrel.is_types_restriction():need_distinct=Trueelse:rschema=getrschema(rel.r_type)ifnotrschema.is_final():ifrschema.inlined:try:var=rel.children[1].children[0].variableexceptAttributeError:pass# rewritten variableelse:ifnotvar.stinfo['constnode']:need_distinct=Trueelifgetrschema(rel.r_type).symetric:forvrefinrel.iget_nodes(VariableRef):stinfo=vref.variable.stinfoifnotstinfo['constnode']andstinfo['selected']:need_distinct=True# XXX could mark as not invariantbreakforname,varinrqlst.defined_vars.items():stinfo=var.stinfoifstinfo.get('ftirels'):has_text_query=Trueifstinfo['attrvar']:stinfo['invariant']=Falsestinfo['principal']=_select_main_var(stinfo['rhsrelations'])continueifnotstinfo['relations']andnotstinfo['typerels']:# Any X, Any MAX(X)...# those particular queries should be executed using the system# entities table unless there is some type restrictionstinfo['invariant']=Truestinfo['principal']=Nonecontinueifany(relforrelinstinfo['relations']ifrel.r_type=='eid'andrel.operator()!='=')and \notany(rforrinvar.stinfo['relations']-var.stinfo['rhsrelations']ifr.r_type!='eid'and(getrschema(r.r_type).inlinedorgetrschema(r.r_type).final)):# Any X WHERE X eid > 2# those particular queries should be executed using the system entities tablestinfo['invariant']=Truestinfo['principal']=Nonecontinueifstinfo['selected']andvar.valuable_references()==1+bool(stinfo['constnode']):# "Any X", "Any X, Y WHERE X attr Y"stinfo['invariant']=Falsecontinuejoins=set()invariant=Falseforrefinvar.references():rel=ref.relation()ifrelisNoneorrel.is_types_restriction():continuelhs,rhs=rel.get_parts()onlhs=refislhsifrel.r_type=='eid':ifnot(onlhsandlen(stinfo['relations'])>1):breakifnotstinfo['constnode']:joins.add(rel)continueelifrel.r_type=='identity':# identity can't be used as principal, so check other relation are used# XXX explain rhs.operator == '='ifrhs.operator!='='orlen(stinfo['relations'])<=1:#(stinfo['constnode'] and rhs.operator == '='):breakjoins.add(rel)continuerschema=getrschema(rel.r_type)ifrel.optional:ifrelinstinfo['optrelations']:# optional variable can't be invariant if this is the lhs# variable of an inlined relationifnotrelinstinfo['rhsrelations']andrschema.inlined:breakelse:# variable used as main variable of an optional relation# can't be invariantbreakifrschema.finalor(onlhsandrschema.inlined):ifrschema.type!='has_text':# need join anyway if the variable appears in a final or# inlined relationbreakjoins.add(rel)continueifnotstinfo['constnode']:ifrschema.inlinedandrel.neged(strict=True):# if relation is inlined, can't be invariant if that # variable is used anywhere else.# see 'Any P WHERE NOT N ecrit_par P, N eid 512': # sql for 'NOT N ecrit_par P' is 'N.ecrit_par is NULL' so P# can use N.ecrit_par as principalif(stinfo['selected']orlen(stinfo['relations'])>1):breakelifrschema.symetricandstinfo['selected']:breakjoins.add(rel)else:# if there is at least one ambigous relation and no other to# restrict types, can't be invariant since we need to filter out# other typesifnotannotator.is_ambiguous(var):invariant=Truestinfo['invariant']=invariantifinvariantandjoins:# remember rqlst/solutions analyze information# we have to select a kindof "main" relation which will "extrajoins"# the other# priority should be given to relation which are not in inner queries# (eg exists)try:stinfo['principal']=_select_principal(var.sqlscope,joins)exceptCantSelectPrincipal:stinfo['invariant']=Falserqlst.need_distinct=need_distinctreturnhas_text_queryclassCantSelectPrincipal(Exception):passdef_select_principal(sqlscope,relations,_sort=lambdax:x):"""given a list of rqlst relations, select one which will be used to represent an invariant variable (e.g. using on extremity of the relation instead of the variable's type table """# _sort argument is there for testdiffscope_rels={}has_same_scope_rel=Falseored_rels=set()diffscope_rels=set()forrelin_sort(relations):# note: only eid and has_text among all final relations may be thereifrel.r_typein('eid','identity'):has_same_scope_rel=rel.sqlscopeissqlscopecontinueifrel.ored(traverse_scope=True):ored_rels.add(rel)elifrel.sqlscopeissqlscope:returnrelelifnotrel.neged(traverse_scope=True):diffscope_rels.add(rel)iflen(ored_rels)>1:ored_rels_copy=tuple(ored_rels)forrel1inored_rels_copy:forrel2inored_rels_copy:ifrel1isrel2:continueifisinstance(common_parent(rel1,rel2),Or):ored_rels.discard(rel1)ored_rels.discard(rel2)forrelin_sort(ored_rels):ifrel.sqlscopeissqlscope:returnreldiffscope_rels.add(rel)# if DISTINCT query, can use variable from a different scope as principal# since introduced duplicates will be removedifsqlscope.stmt.distinctanddiffscope_rels:returniter(_sort(diffscope_rels)).next()# XXX could use a relation for a different scope if it can't generate# duplicates, so we would have to check cardinalityraiseCantSelectPrincipal()def_select_main_var(relations):"""given a list of rqlst relations, select one which will be used as main relation for the rhs variable """forrelinrelations:ifrel.sqlscopeisrel.stmt:returnrelprincipal=relreturnprincipaldefset_qdata(getrschema,union,noinvariant):"""recursive function to set querier data on variables in the syntax tree """forselectinunion.children:forsubqueryinselect.with_:set_qdata(getrschema,subquery.query,noinvariant)forvarinselect.defined_vars.itervalues():ifvar.stinfo['invariant']:ifvarinnoinvariantandnotvar.stinfo['principal'].r_type=='has_text':var._q_invariant=Falseelse:var._q_invariant=Trueelse:var._q_invariant=Falseforrelinselect.iget_nodes(Relation):ifrel.neged(strict=True)andnotrel.is_types_restriction():rschema=getrschema(rel.r_type)ifnotrschema.is_final():# if one of the relation's variable is ambiguous but not# invariant, an intersection will be necessaryforvrefinrel.get_nodes(VariableRef):var=vref.variableif(notvar._q_invariantandvar.valuable_references()==1andlen(var.stinfo['possibletypes'])>1):select.need_intersect=Truebreakelse:continuebreakelse:select.need_intersect=FalseclassSQLGenAnnotator(object):def__init__(self,schema):self.schema=schemaself.nfdomain=frozenset(eschema.typeforeschemainschema.entities()ifnoteschema.is_final())defannotate(self,rqlst):"""add information to the rql syntax tree to help sources to do their job (read sql generation) a variable is tagged as invariant if: * it's a non final variable * it's not used as lhs in any final or inlined relation * there is no type restriction on this variable (either explicit in the syntax tree or because a solution for this variable has been removed due to security filtering) """assertrqlst.TYPE=='select',rqlstrqlst.has_text_query=self._annotate_union(rqlst)def_annotate_union(self,union):has_text_query=Falseforselectinunion.children:htq=_annotate_select(self,select)ifhtq:has_text_query=Truereturnhas_text_querydefis_ambiguous(self,var):# ignore has_text relationiflen([relforrelinvar.stinfo['relations']ifrel.sqlscopeisvar.sqlscopeandrel.r_type=='has_text'])==1:returnFalsetry:data=var.stmt._deamb_dataexceptAttributeError:data=var.stmt._deamb_data=IsAmbData(self.schema,self.nfdomain)data.compute(var.stmt)returndata.is_ambiguous(var)classIsAmbData(object):def__init__(self,schema,nfdomain):self.schema=schema# shortcutsself.rschema=schema.rschemaself.eschema=schema.eschema# domain for non final variablesself.nfdomain=nfdomain# {var: possible solutions set}self.varsols={}# set of ambiguous variablesself.ambiguousvars=set()# remember if a variable has been deambiguified by another to avoid# doing the oppositeself.deambification_map={}# not invariant variables (access to final.inlined relation)self.not_invariants=set()defis_ambiguous(self,var):returnvarinself.ambiguousvarsdefrestrict(self,var,restricted_domain):self.varsols[var]&=restricted_domainifvarinself.ambiguousvarsandself.varsols[var]==var.stinfo['possibletypes']:self.ambiguousvars.remove(var)defcompute(self,rqlst):# set domains for each variableforvarname,varinrqlst.defined_vars.iteritems():ifvar.stinfo['uidrels']or \self.eschema(rqlst.solutions[0][varname]).is_final():ptypes=var.stinfo['possibletypes']else:ptypes=set(self.nfdomain)self.ambiguousvars.add(var)self.varsols[var]=ptypesifnotself.ambiguousvars:return# apply relation restrictionself.maydeambrels=maydeambrels={}forrelinrqlst.iget_nodes(Relation):ifrel.is_types_restriction()orrel.r_type=='eid':continuelhs,rhs=rel.get_variable_parts()ifisinstance(lhs,VariableRef)orisinstance(rhs,VariableRef):rschema=self.rschema(rel.r_type)ifrschema.inlinedorrschema.is_final():self.not_invariants.add(lhs.variable)self.set_rel_constraint(lhs,rel,rschema.subjects)self.set_rel_constraint(rhs,rel,rschema.objects)# try to deambiguify more variables by considering other variables'typemodified=Truewhilemodifiedandself.ambiguousvars:modified=Falseforvarinself.ambiguousvars.copy():try:forrelin(var.stinfo['relations']&maydeambrels[var]):ifself.deambiguifying_relation(var,rel):modified=TruebreakexceptKeyError:# no relation to deambiguifycontinuedef_debug_print(self):print'varsols',dict((x,sorted(str(v)forvinvalues))forx,valuesinself.varsols.iteritems())print'ambiguous vars',sorted(self.ambiguousvars)defset_rel_constraint(self,term,rel,etypes_func):ifisinstance(term,VariableRef)andself.is_ambiguous(term.variable):var=term.variableiflen(var.stinfo['relations']-var.stinfo['typerels'])==1 \orrel.sqlscopeisvar.sqlscope:self.restrict(var,frozenset(etypes_func()))try:self.maydeambrels[var].add(rel)exceptKeyError:self.maydeambrels[var]=set((rel,))defdeambiguifying_relation(self,var,rel):lhs,rhs=rel.get_variable_parts()onlhs=varisgetattr(lhs,'variable',None)other=onlhsandrhsorlhsotheretypes=None# XXX isinstance(other.variable, Variable) to skip column aliasifisinstance(other,VariableRef)andisinstance(other.variable,Variable):deambiguifier=other.variableifnotvarisself.deambification_map.get(deambiguifier):ifnotvar.stinfo['typerels']:otheretypes=deambiguifier.stinfo['possibletypes']elifnotself.is_ambiguous(deambiguifier):otheretypes=self.varsols[deambiguifier]elifdeambiguifierinself.not_invariants:# we know variable won't be invariant, try to use# it to deambguify the current variableotheretypes=self.varsols[deambiguifier]elifisinstance(other,Constant)andother.uidtype:otheretypes=(other.uidtype,)deambiguifier=NoneifotheretypesisnotNone:# to restrict, we must check that for all type in othertypes,# possible types on the other end of the relation are matching# variable's possible typesrschema=self.rschema(rel.r_type)ifonlhs:rtypefunc=rschema.subjectselse:rtypefunc=rschema.objectsforotheretypeinotheretypes:reltypes=frozenset(rtypefunc(otheretype))ifvar.stinfo['possibletypes']!=reltypes:breakelse:self.restrict(var,var.stinfo['possibletypes'])self.deambification_map[var]=deambiguifierreturnTruereturnFalse