Speed Improvement:
[odoo/odoo.git] / bin / osv / expression.py
1 #!/usr/bin/env python
2 # -*- encoding: utf-8 -*-
3
4 from tools import flatten, reverse_enumerate
5
6
7 class expression(object):
8     """
9     parse a domain expression
10     use a real polish notation
11     leafs are still in a ('foo', '=', 'bar') format
12     For more info: http://...
13     """
14
15     def _is_operator(self, element):
16         return isinstance(element, str) \
17            and element in ['&', '|', '!']
18
19     def _is_leaf(self, element):
20         return isinstance(element, tuple) \
21            and len(element) == 3 \
22            and element[1] in ('=', '!=', '<>', '<=', '<', '>', '>=', '=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of')
23
24     def __execute_recursive_in(self, cr, s, f, w, ids):
25         #deprecated -> use _left and _right...
26         res = []
27         for i in range(0, len(ids), cr.IN_MAX):
28             subids = ids[i:i+cr.IN_MAX]
29             cr.execute('SELECT "%s"'    \
30                        '  FROM "%s"'    \
31                        ' WHERE "%s" in (%s)' % (s, f, w, ','.join(['%d']*len(subids))),
32                        subids)
33             res.extend([r[0] for r in cr.fetchall()])
34         return res
35
36
37     def __init__(self, exp):
38
39         # check if the expression is valid
40         if not reduce(lambda acc, val: acc and (self._is_operator(val) or self._is_leaf(val)), exp, True):
41             raise ValueError('Bad expression: %r' % (exp,))
42
43         self.__exp = exp
44         self.__tables = {}  # used to store the table to use for the sql generation. key = index of the leaf
45         self.__joins = []
46         self.__main_table = None # 'root' table. set by parse()
47
48         self.__DUMMY_LEAF = (1, '=', 1) # a dummy leaf that must not be parsed or sql generated
49
50
51     def parse(self, cr, uid, table, context):
52         """ transform the leafs of the expression """
53
54         def _rec_get(ids, table, parent):
55             if table._parent_store:
56                 doms = []
57                 for o in table.browse(cr, uid, ids, context=context):
58                     if doms:
59                         doms.insert(0,'|')
60                     doms.append(['&',('parent_left','<',o.parent_right),('parent_left','>=',o.parent_left)])
61                 return table.search(cr, uid, doms, context=context)
62             else:
63                 if not ids:
64                     return []
65                 ids2 = table.search(cr, uid, [(parent, 'in', ids)], context=context)
66                 return ids + _rec_get(ids2, table, parent)
67
68         if not self.__exp:
69             return self
70
71         self.__main_table = table
72         working_table = table
73         
74         for i, e in enumerate(self.__exp):
75             if self._is_operator(e) or e == self.__DUMMY_LEAF:
76                 continue
77             left, operator, right = e
78
79             if left in table._inherit_fields:
80                 working_table = table.pool.get(table._inherit_fields[left][0])
81                 if working_table not in self.__tables.values():
82                     self.__joins.append('%s.%s' % (table._table, table._inherits[working_table._name]))
83             
84             self.__tables[i] = working_table
85             
86             fargs = left.split('.', 1)
87             field = working_table._columns.get(fargs[0], False)
88             if not field:
89                 if left == 'id' and operator == 'child_of':
90                     right += _rec_get(right, working_table, working_table._parent_name)
91                     self.__exp[i] = ('id', 'in', right)
92                 continue
93             
94             field_obj = table.pool.get(field._obj)
95             if len(fargs) > 1:
96                 if field._type == 'many2one':
97                     right = field_obj.search(cr, uid, [(fargs[1], operator, right)], context=context)
98                     self.__exp[i] = (fargs[0], 'in', right)
99                 continue
100
101             if field._properties:
102                 # this is a function field
103                 if not field.store:
104                     if not field._fnct_search:
105                         # the function field doesn't provide a search function and doesn't store
106                         # values in the database, so we must ignore it : we generate a dummy leaf
107                         self.__exp[i] = self.__DUMMY_LEAF
108                     else:
109                         subexp = field.search(cr, uid, table, left, [self.__exp[i]])
110                         # we assume that the expression is valid 
111                         # we create a dummy leaf for forcing the parsing of the resulting expression
112                         self.__exp[i] = '&'
113                         self.__exp.insert(i + 1, self.__DUMMY_LEAF)
114                         for j, se in enumerate(subexp):
115                             self.__exp.insert(i + 2 + j, se)
116                 
117                 # else, the value of the field is store in the database, so we search on it
118
119
120             elif field._type == 'one2many':
121                 if isinstance(right, basestring):
122                     ids2 = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator)]
123                 else:
124                     ids2 = list(right)
125                 if not ids2:
126                     self.__exp[i] = ('id', '=', '0')
127                 else:
128                     self.__exp[i] = ('id', 'in', self.__execute_recursive_in(cr, field._fields_id, field_obj._table, 'id', ids2))
129
130             elif field._type == 'many2many':
131                 #FIXME
132                 if operator == 'child_of':
133                     if isinstance(right, basestring):
134                         ids2 = [x[0] for x in field_obj.name_search(cr, uid, right, [], 'like')]
135                     else:
136                         ids2 = list(right)
137
138                     def _rec_convert(ids):
139                         if field_obj == table:
140                             return ids
141                         return self.__execute_recursive_in(cr, field._id1, field._rel, field._id2, ids)
142
143                     self.__exp[i] = ('id', 'in', _rec_convert(ids2 + _rec_get(ids2, field_obj, working_table._parent_name)))
144                 else:
145                     if isinstance(right, basestring):
146                         res_ids = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator)]
147                     else:
148                         res_ids = list(right)
149                     self.__exp[i] = ('id', 'in', self.__execute_recursive_in(cr, field._id1, field._rel, field._id2, res_ids) or [0])
150             elif field._type == 'many2one':
151                 if operator == 'child_of':
152                     if isinstance(right, basestring):
153                         ids2 = [x[0] for x in field_obj.search_name(cr, uid, right, [], 'like')]
154                     else:
155                         ids2 = list(right)
156
157                     self.__operator = 'in'
158                     if field._obj != working_table._name:
159                         right = ids2 + _rec_get(ids2, field_obj, working_table._parent_name)
160                     else:
161                         right = ids2 + _rec_get(ids2, working_table, left)
162                         left = 'id'
163                     self.__exp[i] = (left, 'in', right)
164                 else:
165                     if isinstance(right, basestring):
166                         res_ids = field_obj.name_search(cr, uid, right, [], operator)
167                         right = map(lambda x: x[0], res_ids)
168                         self.__exp[i] = (left, 'in', right)
169             else:
170                 # other field type
171                 if field.translate:
172                     if operator in ('like', 'ilike', 'not like', 'not ilike'):
173                         right = '%%%s%%' % right
174
175                     query1 = '( SELECT res_id'          \
176                              '    FROM ir_translation'  \
177                              '   WHERE name = %s'       \
178                              '     AND lang = %s'       \
179                              '     AND type = %s'       \
180                              '     AND value ' + operator + ' %s'    \
181                              ') UNION ('                \
182                              '  SELECT id'              \
183                              '    FROM "' + working_table._table + '"'       \
184                              '   WHERE "' + left + '" ' + operator + ' %s' \
185                              ')'
186                     query2 = [working_table._name + ',' + left,
187                               context.get('lang', False) or 'en_US',
188                               'model',
189                               right,
190                               right,
191                              ]
192
193                     self.__exp[i] = ('id', 'inselect', (query1, query2))
194
195         return self
196
197     def __leaf_to_sql(self, leaf, table):
198         left, operator, right = leaf
199
200         if operator == 'inselect':
201             query = '(%s.%s in (%s))' % (table._table, left, right[0])
202             params = right[1]
203         elif operator in ['in', 'not in']:
204             params = right[:]
205             len_before = len(params)
206             for i in range(len_before)[::-1]:
207                 if params[i] == False:
208                     del params[i]
209
210             len_after = len(params)
211             check_nulls = len_after != len_before
212             query = '(1=0)'
213
214             if len_after:
215                 if left == 'id':
216                      instr = ','.join(['%d'] * len_after)
217                 else:
218                     instr = ','.join([table._columns[left]._symbol_set[0]] * len_after)
219                 query = '(%s.%s %s (%s))' % (table._table, left, operator, instr)
220
221             if check_nulls:
222                 query = '(%s OR %s IS NULL)' % (query, left)
223         else:
224             params = []
225             if right is False and operator == '=':
226                 query = '%s IS NULL' % left
227             elif right is False and operator in ['<>', '!=']:
228                 query = '%s IS NOT NULL' % left
229             else:
230                 if left == 'id':
231                     query = '%s.id %s %%s' % (table._table, operator)
232                     params = right
233                 else:
234                     like = operator in ('like', 'ilike', 'not like', 'not ilike')
235
236                     op = operator == '=like' and 'like' or operator
237                     if left in table._columns:
238                         format = like and '%s' or table._columns[left]._symbol_set[0]
239                         query = '(%s.%s %s %s)' % (table._table, left, op, format)
240                     else:
241                         query = "(%s.%s %s '%s')" % (table._table, left, op, right)
242
243                     add_null = False
244                     if like:
245                         if isinstance(right, str):
246                             str_utf8 = right
247                         elif isinstance(right, unicode):
248                             str_utf8 = right.encode('utf-8')
249                         else:
250                             str_utf8 = str(right)
251                         params = '%%%s%%' % str_utf8
252                         add_null = not str_utf8
253                     elif left in table._columns:
254                         params = table._columns[left]._symbol_set[1](right)
255
256                     if add_null:
257                         query = '(%s OR %s IS NULL)' % (query, left)
258
259         if isinstance(params, basestring):
260             params = [params]
261         return (query, params)
262
263
264     def to_sql(self):
265         stack = []
266         params = []
267         for i, e in reverse_enumerate(self.__exp):
268             if self._is_leaf(e):
269                 table = self.__tables.has_key(i) and self.__tables[i] or self.__main_table
270                 q, p = self.__leaf_to_sql(e, table)
271                 params.insert(0, p)
272                 stack.append(q)
273             else:
274                 if e == '!':
275                     stack.append('(NOT (%s))' % (stack.pop(),))
276                 else:
277                     ops = {'&': ' AND ', '|': ' OR '}
278                     q1 = stack.pop()
279                     q2 = stack.pop()
280                     stack.append('(%s %s %s)' % (q1, ops[e], q2,))
281         
282         query = ' AND '.join(reversed(stack))
283         joins = ' AND '.join(map(lambda j: '%s.id = %s' % (self.__main_table._table, j), self.__joins))
284         if joins:
285             query = '(%s AND (%s))' % (joins, query)
286         return (query, flatten(params))
287
288     def get_tables(self):
289         return ['"%s"' % t._table for t in set(self.__tables.values())]
290
291 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
292