2 # -*- encoding: utf-8 -*-
3 ##############################################################################
5 # OpenERP, Open Source Management Solution
6 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 ##############################################################################
24 from tools
import flatten
, reverse_enumerate
28 class expression(object):
30 parse a domain expression
31 use a real polish notation
32 leafs are still in a ('foo', '=', 'bar') format
33 For more info: http://christophe-simonis-at-tiny.blogspot.com/2008/08/new-new-domain-notation.html
36 def _is_operator(self
, element
):
37 return isinstance(element
, (str, unicode)) and element
in ['&', '|', '!']
39 def _is_leaf(self
, element
, internal
=False):
40 OPS
= ('=', '!=', '<>', '<=', '<', '>', '>=', '=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of')
41 INTERNAL_OPS
= OPS
+ ('inselect',)
42 return (isinstance(element
, tuple) or isinstance(element
, list)) \
43 and len(element
) == 3 \
44 and (((not internal
) and element
[1] in OPS
) \
45 or (internal
and element
[1] in INTERNAL_OPS
))
47 def __execute_recursive_in(self
, cr
, s
, f
, w
, ids
):
49 for i
in range(0, len(ids
), cr
.IN_MAX
):
50 subids
= ids
[i
:i
+cr
.IN_MAX
]
51 cr
.execute('SELECT "%s"' \
53 ' WHERE "%s" in (%s)' % (s
, f
, w
, ','.join(['%s']*len(subids
))),
55 res
.extend([r
[0] for r
in cr
.fetchall()])
59 def __init__(self
, exp
):
60 # check if the expression is valid
61 if not reduce(lambda acc
, val
: acc
and (self
._is
_operator
(val
) or self
._is
_leaf
(val
)), exp
, True):
62 raise ValueError('Bad domain expression: %r' % (exp
,))
64 self
.__tables
= {} # used to store the table to use for the sql generation. key = index of the leaf
66 self
.__main
_table
= None # 'root' table. set by parse()
67 self
.__DUMMY
_LEAF
= (1, '=', 1) # a dummy leaf that must not be parsed or sql generated
70 def parse(self
, cr
, uid
, table
, context
):
71 """ transform the leafs of the expression """
75 def _rec_get(ids
, table
, parent
=None, left
='id', prefix
=''):
76 if table
._parent
_store
and (not table
.pool
._init
):
77 # TODO: Improve where joins are implemented for many with '.', replace by:
78 # doms += ['&',(prefix+'.parent_left','<',o.parent_right),(prefix+'.parent_left','>=',o.parent_left)]
80 for o
in table
.browse(cr
, uid
, ids
, context
=context
):
83 doms
+= ['&', ('parent_left', '<', o
.parent_right
), ('parent_left', '>=', o
.parent_left
)]
85 return [(left
, 'in', table
.search(cr
, uid
, doms
, context
=context
))]
88 def rg(ids
, table
, parent
):
91 ids2
= table
.search(cr
, uid
, [(parent
, 'in', ids
)], context
=context
)
92 return ids
+ rg(ids2
, table
, parent
)
93 return [(left
, 'in', rg(ids
, table
, parent
or table
._parent
_name
))]
95 self
.__main
_table
= table
98 while i
+ 1<len(self
.__exp
):
101 if self
._is
_operator
(e
) or e
== self
.__DUMMY
_LEAF
:
103 left
, operator
, right
= e
105 working_table
= table
106 if left
in table
._inherit
_fields
:
107 working_table
= table
.pool
.get(table
._inherit
_fields
[left
][0])
108 if working_table
not in self
.__tables
.values():
109 self
.__joins
.append(('%s.%s=%s.%s' % (working_table
._table
, 'id', table
._table
, table
._inherits
[working_table
._name
]), working_table
._table
))
111 self
.__tables
[i
] = working_table
113 fargs
= left
.split('.', 1)
114 field
= working_table
._columns
.get(fargs
[0], False)
116 if left
== 'id' and operator
== 'child_of':
117 dom
= _rec_get(right
, working_table
)
118 self
.__exp
= self
.__exp
[:i
] + dom
+ self
.__exp
[i
+1:]
121 field_obj
= table
.pool
.get(field
._obj
)
123 if field
._type
== 'many2one':
124 right
= field_obj
.search(cr
, uid
, [(fargs
[1], operator
, right
)], context
=context
)
125 self
.__exp
[i
] = (fargs
[0], 'in', right
)
128 if field
._properties
:
129 # this is a function field
131 if not field
._fnct
_search
:
132 # the function field doesn't provide a search function and doesn't store
133 # values in the database, so we must ignore it : we generate a dummy leaf
134 self
.__exp
[i
] = self
.__DUMMY
_LEAF
136 subexp
= field
.search(cr
, uid
, table
, left
, [self
.__exp
[i
]])
137 # we assume that the expression is valid
138 # we create a dummy leaf for forcing the parsing of the resulting expression
140 self
.__exp
.insert(i
+ 1, self
.__DUMMY
_LEAF
)
141 for j
, se
in enumerate(subexp
):
142 self
.__exp
.insert(i
+ 2 + j
, se
)
144 # else, the value of the field is store in the database, so we search on it
147 elif field
._type
== 'one2many':
148 if isinstance(right
, basestring
):
149 ids2
= [x
[0] for x
in field_obj
.name_search(cr
, uid
, right
, [], operator
, limit
=None)]
153 self
.__exp
[i
] = ('id', '=', '0')
155 self
.__exp
[i
] = ('id', 'in', self
.__execute
_recursive
_in
(cr
, field
._fields
_id
, field_obj
._table
, 'id', ids2
))
157 elif field
._type
== 'many2many':
159 if operator
== 'child_of':
160 if isinstance(right
, basestring
):
161 ids2
= [x
[0] for x
in field_obj
.name_search(cr
, uid
, right
, [], 'like', limit
=None)]
165 def _rec_convert(ids
):
166 if field_obj
== table
:
168 return self
.__execute
_recursive
_in
(cr
, field
._id
1, field
._rel
, field
._id
2, ids
)
170 dom
= _rec_get(ids2
, field_obj
)
171 ids2
= field_obj
.search(cr
, uid
, dom
, context
=context
)
172 self
.__exp
[i
] = ('id', 'in', _rec_convert(ids2
))
174 if isinstance(right
, basestring
):
175 res_ids
= [x
[0] for x
in field_obj
.name_search(cr
, uid
, right
, [], operator
)]
177 res_ids
= list(right
)
178 self
.__exp
[i
] = ('id', 'in', self
.__execute
_recursive
_in
(cr
, field
._id
1, field
._rel
, field
._id
2, res_ids
) or [0])
179 elif field
._type
== 'many2one':
180 if operator
== 'child_of':
181 if isinstance(right
, basestring
):
182 ids2
= [x
[0] for x
in field_obj
.name_search(cr
, uid
, right
, [], 'like', limit
=None)]
186 self
.__operator
= 'in'
187 if field
._obj
!= working_table
._name
:
188 dom
= _rec_get(ids2
, field_obj
, left
=left
, prefix
=field
._obj
)
190 dom
= _rec_get(ids2
, working_table
, parent
=left
)
191 self
.__exp
= self
.__exp
[:i
] + dom
+ self
.__exp
[i
+1:]
193 if isinstance(right
, basestring
): # and not isinstance(field, fields.related):
195 c
['active_test'] = False
196 res_ids
= field_obj
.name_search(cr
, uid
, right
, [], operator
, limit
=None, context
=c
)
197 right
= map(lambda x
: x
[0], res_ids
)
198 self
.__exp
[i
] = (left
, 'in', right
)
202 if operator
in ('like', 'ilike', 'not like', 'not ilike'):
203 right
= '%%%s%%' % right
205 operator
= operator
== '=like' and 'like' or operator
207 query1
= '( SELECT res_id' \
208 ' FROM ir_translation' \
213 #Covering in,not in operators with operands (%s,%s) ,etc.
214 if operator
in ['in','not in']:
215 instr
= ','.join(['%s'] * len(right
))
216 query1
+= ' AND value ' + operator
+ ' ' +" (" + instr
+ ")" \
219 ' FROM "' + working_table
._table
+ '"' \
220 ' WHERE "' + left
+ '" ' + operator
+ ' ' +" (" + instr
+ "))"
222 query1
+= ' AND value ' + operator
+ instr
+ \
225 ' FROM "' + working_table
._table
+ '"' \
226 ' WHERE "' + left
+ '" ' + operator
+ instr
+ ")"
228 query2
= [working_table
._name
+ ',' + left
,
229 context
.get('lang', False) or 'en_US',
235 self
.__exp
[i
] = ('id', 'inselect', (query1
, query2
))
239 def __leaf_to_sql(self
, leaf
, table
):
240 if leaf
== self
.__DUMMY
_LEAF
:
242 left
, operator
, right
= leaf
244 if operator
== 'inselect':
245 query
= '(%s.%s in (%s))' % (table
._table
, left
, right
[0])
247 elif operator
in ['in', 'not in']:
249 len_before
= len(params
)
250 for i
in range(len_before
)[::-1]:
251 if params
[i
] == False:
254 len_after
= len(params
)
255 check_nulls
= len_after
!= len_before
260 instr
= ','.join(['%s'] * len_after
)
262 instr
= ','.join([table
._columns
[left
]._symbol
_set
[0]] * len_after
)
263 query
= '(%s.%s %s (%s))' % (table
._table
, left
, operator
, instr
)
266 query
= '(%s OR %s.%s IS NULL)' % (query
, table
._table
, left
)
270 if right
== False and (leaf
[0] in table
._columns
) and table
._columns
[leaf
[0]]._type
=="boolean" and (operator
== '='):
271 query
= '(%s.%s IS NULL or %s.%s = false )' % (table
._table
, left
,table
._table
, left
)
272 elif (((right
== False) and (type(right
)==bool)) or (right
is None)) and (operator
== '='):
273 query
= '%s.%s IS NULL ' % (table
._table
, left
)
274 elif right
== False and (leaf
[0] in table
._columns
) and table
._columns
[leaf
[0]]._type
=="boolean" and (operator
in ['<>', '!=']):
275 query
= '(%s.%s IS NOT NULL and %s.%s != false)' % (table
._table
, left
,table
._table
, left
)
276 elif (((right
== False) and (type(right
)==bool)) or right
is None) and (operator
in ['<>', '!=']):
277 query
= '%s.%s IS NOT NULL' % (table
._table
, left
)
280 query
= '%s.id %s %%s' % (table
._table
, operator
)
283 like
= operator
in ('like', 'ilike', 'not like', 'not ilike')
285 op
= operator
== '=like' and 'like' or operator
286 if left
in table
._columns
:
287 format
= like
and '%s' or table
._columns
[left
]._symbol
_set
[0]
288 query
= '(%s.%s %s %s)' % (table
._table
, left
, op
, format
)
290 query
= "(%s.%s %s '%s')" % (table
._table
, left
, op
, right
)
294 if isinstance(right
, str):
296 elif isinstance(right
, unicode):
297 str_utf8
= right
.encode('utf-8')
299 str_utf8
= str(right
)
300 params
= '%%%s%%' % str_utf8
301 add_null
= not str_utf8
302 elif left
in table
._columns
:
303 params
= table
._columns
[left
]._symbol
_set
[1](right
)
306 query
= '(%s OR %s IS NULL)' % (query
, left
)
308 if isinstance(params
, basestring
):
310 return (query
, params
)
316 for i
, e
in reverse_enumerate(self
.__exp
):
317 if self
._is
_leaf
(e
, internal
=True):
318 table
= self
.__tables
.get(i
, self
.__main
_table
)
319 q
, p
= self
.__leaf
_to
_sql
(e
, table
)
324 stack
.append('(NOT (%s))' % (stack
.pop(),))
326 ops
= {'&': ' AND ', '|': ' OR '}
329 stack
.append('(%s %s %s)' % (q1
, ops
[e
], q2
,))
331 query
= ' AND '.join(reversed(stack
))
332 joins
= ' AND '.join(map(lambda j
: j
[0], self
.__joins
))
334 query
= '(%s) AND (%s)' % (joins
, query
)
335 return (query
, flatten(params
))
337 def get_tables(self
):
338 return ['"%s"' % t
._table
for t
in set(self
.__tables
.values()+[self
.__main
_table
])]
340 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: