4d558bdb619e680e2be2caec63be6110386561a2
[odoo/odoo.git] / openerp / osv / expression.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 ##############################################################################
4 #
5 #    OpenERP, Open Source Management Solution
6 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU Affero General Public License as
10 #    published by the Free Software Foundation, either version 3 of the
11 #    License, or (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU Affero General Public License for more details.
17 #
18 #    You should have received a copy of the GNU Affero General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 """ Domain expression processing
24
25 The main duty of this module is to compile a domain expression into a SQL
26 query. A lot of things should be documented here, but as a first step in the
27 right direction, some tests in test_osv_expression.yml might give you some
28 additional information.
29
30 For legacy reasons, a domain uses an inconsistent two-levels abstract syntax
31 (domains are regular Python data structures). At the first level, a domain
32 is an expression made of terms (sometimes called leaves) and (domain) operators
33 used in prefix notation. The available operators at this level are '!', '&',
34 and '|'. '!' is a unary 'not', '&' is a binary 'and', and '|' is a binary 'or'.
35 For instance, here is a possible domain. (<term> stands for an arbitrary term,
36 more on this later.)
37
38     ['&', '!', <term1>, '|', <term2>, <term3>]
39
40 It is equivalent to this pseudo code using infix notation:
41
42     (not <term1>) and (<term2> or <term3>)
43
44 The second level of syntax deals with the term representation. A term is
45 a triple of the form (left, operator, right). That is, a term uses an infix
46 notation, and the available operators, and possible left and right operands
47 differ with those of the previous level. Here is a possible term:
48
49     ('company_id.name', '=', 'OpenERP')
50
51 The left and right operand don't have the same possible values. The left
52 operand is field name (related to the model for which the domain applies).
53 Actually, the field name can use the dot-notation to traverse relationships.
54 The right operand is a Python value whose type should match the used operator
55 and field type. In the above example, a string is used because the name field
56 of a company has type string, and because we use the '=' operator. When
57 appropriate, a 'in' operator can be used, and thus the right operand should be
58 a list.
59
60 Note: the non-uniform syntax could have been more uniform, but this would hide
61 an important limitation of the domain syntax. Say that the term representation
62 was ['=', 'company_id.name', 'OpenERP']. Used in a complete domain, this would
63 look like:
64
65   ['!', ['=', 'company_id.name', 'OpenERP']]
66
67 and you would be tempted to believe something like this would be possible:
68
69   ['!', ['=', 'company_id.name', ['&', ..., ...]]]
70
71 That is, a domain could be a valid operand. But this is not the case. A domain
72 is really limited to a two-level nature, and can not takes a recursive form: a
73 domain is not a valid second-level operand.
74
75 Unaccent - Accent-insensitive search
76
77 OpenERP will use the SQL function 'unaccent' when available for the 'ilike' and
78 'not ilike' operators, and enabled in the configuration.
79 Normally the 'unaccent' function is obtained from the PostgreSQL 'unaccent'
80 contrib module[0]. 
81
82
83 ..todo: The following explanation should be moved in some external installation
84         guide 
85
86 The steps to install the module might differ on specific PostgreSQL versions.
87 We give here some instruction for PostgreSQL 9.x on a Ubuntu system.
88
89 Ubuntu doesn't come yet with PostgreSQL 9.x, so an alternive package source
90 is used. We use Martin Pitt's PPA available at ppa:pitti/postgresql[1]. See
91 [2] for instructions. Basically:
92
93     > sudo add-apt-repository ppa:pitti/postgresql
94     > sudo apt-get update
95
96 Once the package list is up-to-date, you have to install PostgreSQL 9.0 and
97 its contrib modules.
98
99     > sudo apt-get install postgresql-9.0 postgresql-contrib-9.0
100
101 When you want to enable unaccent on some database:
102
103     > psql9 <database> -f /usr/share/postgresql/9.0/contrib/unaccent.sql
104
105 Here 'psql9' is an alias for the newly installed PostgreSQL 9.0 tool, together
106 with the correct port if necessary (for instance if PostgreSQL 8.4 is running
107 on 5432). (Other aliases can be used for createdb and dropdb.)
108
109     > alias psql9='/usr/lib/postgresql/9.0/bin/psql -p 5433'
110
111 You can check unaccent is working:
112
113     > psql9 <database> -c"select unaccent('hélène')"
114
115 Finally, to instruct OpenERP to really use the unaccent function, you have to
116 start the server specifying the --unaccent flag.
117
118 [0] http://developer.postgresql.org/pgdocs/postgres/unaccent.html
119 [1] https://launchpad.net/~pitti/+archive/postgresql
120 [2] https://launchpad.net/+help/soyuz/ppa-sources-list.html
121
122 """
123
124 import logging
125
126 from openerp.tools import flatten, reverse_enumerate
127 import fields
128 import openerp.modules
129
130 #.apidoc title: Domain Expressions
131
132 # Domain operators.
133 NOT_OPERATOR = '!'
134 OR_OPERATOR = '|'
135 AND_OPERATOR = '&'
136 DOMAIN_OPERATORS = (NOT_OPERATOR, OR_OPERATOR, AND_OPERATOR)
137
138 # List of available term operators. It is also possible to use the '<>'
139 # operator, which is strictly the same as '!='; the later should be prefered
140 # for consistency. This list doesn't contain '<>' as it is simpified to '!='
141 # by the normalize_operator() function (so later part of the code deals with
142 # only one representation).
143 # An internal (i.e. not available to the user) 'inselect' operator is also
144 # used. In this case its right operand has the form (subselect, params).
145 TERM_OPERATORS = ('=', '!=', '<=', '<', '>', '>=', '=?', '=like', '=ilike',
146                   'like', 'not like', 'ilike', 'not ilike', 'in', 'not in',
147                   'child_of')
148
149 # A subset of the above operators, with a 'negative' semantic. When the
150 # expressions 'in NEGATIVE_TERM_OPERATORS' or 'not in NEGATIVE_TERM_OPERATORS' are used in the code
151 # below, this doesn't necessarily mean that any of those NEGATIVE_TERM_OPERATORS is
152 # legal in the processed term.
153 NEGATIVE_TERM_OPERATORS = ('!=', 'not like', 'not ilike', 'not in')
154
155 TRUE_LEAF = (1, '=', 1)
156 FALSE_LEAF = (0, '=', 1)
157
158 TRUE_DOMAIN = [TRUE_LEAF]
159 FALSE_DOMAIN = [FALSE_LEAF]
160
161 _logger = logging.getLogger('expression')
162
163 def normalize(domain):
164     """Returns a normalized version of ``domain_expr``, where all implicit '&' operators
165        have been made explicit. One property of normalized domain expressions is that they
166        can be easily combined together as if they were single domain components.
167     """
168     assert isinstance(domain, (list, tuple)), "Domains to normalize must have a 'domain' form: a list or tuple of domain components"
169     if not domain:
170         return TRUE_DOMAIN
171     result = []
172     expected = 1                            # expected number of expressions
173     op_arity = {NOT_OPERATOR: 1, AND_OPERATOR: 2, OR_OPERATOR: 2}
174     for token in domain:
175         if expected == 0:                   # more than expected, like in [A, B]
176             result[0:0] = [AND_OPERATOR]             # put an extra '&' in front
177             expected = 1
178         result.append(token)
179         if isinstance(token, (list, tuple)): # domain term
180             expected -= 1
181         else:
182             expected += op_arity.get(token, 0) - 1
183     assert expected == 0
184     return result
185
186 def combine(operator, unit, zero, domains):
187     """Returns a new domain expression where all domain components from ``domains``
188        have been added together using the binary operator ``operator``. The given
189        domains must be normalized.
190
191        :param unit: the identity element of the domains "set" with regard to the operation
192                     performed by ``operator``, i.e the domain component ``i`` which, when
193                     combined with any domain ``x`` via ``operator``, yields ``x``. 
194                     E.g. [(1,'=',1)] is the typical unit for AND_OPERATOR: adding it
195                     to any domain component gives the same domain.
196        :param zero: the absorbing element of the domains "set" with regard to the operation
197                     performed by ``operator``, i.e the domain component ``z`` which, when
198                     combined with any domain ``x`` via ``operator``, yields ``z``. 
199                     E.g. [(1,'=',1)] is the typical zero for OR_OPERATOR: as soon as
200                     you see it in a domain component the resulting domain is the zero.
201        :param domains: a list of normalized domains.
202     """
203     result = []
204     count = 0
205     for domain in domains:
206         if domain == unit:
207             continue
208         if domain == zero:
209             return zero
210         if domain:
211             result += domain
212             count += 1
213     result = [operator] * (count - 1) + result
214     return result
215
216 def AND(domains):
217     """AND([D1,D2,...]) returns a domain representing D1 and D2 and ... """
218     return combine(AND_OPERATOR, TRUE_DOMAIN, FALSE_DOMAIN, domains)
219
220 def OR(domains):
221     """OR([D1,D2,...]) returns a domain representing D1 or D2 or ... """
222     return combine(OR_OPERATOR, FALSE_DOMAIN, TRUE_DOMAIN, domains)
223
224 def is_operator(element):
225     """Test whether an object is a valid domain operator. """
226     return isinstance(element, basestring) and element in DOMAIN_OPERATORS
227
228 # TODO change the share wizard to use this function.
229 def is_leaf(element, internal=False):
230     """ Test whether an object is a valid domain term.
231
232     :param internal: allow or not the 'inselect' internal operator in the term.
233     This normally should be always left to False.
234     """
235     INTERNAL_OPS = TERM_OPERATORS + ('inselect',)
236     return (isinstance(element, tuple) or isinstance(element, list)) \
237        and len(element) == 3 \
238        and (((not internal) and element[1] in TERM_OPERATORS + ('<>',)) \
239             or (internal and element[1] in INTERNAL_OPS + ('<>',)))
240
241 def normalize_leaf(left, operator, right):
242     """ Change a term's operator to some canonical form, simplifying later
243     processing.
244     """
245     original = operator
246     operator = operator.lower()
247     if operator == '<>':
248         operator = '!='
249     if isinstance(right, bool) and operator in ('in', 'not in'):
250         _logger.warning("The domain term '%s' should use the '=' or '!=' operator." % ((left, original, right),))
251         operator = '=' if operator == 'in' else '!='
252     if isinstance(right, (list, tuple)) and operator in ('=', '!='):
253         _logger.warning("The domain term '%s' should use the 'in' or 'not in' operator." % ((left, original, right),))
254         operator = 'in' if operator == '=' else 'not in'
255     return left, operator, right
256
257 def distribute_not(domain):
258     """ Distribute any '!' domain operators found inside a normalized domain.
259
260     Because we don't use SQL semantic for processing a 'left not in right'
261     query (i.e. our 'not in' is not simply translated to a SQL 'not in'),
262     it means that a '! left in right' can not be simply processed
263     by __leaf_to_sql by first emitting code for 'left in right' then wrapping
264     the result with 'not (...)', as it would result in a 'not in' at the SQL
265     level.
266
267     This function is thus responsible for pushing any '!' domain operators
268     inside the terms themselves. For example::
269
270          ['!','&',('user_id','=',4),('partner_id','in',[1,2])]
271             will be turned into:
272          ['|',('user_id','!=',4),('partner_id','not in',[1,2])]
273
274     """
275     def negate(leaf):
276         """Negates and returns a single domain leaf term,
277         using the opposite operator if possible"""
278         left, operator, right = leaf
279         mapping = {
280             '<': '>=',
281             '>': '<=',
282             '<=': '>',
283             '>=': '<',
284             '=': '!=',
285             '!=': '=',
286         }
287         if operator in ('in', 'like', 'ilike'):
288             operator = 'not ' + operator
289             return [(left, operator, right)]
290         if operator in ('not in', 'not like', 'not ilike'):
291             operator = operator[4:]
292             return [(left, operator, right)]
293         if operator in mapping:
294             operator = mapping[operator]
295             return [(left, operator, right)]
296         return [NOT_OPERATOR, (left, operator, right)]
297     def distribute_negate(domain):
298         """Negate the domain ``subtree`` rooted at domain[0],
299         leaving the rest of the domain intact, and return
300         (negated_subtree, untouched_domain_rest)
301         """
302         if is_leaf(domain[0]):
303             return negate(domain[0]), domain[1:]
304         if domain[0] == AND_OPERATOR:
305             done1, todo1 = distribute_negate(domain[1:])
306             done2, todo2 = distribute_negate(todo1)
307             return [OR_OPERATOR] + done1 + done2, todo2
308         if domain[0] == OR_OPERATOR:
309             done1, todo1 = distribute_negate(domain[1:])
310             done2, todo2 = distribute_negate(todo1)
311             return [AND_OPERATOR] + done1 + done2, todo2
312     if not domain:
313         return []
314     if domain[0] != NOT_OPERATOR:
315         return [domain[0]] + distribute_not(domain[1:])
316     if domain[0] == NOT_OPERATOR:
317         done, todo = distribute_negate(domain[1:])
318         return done + distribute_not(todo)
319
320 def select_from_where(cr, select_field, from_table, where_field, where_ids, where_operator):
321     # todo: merge into parent query as sub-query
322     res = []
323     if where_ids:
324         if where_operator in ['<','>','>=','<=']:
325             cr.execute('SELECT "%s" FROM "%s" WHERE "%s" %s %%s' % \
326                 (select_field, from_table, where_field, where_operator),
327                 (where_ids[0],)) # TODO shouldn't this be min/max(where_ids) ?
328             res = [r[0] for r in cr.fetchall()]
329         else: # TODO where_operator is supposed to be 'in'? It is called with child_of...
330             for i in range(0, len(where_ids), cr.IN_MAX):
331                 subids = where_ids[i:i+cr.IN_MAX]
332                 cr.execute('SELECT "%s" FROM "%s" WHERE "%s" IN %%s' % \
333                     (select_field, from_table, where_field), (tuple(subids),))
334                 res.extend([r[0] for r in cr.fetchall()])
335     return res
336
337 def select_distinct_from_where_not_null(cr, select_field, from_table):
338     cr.execute('SELECT distinct("%s") FROM "%s" where "%s" is not null' % \
339                (select_field, from_table, select_field))
340     return [r[0] for r in cr.fetchall()]
341
342 class expression(object):
343     """
344     parse a domain expression
345     use a real polish notation
346     leafs are still in a ('foo', '=', 'bar') format
347     For more info: http://christophe-simonis-at-tiny.blogspot.com/2008/08/new-new-domain-notation.html
348     """
349
350     def __init__(self, cr, uid, exp, table, context):
351         self.has_unaccent = openerp.modules.registry.RegistryManager.get(cr.dbname).has_unaccent
352         self.__field_tables = {}  # used to store the table to use for the sql generation. key = index of the leaf
353         self.__all_tables = set()
354         self.__joins = []
355         self.__main_table = None # 'root' table. set by parse()
356         # assign self.__exp with the normalized, parsed domain.
357         self.parse(cr, uid, distribute_not(normalize(exp)), table, context)
358
359     # TODO used only for osv_memory
360     @property
361     def exp(self):
362         return self.__exp[:]
363
364     def parse(self, cr, uid, exp, table, context):
365         """ transform the leaves of the expression """
366         self.__exp = exp
367         self.__main_table = table
368         self.__all_tables.add(table)
369
370         def child_of_domain(left, ids, left_model, parent=None, prefix=''):
371             """Returns a domain implementing the child_of operator for [(left,child_of,ids)],
372             either as a range using the parent_left/right tree lookup fields (when available),
373             or as an expanded [(left,in,child_ids)]"""
374             if left_model._parent_store and (not left_model.pool._init):
375                 # TODO: Improve where joins are implemented for many with '.', replace by:
376                 # doms += ['&',(prefix+'.parent_left','<',o.parent_right),(prefix+'.parent_left','>=',o.parent_left)]
377                 doms = []
378                 for o in left_model.browse(cr, uid, ids, context=context):
379                     if doms:
380                         doms.insert(0, OR_OPERATOR)
381                     doms += [AND_OPERATOR, ('parent_left', '<', o.parent_right), ('parent_left', '>=', o.parent_left)]
382                 if prefix:
383                     return [(left, 'in', left_model.search(cr, uid, doms, context=context))]
384                 return doms
385             else:
386                 def recursive_children(ids, model, parent_field):
387                     if not ids:
388                         return []
389                     ids2 = model.search(cr, uid, [(parent_field, 'in', ids)], context=context)
390                     return ids + recursive_children(ids2, model, parent_field)
391                 return [(left, 'in', recursive_children(ids, left_model, parent or left_model._parent_name))]
392
393         def to_ids(value, field_obj):
394             """ Normalize a single id, or a string, or a list of ids to a list of ids.
395             """
396             if isinstance(value, basestring):
397                 return [x[0] for x in field_obj.name_search(cr, uid, value, [], 'ilike', context=context, limit=None)]
398             elif isinstance(value, (int, long)):
399                 return [value]
400             else:
401                 return list(value)
402
403         i = -1
404         while i + 1<len(self.__exp):
405             i += 1
406             e = self.__exp[i]
407             if is_operator(e) or e == TRUE_LEAF or e == FALSE_LEAF:
408                 continue
409
410             # check if the expression is valid
411             if not is_leaf(e):
412                 raise ValueError('Bad domain expression: %r, %r is not a valid term.' % (exp, e))
413
414             # normalize the leaf's operator
415             e = normalize_leaf(*e)
416             self.__exp[i] = e
417             left, operator, right = e
418
419             working_table = table # The table containing the field (the name provided in the left operand)
420             fargs = left.split('.', 1)
421
422             # If the field is _inherits'd, search for the working_table,
423             # and extract the field.
424             if fargs[0] in table._inherit_fields:
425                 while True:
426                     field = working_table._columns.get(fargs[0])
427                     if field:
428                         self.__field_tables[i] = working_table
429                         break
430                     next_table = working_table.pool.get(working_table._inherit_fields[fargs[0]][0])
431                     if next_table not in self.__all_tables:
432                         self.__joins.append('%s."%s"=%s."%s"' % (next_table._table, 'id', working_table._table, working_table._inherits[next_table._name]))
433                         self.__all_tables.add(next_table)
434                     working_table = next_table
435             # Or (try to) directly extract the field.
436             else:
437                 field = working_table._columns.get(fargs[0])
438
439             if not field:
440                 if left == 'id' and operator == 'child_of':
441                     ids2 = to_ids(right, table)
442                     dom = child_of_domain(left, ids2, working_table)
443                     self.__exp = self.__exp[:i] + dom + self.__exp[i+1:]
444                 continue
445
446             field_obj = table.pool.get(field._obj)
447             if len(fargs) > 1:
448                 if field._type == 'many2one':
449                     right = field_obj.search(cr, uid, [(fargs[1], operator, right)], context=context)
450                     self.__exp[i] = (fargs[0], 'in', right)
451                 # Making search easier when there is a left operand as field.o2m or field.m2m
452                 if field._type in ['many2many', 'one2many']:
453                     right = field_obj.search(cr, uid, [(fargs[1], operator, right)], context=context)
454                     right1 = table.search(cr, uid, [(fargs[0], 'in', right)], context=context)
455                     self.__exp[i] = ('id', 'in', right1)
456
457                 if not isinstance(field, fields.property):
458                     continue
459
460             if field._properties and not field.store:
461                 # this is a function field that is not stored
462                 if not field._fnct_search:
463                     # the function field doesn't provide a search function and doesn't store
464                     # values in the database, so we must ignore it : we generate a dummy leaf
465                     self.__exp[i] = TRUE_LEAF
466                 else:
467                     subexp = field.search(cr, uid, table, left, [self.__exp[i]], context=context)
468                     if not subexp:
469                         self.__exp[i] = TRUE_LEAF
470                     else:
471                         # we assume that the expression is valid
472                         # we create a dummy leaf for forcing the parsing of the resulting expression
473                         self.__exp[i] = AND_OPERATOR
474                         self.__exp.insert(i + 1, TRUE_LEAF)
475                         for j, se in enumerate(subexp):
476                             self.__exp.insert(i + 2 + j, se)
477             # else, the value of the field is store in the database, so we search on it
478
479             elif field._type == 'one2many':
480                 # Applying recursivity on field(one2many)
481                 if operator == 'child_of':
482                     ids2 = to_ids(right, field_obj)
483                     if field._obj != working_table._name:
484                         dom = child_of_domain(left, ids2, field_obj, prefix=field._obj)
485                     else:
486                         dom = child_of_domain('id', ids2, working_table, parent=left)
487                     self.__exp = self.__exp[:i] + dom + self.__exp[i+1:]
488
489                 else:
490                     call_null = True
491
492                     if right is not False:
493                         if isinstance(right, basestring):
494                             ids2 = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator, context=context, limit=None)]
495                             if ids2:
496                                 operator = 'in'
497                         else:
498                             if not isinstance(right, list):
499                                 ids2 = [right]
500                             else:
501                                 ids2 = right
502                         if not ids2:
503                             if operator in ['like','ilike','in','=']:
504                                 #no result found with given search criteria
505                                 call_null = False
506                                 self.__exp[i] = FALSE_LEAF
507                         else:
508                             call_null = False
509                             o2m_op = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in'
510                             self.__exp[i] = ('id', o2m_op, select_from_where(cr, field._fields_id, field_obj._table, 'id', ids2, operator))
511
512                     if call_null:
513                         o2m_op = 'in' if operator in NEGATIVE_TERM_OPERATORS else 'not in'
514                         self.__exp[i] = ('id', o2m_op, select_distinct_from_where_not_null(cr, field._fields_id, field_obj._table))
515
516             elif field._type == 'many2many':
517                 #FIXME
518                 if operator == 'child_of':
519                     def _rec_convert(ids):
520                         if field_obj == table:
521                             return ids
522                         return select_from_where(cr, field._id1, field._rel, field._id2, ids, operator)
523
524                     ids2 = to_ids(right, field_obj)
525                     dom = child_of_domain('id', ids2, field_obj)
526                     ids2 = field_obj.search(cr, uid, dom, context=context)
527                     self.__exp[i] = ('id', 'in', _rec_convert(ids2))
528                 else:
529                     call_null_m2m = True
530                     if right is not False:
531                         if isinstance(right, basestring):
532                             res_ids = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator, context=context)]
533                             if res_ids:
534                                 operator = 'in'
535                         else:
536                             if not isinstance(right, list):
537                                 res_ids = [right]
538                             else:
539                                 res_ids = right
540                         if not res_ids:
541                             if operator in ['like','ilike','in','=']:
542                                 #no result found with given search criteria
543                                 call_null_m2m = False
544                                 self.__exp[i] = FALSE_LEAF
545                             else:
546                                 operator = 'in' # operator changed because ids are directly related to main object
547                         else:
548                             call_null_m2m = False
549                             m2m_op = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in'
550                             self.__exp[i] = ('id', m2m_op, select_from_where(cr, field._id1, field._rel, field._id2, res_ids, operator) or [0])
551
552                     if call_null_m2m:
553                         m2m_op = 'in' if operator in NEGATIVE_TERM_OPERATORS else 'not in'
554                         self.__exp[i] = ('id', m2m_op, select_distinct_from_where_not_null(cr, field._id1, field._rel))
555
556             elif field._type == 'many2one':
557                 if operator == 'child_of':
558                     ids2 = to_ids(right, field_obj)
559                     if field._obj != working_table._name:
560                         dom = child_of_domain(left, ids2, field_obj, prefix=field._obj)
561                     else:
562                         dom = child_of_domain('id', ids2, working_table, parent=left)
563                     self.__exp = self.__exp[:i] + dom + self.__exp[i+1:]
564                 else:
565                     def _get_expression(field_obj, cr, uid, left, right, operator, context=None):
566                         if context is None:
567                             context = {}
568                         c = context.copy()
569                         c['active_test'] = False
570                         #Special treatment to ill-formed domains
571                         operator = ( operator in ['<','>','<=','>='] ) and 'in' or operator
572
573                         dict_op = {'not in':'!=','in':'=','=':'in','!=':'not in'}
574                         if isinstance(right, tuple):
575                             right = list(right)
576                         if (not isinstance(right, list)) and operator in ['not in','in']:
577                             operator = dict_op[operator]
578                         elif isinstance(right, list) and operator in ['!=','=']: #for domain (FIELD,'=',['value1','value2'])
579                             operator = dict_op[operator]
580                         res_ids = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator, limit=None, context=c)]
581                         if operator in NEGATIVE_TERM_OPERATORS:
582                             res_ids.append(False) # TODO this should not be appended if False was in 'right'
583                         return (left, 'in', res_ids)
584
585                     m2o_str = False
586                     if right:
587                         if isinstance(right, basestring): # and not isinstance(field, fields.related):
588                             m2o_str = True
589                         elif isinstance(right, (list, tuple)):
590                             m2o_str = True
591                             for ele in right:
592                                 if not isinstance(ele, basestring):
593                                     m2o_str = False
594                                     break
595                         if m2o_str:
596                             self.__exp[i] = _get_expression(field_obj, cr, uid, left, right, operator, context=context)
597                     elif right == []:
598                         pass # Handled by __leaf_to_sql().
599                     else: # right is False
600                         pass # Handled by __leaf_to_sql().
601
602             else:
603                 # other field type
604                 # add the time part to datetime field when it's not there:
605                 if field._type == 'datetime' and self.__exp[i][2] and len(self.__exp[i][2]) == 10:
606
607                     self.__exp[i] = list(self.__exp[i])
608
609                     if operator in ('>', '>='):
610                         self.__exp[i][2] += ' 00:00:00'
611                     elif operator in ('<', '<='):
612                         self.__exp[i][2] += ' 23:59:59'
613
614                     self.__exp[i] = tuple(self.__exp[i])
615
616                 if field.translate:
617                     need_wildcard = operator in ('like', 'ilike', 'not like', 'not ilike')
618                     sql_operator = {'=like':'like','=ilike':'ilike'}.get(operator,operator)
619                     if need_wildcard:
620                         right = '%%%s%%' % right
621
622                     subselect = '( SELECT res_id'          \
623                              '    FROM ir_translation'  \
624                              '   WHERE name = %s'       \
625                              '     AND lang = %s'       \
626                              '     AND type = %s'
627                     instr = ' %s'
628                     #Covering in,not in operators with operands (%s,%s) ,etc.
629                     if sql_operator in ['in','not in']:
630                         instr = ','.join(['%s'] * len(right))
631                         subselect += '     AND value ' + sql_operator +  ' ' +" (" + instr + ")"   \
632                              ') UNION ('                \
633                              '  SELECT id'              \
634                              '    FROM "' + working_table._table + '"'       \
635                              '   WHERE "' + left + '" ' + sql_operator + ' ' +" (" + instr + "))"
636                     else:
637                         subselect += '     AND value ' + sql_operator + instr +   \
638                              ') UNION ('                \
639                              '  SELECT id'              \
640                              '    FROM "' + working_table._table + '"'       \
641                              '   WHERE "' + left + '" ' + sql_operator + instr + ")"
642
643                     params = [working_table._name + ',' + left,
644                               context.get('lang', False) or 'en_US',
645                               'model',
646                               right,
647                               right,
648                              ]
649
650                     self.__exp[i] = ('id', 'inselect', (subselect, params))
651
652     def __leaf_to_sql(self, leaf, table):
653         left, operator, right = leaf
654
655         if leaf == TRUE_LEAF:
656             query = 'TRUE'
657             params = []
658
659         elif leaf == FALSE_LEAF:
660             query = 'FALSE'
661             params = []
662
663         elif operator == 'inselect':
664             query = '(%s."%s" in (%s))' % (table._table, left, right[0])
665             params = right[1]
666
667         elif operator in ['in', 'not in']:
668             # Two cases: right is a boolean or a list. The boolean case is an
669             # abuse and handled for backward compatibility.
670             if isinstance(right, bool):
671                 _logger.warning("The domain term '%s' should use the '=' or '!=' operator." % (leaf,))
672                 if operator == 'in':
673                     r = 'NOT NULL' if right else 'NULL'
674                 else:
675                     r = 'NULL' if right else 'NOT NULL'
676                 query = '(%s."%s" IS %s)' % (table._table, left, r)
677                 params = []
678             elif isinstance(right, (list, tuple)):
679                 params = right[:]
680                 check_nulls = False
681                 for i in range(len(params))[::-1]:
682                     if params[i] == False:
683                         check_nulls = True
684                         del params[i]
685
686                 if params:
687                     if left == 'id':
688                         instr = ','.join(['%s'] * len(params))
689                     else:
690                         instr = ','.join([table._columns[left]._symbol_set[0]] * len(params))
691                     query = '(%s."%s" %s (%s))' % (table._table, left, operator, instr)
692                 else:
693                     # The case for (left, 'in', []) or (left, 'not in', []).
694                     query = 'FALSE' if operator == 'in' else 'TRUE'
695
696                 if check_nulls and operator == 'in':
697                     query = '(%s OR %s."%s" IS NULL)' % (query, table._table, left)
698                 elif not check_nulls and operator == 'not in':
699                     query = '(%s OR %s."%s" IS NULL)' % (query, table._table, left)
700                 elif check_nulls and operator == 'not in':
701                     query = '(%s AND %s."%s" IS NOT NULL)' % (query, table._table, left) # needed only for TRUE.
702             else: # Must not happen.
703                 pass
704
705         elif right == False and (left in table._columns) and table._columns[left]._type=="boolean" and (operator == '='):
706             query = '(%s."%s" IS NULL or %s."%s" = false )' % (table._table, left, table._table, left)
707             params = []
708
709         elif (right is False or right is None) and (operator == '='):
710             query = '%s."%s" IS NULL ' % (table._table, left)
711             params = []
712
713         elif right == False and (left in table._columns) and table._columns[left]._type=="boolean" and (operator == '!='):
714             query = '(%s."%s" IS NOT NULL and %s."%s" != false)' % (table._table, left, table._table, left)
715             params = []
716
717         elif (right is False or right is None) and (operator == '!='):
718             query = '%s."%s" IS NOT NULL' % (table._table, left)
719             params = []
720
721         elif (operator == '=?'):
722             if (right is False or right is None):
723                 query = 'TRUE'
724                 params = []
725             elif left in table._columns:
726                 format = table._columns[left]._symbol_set[0]
727                 query = '(%s."%s" = %s)' % (table._table, left, format)
728                 params = table._columns[left]._symbol_set[1](right)
729             else:
730                 query = "(%s.\"%s\" = '%%s')" % (table._table, left)
731                 params = right
732
733         elif left == 'id':
734             query = '%s.id %s %%s' % (table._table, operator)
735             params = right
736
737         else:
738             need_wildcard = operator in ('like', 'ilike', 'not like', 'not ilike')
739             sql_operator = {'=like':'like','=ilike':'ilike'}.get(operator,operator)
740
741             if left in table._columns:
742                 format = need_wildcard and '%s' or table._columns[left]._symbol_set[0]
743                 if self.has_unaccent and sql_operator in ('ilike', 'not ilike'):
744                     query = '(unaccent(%s."%s") %s unaccent(%s))' % (table._table, left, sql_operator, format)
745                 else:
746                     query = '(%s."%s" %s %s)' % (table._table, left, sql_operator, format)
747             else:
748                 if self.has_unaccent and sql_operator in ('ilike', 'not ilike'):
749                     query = "(unaccent(%s.\"%s\") %s unaccent('%s'))" % (table._table, left, sql_operator, right)
750                 else:
751                     query = "(%s.\"%s\" %s '%s')" % (table._table, left, sql_operator, right)
752
753             add_null = False
754             if need_wildcard:
755                 if isinstance(right, str):
756                     str_utf8 = right
757                 elif isinstance(right, unicode):
758                     str_utf8 = right.encode('utf-8')
759                 else:
760                     str_utf8 = str(right)
761                 params = '%%%s%%' % str_utf8
762                 add_null = not str_utf8
763             elif left in table._columns:
764                 params = table._columns[left]._symbol_set[1](right)
765
766             if add_null:
767                 query = '(%s OR %s."%s" IS NULL)' % (query, table._table, left)
768
769         if isinstance(params, basestring):
770             params = [params]
771         return (query, params)
772
773
774     def to_sql(self):
775         stack = []
776         params = []
777         # Process the domain from right to left, using a stack, to generate a SQL expression.
778         for i, e in reverse_enumerate(self.__exp):
779             if is_leaf(e, internal=True):
780                 table = self.__field_tables.get(i, self.__main_table)
781                 q, p = self.__leaf_to_sql(e, table)
782                 params.insert(0, p)
783                 stack.append(q)
784             elif e == NOT_OPERATOR:
785                 stack.append('(NOT (%s))' % (stack.pop(),))
786             else:
787                 ops = {AND_OPERATOR: ' AND ', OR_OPERATOR: ' OR '}
788                 q1 = stack.pop()
789                 q2 = stack.pop()
790                 stack.append('(%s %s %s)' % (q1, ops[e], q2,))
791
792         assert len(stack) == 1
793         query = stack[0]
794         joins = ' AND '.join(self.__joins)
795         if joins:
796             query = '(%s) AND %s' % (joins, query)
797         return (query, flatten(params))
798
799     def get_tables(self):
800         return ['"%s"' % t._table for t in self.__all_tables]
801
802 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
803