goa/rqlinterpreter.py
branchstable
changeset 6340 470d8e828fda
parent 6339 bdc3dc94d744
child 6341 ad5e08981153
equal deleted inserted replaced
6339:bdc3dc94d744 6340:470d8e828fda
     1 # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """provide a minimal RQL support for google appengine dbmodel
       
    19 
       
    20 """
       
    21 __docformat__ = "restructuredtext en"
       
    22 
       
    23 from datetime import datetime
       
    24 
       
    25 from rql import RQLHelper, nodes
       
    26 
       
    27 from cubicweb import Binary
       
    28 from cubicweb.rset import ResultSet
       
    29 from cubicweb.server import SQL_CONNECT_HOOKS
       
    30 
       
    31 from google.appengine.api.datastore import Key, Get, Query, Entity
       
    32 from google.appengine.api.datastore_types import Text, Blob
       
    33 from google.appengine.api.datastore_errors import EntityNotFoundError, BadKeyError
       
    34 
       
    35 
       
    36 def etype_from_key(key):
       
    37     return Key(key).kind()
       
    38 
       
    39 def poss_var_types(myvar, ovar, kind, solutions):
       
    40     return frozenset(etypes[myvar] for etypes in solutions
       
    41                      if etypes[ovar] == kind)
       
    42 
       
    43 def expand_result(results, result, myvar, values, dsget=None):
       
    44     values = map(dsget, values)
       
    45     if values:
       
    46         result[myvar] = values.pop(0)
       
    47         for value in values:
       
    48             newresult = result.copy()
       
    49             newresult[myvar] = value
       
    50             results.append(newresult)
       
    51     else:
       
    52         results.remove(result)
       
    53 
       
    54 def _resolve(restrictions, solutions, fixed):
       
    55     varname = restrictions[0].searched_var
       
    56     objs = []
       
    57     for etype in frozenset(etypes[varname] for etypes in solutions):
       
    58         gqlargs = {}
       
    59         query = Query(etype)
       
    60         for restriction in restrictions:
       
    61             restriction.fill_query(fixed, query)
       
    62         pobjs = query.Run()
       
    63         if varname in fixed:
       
    64             value = fixed[varname]
       
    65             objs += (x for x in pobjs if x == value)
       
    66         else:
       
    67             objs += pobjs
       
    68     if varname in fixed and not objs:
       
    69         raise EidMismatch(varname, value)
       
    70     return objs
       
    71 
       
    72 def _resolve_not(restrictions, solutions, fixed):
       
    73     restr = restrictions[0]
       
    74     constrvarname = restr.constraint_var
       
    75     if len(restrictions) > 1 or not constrvarname in fixed:
       
    76         raise NotImplementedError()
       
    77     varname = restr.searched_var
       
    78     objs = []
       
    79     for etype in frozenset(etypes[varname] for etypes in solutions):
       
    80         gqlargs = {}
       
    81         for operator in ('<', '>'):
       
    82             query = Query(etype)
       
    83             restr.fill_query(fixed, query, operator)
       
    84             objs += query.Run()
       
    85     return objs
       
    86 
       
    87 def _print_results(rlist):
       
    88     return '[%s]' % ', '.join(_print_result(r) for r in rlist)
       
    89 
       
    90 def _print_result(rdict):
       
    91     string = []
       
    92     for k, v in rdict.iteritems():
       
    93         if isinstance(v, Entity):
       
    94             string.append('%s: %s' % (k, v.key()))#_print_result(v)))
       
    95         elif isinstance(v, list):
       
    96             string.append('%s: [%s]' % (k, ', '.join(str(i) for i in v)))
       
    97         else:
       
    98             string.append('%s: %s' % (k, v))
       
    99     return '{%s}' % ', '.join(string)
       
   100 
       
   101 
       
   102 class EidMismatch(Exception):
       
   103     def __init__(self, varname, value):
       
   104         self.varname = varname
       
   105         self.value = value
       
   106 
       
   107 
       
   108 class Restriction(object):
       
   109     supported_operators = ('=',)
       
   110     def __init__(self, rel):
       
   111         operator = rel.children[1].operator
       
   112         if not operator in self.supported_operators:
       
   113             raise NotImplementedError('unsupported operator')
       
   114         self.rel = rel
       
   115         self.operator = operator
       
   116         self.rtype = rel.r_type
       
   117         self.var = rel.children[0]
       
   118 
       
   119     def __repr__(self):
       
   120         return '<%s for %s>' % (self.__class__.__name__, self.rel)
       
   121 
       
   122     @property
       
   123     def rhs(self):
       
   124         return self.rel.children[1].children[0]
       
   125 
       
   126 
       
   127 class MultipleRestriction(object):
       
   128     def __init__(self, restrictions):
       
   129         self.restrictions = restrictions
       
   130 
       
   131     def resolve(self, solutions, fixed):
       
   132         return _resolve(self.restrictions, solutions, fixed)
       
   133 
       
   134 
       
   135 class VariableSelection(Restriction):
       
   136     def __init__(self, rel, dsget, prefix='s'):
       
   137         Restriction.__init__(self, rel)
       
   138         self._dsget = dsget
       
   139         self._not = self.rel.neged(strict=True)
       
   140         self._prefix = prefix + '_'
       
   141 
       
   142     def __repr__(self):
       
   143         return '<%s%s for %s>' % (self._prefix[0], self.__class__.__name__, self.rel)
       
   144 
       
   145     @property
       
   146     def searched_var(self):
       
   147         if self._prefix == 's_':
       
   148             return self.var.name
       
   149         return self.rhs.name
       
   150 
       
   151     @property
       
   152     def constraint_var(self):
       
   153         if self._prefix == 's_':
       
   154             return self.rhs.name
       
   155         return self.var.name
       
   156 
       
   157     def _possible_values(self, myvar, ovar, entity, solutions, dsprefix):
       
   158         if self.rtype == 'identity':
       
   159             return (entity.key(),)
       
   160         value = entity.get(dsprefix + self.rtype)
       
   161         if value is None:
       
   162             return []
       
   163         if not isinstance(value, list):
       
   164             value = [value]
       
   165         vartypes = poss_var_types(myvar, ovar, entity.kind(), solutions)
       
   166         return (v for v in value if v.kind() in vartypes)
       
   167 
       
   168     def complete_and_filter(self, solutions, results):
       
   169         myvar = self.rhs.name
       
   170         ovar = self.var.name
       
   171         rtype = self.rtype
       
   172         if self.schema.rschema(rtype).final:
       
   173             # should be detected by rql.stcheck: "Any C WHERE NOT X attr C" doesn't make sense
       
   174             #if self._not:
       
   175             #    raise NotImplementedError()
       
   176             for result in results:
       
   177                 result[myvar] = result[ovar].get('s_'+rtype)
       
   178         elif self.var.name in results[0]:
       
   179             if self.rhs.name in results[0]:
       
   180                 self.filter(solutions, results)
       
   181             else:
       
   182                 if self._not:
       
   183                     raise NotImplementedError()
       
   184                 for result in results[:]:
       
   185                     values = self._possible_values(myvar, ovar, result[ovar],
       
   186                                                    solutions, 's_')
       
   187                     expand_result(results, result, myvar, values, self._dsget)
       
   188         else:
       
   189             assert self.rhs.name in results[0]
       
   190             self.object_complete_and_filter(solutions, results)
       
   191 
       
   192     def filter(self, solutions, results):
       
   193         myvar = self.rhs.name
       
   194         ovar = self.var.name
       
   195         newsols = {}
       
   196         for result in results[:]:
       
   197             entity = result[ovar]
       
   198             key = entity.key()
       
   199             if not key in newsols:
       
   200                 values = self._possible_values(myvar, ovar, entity, solutions, 's_')
       
   201                 newsols[key] = frozenset(v for v in values)
       
   202             if self._not:
       
   203                 if result[myvar].key() in newsols[key]:
       
   204                     results.remove(result)
       
   205             elif not result[myvar].key() in newsols[key]:
       
   206                 results.remove(result)
       
   207 
       
   208     def object_complete_and_filter(self, solutions, results):
       
   209         if self._not:
       
   210             raise NotImplementedError()
       
   211         myvar = self.var.name
       
   212         ovar = self.rhs.name
       
   213         for result in results[:]:
       
   214             values = self._possible_values(myvar, ovar, result[ovar],
       
   215                                            solutions, 'o_')
       
   216             expand_result(results, result, myvar, values, self._dsget)
       
   217 
       
   218 
       
   219 class EidRestriction(Restriction):
       
   220     def __init__(self, rel, dsget):
       
   221         Restriction.__init__(self, rel)
       
   222         self._dsget = dsget
       
   223 
       
   224     def resolve(self, kwargs):
       
   225         value = self.rel.children[1].children[0].eval(kwargs)
       
   226         return self._dsget(value)
       
   227 
       
   228 
       
   229 class RelationRestriction(VariableSelection):
       
   230 
       
   231     def _get_value(self, fixed):
       
   232         return fixed[self.constraint_var].key()
       
   233 
       
   234     def fill_query(self, fixed, query, operator=None):
       
   235         restr = '%s%s %s' % (self._prefix, self.rtype, operator or self.operator)
       
   236         query[restr] = self._get_value(fixed)
       
   237 
       
   238     def resolve(self, solutions, fixed):
       
   239         if self.rtype == 'identity':
       
   240             if self._not:
       
   241                 raise NotImplementedError()
       
   242             return [fixed[self.constraint_var]]
       
   243         if self._not:
       
   244             return _resolve_not([self], solutions, fixed)
       
   245         return _resolve([self], solutions, fixed)
       
   246 
       
   247 
       
   248 class NotRelationRestriction(RelationRestriction):
       
   249 
       
   250     def _get_value(self, fixed):
       
   251         return None
       
   252 
       
   253     def resolve(self, solutions, fixed):
       
   254         if self.rtype == 'identity':
       
   255             raise NotImplementedError()
       
   256         return _resolve([self], solutions, fixed)
       
   257 
       
   258 
       
   259 class AttributeRestriction(RelationRestriction):
       
   260     supported_operators = ('=', '>', '>=', '<', '<=', 'ILIKE')
       
   261     def __init__(self, rel, kwargs):
       
   262         RelationRestriction.__init__(self, rel, None)
       
   263         value = self.rhs.eval(kwargs)
       
   264         self.value = value
       
   265         if self.operator == 'ILIKE':
       
   266             if value.startswith('%'):
       
   267                 raise NotImplementedError('LIKE is only supported for prefix search')
       
   268             if not value.endswith('%'):
       
   269                 raise NotImplementedError('LIKE is only supported for prefix search')
       
   270             self.operator = '>'
       
   271             self.value = value[:-1]
       
   272 
       
   273     def complete_and_filter(self, solutions, results):
       
   274         # check lhs var first in case this is a restriction
       
   275         assert self._not
       
   276         myvar, rtype, value = self.var.name, self.rtype, self.value
       
   277         for result in results[:]:
       
   278             if result[myvar].get('s_'+rtype) == value:
       
   279                 results.remove(result)
       
   280 
       
   281     def _get_value(self, fixed):
       
   282         return self.value
       
   283 
       
   284 
       
   285 class DateAttributeRestriction(AttributeRestriction):
       
   286     """just a thin layer on top af `AttributeRestriction` that
       
   287     tries to convert date strings such as in :
       
   288     Any X WHERE X creation_date >= '2008-03-04'
       
   289     """
       
   290     def __init__(self, rel, kwargs):
       
   291         super(DateAttributeRestriction, self).__init__(rel, kwargs)
       
   292         if isinstance(self.value, basestring):
       
   293 #             try:
       
   294             self.value = datetime.strptime(self.value, '%Y-%m-%d')
       
   295 #             except Exception, exc:
       
   296 #                 from logging import error
       
   297 #                 error('unable to parse date %s with format %%Y-%%m-%%d (exc=%s)', value, exc)
       
   298 
       
   299 
       
   300 class AttributeInRestriction(AttributeRestriction):
       
   301     def __init__(self, rel, kwargs):
       
   302         RelationRestriction.__init__(self, rel, None)
       
   303         values = []
       
   304         for c in self.rel.children[1].iget_nodes(nodes.Constant):
       
   305             values.append(c.eval(kwargs))
       
   306         self.value = values
       
   307 
       
   308     @property
       
   309     def operator(self):
       
   310         return 'in'
       
   311 
       
   312 
       
   313 class TypeRestriction(AttributeRestriction):
       
   314     def __init__(self, var):
       
   315         self.var = var
       
   316 
       
   317     def __repr__(self):
       
   318         return '<%s for %s>' % (self.__class__.__name__, self.var)
       
   319 
       
   320     def resolve(self, solutions, fixed):
       
   321         objs = []
       
   322         for etype in frozenset(etypes[self.var.name] for etypes in solutions):
       
   323             objs += Query(etype).Run()
       
   324         return objs
       
   325 
       
   326 
       
   327 def append_result(res, descr, i, j, value, etype):
       
   328     if value is not None:
       
   329         if isinstance(value, Text):
       
   330             value = unicode(value)
       
   331         elif isinstance(value, Blob):
       
   332             value = Binary(str(value))
       
   333     if j == 0:
       
   334         res.append([value])
       
   335         descr.append([etype])
       
   336     else:
       
   337         res[i].append(value)
       
   338         descr[i].append(etype)
       
   339 
       
   340 
       
   341 class ValueResolver(object):
       
   342     def __init__(self, functions, args, term):
       
   343         self.functions = functions
       
   344         self.args = args
       
   345         self.term = term
       
   346         self._solution = self.term.stmt.solutions[0]
       
   347 
       
   348     def compute(self, result):
       
   349         """return (entity type, value) to which self.term is evaluated according
       
   350         to the given result dictionnary and to query arguments (self.args)
       
   351         """
       
   352         return self.term.accept(self, result)
       
   353 
       
   354     def visit_function(self, node, result):
       
   355         args = tuple(n.accept(self, result)[1] for n in node.children)
       
   356         value = self.functions[node.name](*args)
       
   357         return node.get_type(self._solution, self.args), value
       
   358 
       
   359     def visit_variableref(self, node, result):
       
   360         value = result[node.name]
       
   361         try:
       
   362             etype = value.kind()
       
   363             value = str(value.key())
       
   364         except AttributeError:
       
   365             etype = self._solution[node.name]
       
   366         return etype, value
       
   367 
       
   368     def visit_constant(self, node, result):
       
   369         return node.get_type(kwargs=self.args), node.eval(self.args)
       
   370 
       
   371 
       
   372 class RQLInterpreter(object):
       
   373     """algorithm:
       
   374     1. visit the restriction clauses and collect restriction for each subject
       
   375        of a relation. Different restriction types are:
       
   376        * EidRestriction
       
   377        * AttributeRestriction
       
   378        * RelationRestriction
       
   379        * VariableSelection (not really a restriction)
       
   380        -> dictionary {<variable>: [restriction...], ...}
       
   381     2. resolve eid restrictions
       
   382     3. for each select in union:
       
   383            for each solution in select'solutions:
       
   384                1. resolve variables which have attribute restriction
       
   385                2. resolve relation restriction
       
   386                3. resolve selection and add to global results
       
   387     """
       
   388     def __init__(self, schema):
       
   389         self.schema = schema
       
   390         Restriction.schema = schema # yalta!
       
   391         self.rqlhelper = RQLHelper(schema, {'eid': etype_from_key})
       
   392         self._stored_proc = {'LOWER': lambda x: x.lower(),
       
   393                              'UPPER': lambda x: x.upper()}
       
   394         for cb in SQL_CONNECT_HOOKS.get('sqlite', []):
       
   395             cb(self)
       
   396 
       
   397     # emulate sqlite connection interface so we can reuse stored procedures
       
   398     def create_function(self, name, nbargs, func):
       
   399         self._stored_proc[name] = func
       
   400 
       
   401     def create_aggregate(self, name, nbargs, func):
       
   402         self._stored_proc[name] = func
       
   403 
       
   404 
       
   405     def execute(self, operation, parameters=None, eid_key=None, build_descr=True):
       
   406         rqlst = self.rqlhelper.parse(operation, annotate=True)
       
   407         try:
       
   408             self.rqlhelper.compute_solutions(rqlst, kwargs=parameters)
       
   409         except BadKeyError:
       
   410             results, description = [], []
       
   411         else:
       
   412             results, description = self.interpret(rqlst, parameters)
       
   413         return ResultSet(results, operation, parameters, description, rqlst=rqlst)
       
   414 
       
   415     def interpret(self, node, kwargs, dsget=None):
       
   416         if dsget is None:
       
   417             self._dsget = Get
       
   418         else:
       
   419             self._dsget = dsget
       
   420         try:
       
   421             return node.accept(self, kwargs)
       
   422         except NotImplementedError:
       
   423             self.critical('support for query not implemented: %s', node)
       
   424             raise
       
   425 
       
   426     def visit_union(self, node, kwargs):
       
   427         results, description = [], []
       
   428         extra = {'kwargs': kwargs}
       
   429         for child in node.children:
       
   430             pres, pdescr = self.visit_select(child, extra)
       
   431             results += pres
       
   432             description += pdescr
       
   433         return results, description
       
   434 
       
   435     def visit_select(self, node, extra):
       
   436         constraints = {}
       
   437         if node.where is not None:
       
   438             node.where.accept(self, constraints, extra)
       
   439         fixed, toresolve, postresolve, postfilters = {}, {}, {}, []
       
   440         # extract NOT filters
       
   441         for vname, restrictions in constraints.items():
       
   442             for restr in restrictions[:]:
       
   443                 if isinstance(restr, AttributeRestriction) and restr._not:
       
   444                     postfilters.append(restr)
       
   445                     restrictions.remove(restr)
       
   446                     if not restrictions:
       
   447                         del constraints[vname]
       
   448         # add TypeRestriction for variable which have no restrictions at all
       
   449         for varname, var in node.defined_vars.iteritems():
       
   450             if not varname in constraints:
       
   451                 constraints[varname] = [TypeRestriction(var)]
       
   452         #print node, constraints
       
   453         # compute eid restrictions
       
   454         kwargs = extra['kwargs']
       
   455         for varname, restrictions in constraints.iteritems():
       
   456             for restr in restrictions[:]:
       
   457                 if isinstance(restr, EidRestriction):
       
   458                     assert not varname in fixed
       
   459                     try:
       
   460                         value = restr.resolve(kwargs)
       
   461                         fixed[varname] = value
       
   462                     except EntityNotFoundError:
       
   463                         return [], []
       
   464                     restrictions.remove(restr)
       
   465         #print 'fixed', fixed.keys()
       
   466         # combine remaining restrictions
       
   467         for varname, restrictions in constraints.iteritems():
       
   468             for restr in restrictions:
       
   469                 if isinstance(restr, AttributeRestriction):
       
   470                     toresolve.setdefault(varname, []).append(restr)
       
   471                 elif isinstance(restr, NotRelationRestriction) or (
       
   472                     isinstance(restr, RelationRestriction) and
       
   473                     not restr.searched_var in fixed and restr.constraint_var in fixed):
       
   474                     toresolve.setdefault(varname, []).append(restr)
       
   475                 else:
       
   476                     postresolve.setdefault(varname, []).append(restr)
       
   477             try:
       
   478                 if len(toresolve[varname]) > 1:
       
   479                     toresolve[varname] = MultipleRestriction(toresolve[varname])
       
   480                 else:
       
   481                     toresolve[varname] = toresolve[varname][0]
       
   482             except KeyError:
       
   483                 pass
       
   484         #print 'toresolve %s' % toresolve
       
   485         #print 'postresolve %s' % postresolve
       
   486         # resolve additional restrictions
       
   487         if fixed:
       
   488             partres = [fixed.copy()]
       
   489         else:
       
   490             partres = []
       
   491         for varname, restr in toresolve.iteritems():
       
   492             varpartres = partres[:]
       
   493             try:
       
   494                 values = tuple(restr.resolve(node.solutions, fixed))
       
   495             except EidMismatch, ex:
       
   496                 varname = ex.varname
       
   497                 value = ex.value
       
   498                 partres = [res for res in partres if res[varname] != value]
       
   499                 if partres:
       
   500                     continue
       
   501                 # some join failed, no possible results
       
   502                 return [], []
       
   503             if not values:
       
   504                 # some join failed, no possible results
       
   505                 return [], []
       
   506             if not varpartres:
       
   507                 # init results
       
   508                 for value in values:
       
   509                     partres.append({varname: value})
       
   510             elif not varname in partres[0]:
       
   511                 # cartesian product
       
   512                 for res in partres:
       
   513                     res[varname] = values[0]
       
   514                 for res in partres[:]:
       
   515                     for value in values[1:]:
       
   516                         res = res.copy()
       
   517                         res[varname] = value
       
   518                         partres.append(res)
       
   519             else:
       
   520                 # union
       
   521                 for res in varpartres:
       
   522                     for value in values:
       
   523                         res = res.copy()
       
   524                         res[varname] = value
       
   525                         partres.append(res)
       
   526         #print 'partres', len(partres)
       
   527         #print partres
       
   528         # Note: don't check for empty partres since constant selection may still
       
   529         # produce result at this point
       
   530         # sort to get RelationRestriction before AttributeSelection
       
   531         restrictions = sorted((restr for restrictions in postresolve.itervalues()
       
   532                                for restr in restrictions),
       
   533                               key=lambda x: not isinstance(x, RelationRestriction))
       
   534         # compute stuff not doable in the previous step using datastore queries
       
   535         for restr in restrictions + postfilters:
       
   536             restr.complete_and_filter(node.solutions, partres)
       
   537             if not partres:
       
   538                 # some join failed, no possible results
       
   539                 return [], []
       
   540         if extra.pop('has_exists', False):
       
   541             # remove potential duplicates introduced by exists
       
   542             toremovevars = [v.name for v in node.defined_vars.itervalues()
       
   543                             if not v.scope is node]
       
   544             if toremovevars:
       
   545                 newpartres = []
       
   546                 for result in partres:
       
   547                     for var in toremovevars:
       
   548                         del result[var]
       
   549                     if not result in newpartres:
       
   550                         newpartres.append(result)
       
   551                 if not newpartres:
       
   552                     # some join failed, no possible results
       
   553                     return [], []
       
   554                 partres = newpartres
       
   555         if node.orderby:
       
   556             for sortterm in reversed(node.orderby):
       
   557                 resolver = ValueResolver(self._stored_proc, kwargs, sortterm.term)
       
   558                 partres.sort(reverse=not sortterm.asc,
       
   559                              key=lambda x: resolver.compute(x)[1])
       
   560         if partres:
       
   561             if node.offset:
       
   562                 partres = partres[node.offset:]
       
   563             if node.limit:
       
   564                 partres = partres[:node.limit]
       
   565             if not partres:
       
   566                 return [], []
       
   567         #print 'completed partres', _print_results(partres)
       
   568         # compute results
       
   569         res, descr = [], []
       
   570         for j, term in enumerate(node.selection):
       
   571             resolver = ValueResolver(self._stored_proc, kwargs, term)
       
   572             if not partres:
       
   573                 etype, value = resolver.compute({})
       
   574                 # only constant selected
       
   575                 if not res:
       
   576                     res.append([])
       
   577                     descr.append([])
       
   578                     res[0].append(value)
       
   579                     descr[0].append(etype)
       
   580             else:
       
   581                 for i, sol in enumerate(partres):
       
   582                     etype, value = resolver.compute(sol)
       
   583                     append_result(res, descr, i, j, value, etype)
       
   584         #print '--------->', res
       
   585         return res, descr
       
   586 
       
   587     def visit_and(self, node, constraints, extra):
       
   588         for child in node.children:
       
   589             child.accept(self, constraints, extra)
       
   590     def visit_exists(self, node, constraints, extra):
       
   591         extra['has_exists'] = True
       
   592         self.visit_and(node, constraints, extra)
       
   593 
       
   594     def visit_not(self, node, constraints, extra):
       
   595         for child in node.children:
       
   596             child.accept(self, constraints, extra)
       
   597         try:
       
   598             extra.pop(node)
       
   599         except KeyError:
       
   600             raise NotImplementedError()
       
   601 
       
   602     def visit_relation(self, node, constraints, extra):
       
   603         if node.is_types_restriction():
       
   604             return
       
   605         rschema = self.schema.rschema(node.r_type)
       
   606         neged = node.neged(strict=True)
       
   607         if neged:
       
   608             # ok, we *may* process this Not node (not implemented error will be
       
   609             # raised later if we can't)
       
   610             extra[node.parent] = True
       
   611         if rschema.final:
       
   612             self._visit_final_relation(rschema, node, constraints, extra)
       
   613         elif neged:
       
   614             self._visit_non_final_neged_relation(rschema, node, constraints)
       
   615         else:
       
   616             self._visit_non_final_relation(rschema, node, constraints)
       
   617 
       
   618     def _visit_non_final_relation(self, rschema, node, constraints, not_=False):
       
   619         lhs, rhs = node.get_variable_parts()
       
   620         for v1, v2, prefix in ((lhs, rhs, 's'), (rhs, lhs, 'o')):
       
   621             #if not_:
       
   622             nbrels = len(v2.variable.stinfo['relations'])
       
   623             #else:
       
   624             #    nbrels = len(v2.variable.stinfo['relations']) - len(v2.variable.stinfo['uidrels'])
       
   625             if nbrels > 1:
       
   626                 constraints.setdefault(v1.name, []).append(
       
   627                     RelationRestriction(node, self._dsget, prefix))
       
   628                 # just init an empty list for v2 variable to avoid a
       
   629                 # TypeRestriction being added for it
       
   630                 constraints.setdefault(v2.name, [])
       
   631                 break
       
   632         else:
       
   633             constraints.setdefault(rhs.name, []).append(
       
   634                 VariableSelection(node, self._dsget, 's'))
       
   635 
       
   636     def _visit_non_final_neged_relation(self, rschema, node, constraints):
       
   637         lhs, rhs = node.get_variable_parts()
       
   638         for v1, v2, prefix in ((lhs, rhs, 's'), (rhs, lhs, 'o')):
       
   639             stinfo = v2.variable.stinfo
       
   640             if not stinfo['selected'] and len(stinfo['relations']) == 1:
       
   641                 constraints.setdefault(v1.name, []).append(
       
   642                     NotRelationRestriction(node, self._dsget, prefix))
       
   643                 constraints.setdefault(v2.name, [])
       
   644                 break
       
   645         else:
       
   646             self._visit_non_final_relation(rschema, node, constraints, True)
       
   647 
       
   648     def _visit_final_relation(self, rschema, node, constraints, extra):
       
   649         varname = node.children[0].name
       
   650         if rschema.type == 'eid':
       
   651             constraints.setdefault(varname, []).append(
       
   652                 EidRestriction(node, self._dsget))
       
   653         else:
       
   654             rhs = node.children[1].children[0]
       
   655             if isinstance(rhs, nodes.VariableRef):
       
   656                 constraints.setdefault(rhs.name, []).append(
       
   657                     VariableSelection(node, self._dsget))
       
   658             elif isinstance(rhs, nodes.Constant):
       
   659                 if rschema.objects()[0] in ('Datetime', 'Date'): # XXX
       
   660                     constraints.setdefault(varname, []).append(
       
   661                         DateAttributeRestriction(node, extra['kwargs']))
       
   662                 else:
       
   663                     constraints.setdefault(varname, []).append(
       
   664                         AttributeRestriction(node, extra['kwargs']))
       
   665             elif isinstance(rhs, nodes.Function) and rhs.name == 'IN':
       
   666                 constraints.setdefault(varname, []).append(
       
   667                     AttributeInRestriction(node, extra['kwargs']))
       
   668             else:
       
   669                 raise NotImplementedError()
       
   670 
       
   671     def _not_implemented(self, *args, **kwargs):
       
   672         raise NotImplementedError()
       
   673 
       
   674     visit_or = _not_implemented
       
   675     # shouldn't occurs
       
   676     visit_set = _not_implemented
       
   677     visit_insert = _not_implemented
       
   678     visit_delete = _not_implemented
       
   679 
       
   680 
       
   681 from logging import getLogger
       
   682 from cubicweb import set_log_methods
       
   683 set_log_methods(RQLInterpreter, getLogger('cubicweb.goa.rqlinterpreter'))
       
   684 set_log_methods(Restriction, getLogger('cubicweb.goa.rqlinterpreter'))