[FIX] expression: better semantic for in/not in w.r.t. null values:
In SQL, not in (2, 7, 9) will return only non-null records. In OpenERP, a domain with not in [2, 7, 9] should return every record whose value is not 2 or 7 or 9, including the one with a null value. This means that the union of (not in XXX) and (in XXX) will return the whole set. bzr revid: vmt@openerp.com-20110810130646-2y822a276igz2f1g
This commit is contained in:
parent
784a2d6add
commit
19a21de5e1
|
@ -192,9 +192,11 @@
|
|||
for x in partners:
|
||||
if x.parent_id and x.parent_id.id in partner_ids:
|
||||
with_parent.append(x.id)
|
||||
with_parent.sort()
|
||||
for x in partners:
|
||||
if not x.parent_id:
|
||||
without_parent.append(x.id)
|
||||
without_parent.sort()
|
||||
with_website = []
|
||||
for x in partners:
|
||||
if x.website:
|
||||
|
@ -202,17 +204,45 @@
|
|||
with_website.sort()
|
||||
print "with_parent", with_parent
|
||||
print "without_parent", without_parent
|
||||
res_0 = self.search(cr, uid, [('parent_id', 'not like', 'probably_unexisting_name')])
|
||||
res_1 = self.search(cr, uid, [('parent_id', 'not in', [max_partner_id + 1])])
|
||||
res_2 = self.search(cr, uid, [('parent_id', 'not in', False)])
|
||||
res_3 = self.search(cr, uid, [('parent_id', 'not in', [])])
|
||||
res_4 = self.search(cr, uid, [('parent_id', 'not in', [False])])
|
||||
print ">>> 0:", res_0
|
||||
print ">>> 1:", res_1
|
||||
print ">>> 2:", res_2
|
||||
print ">>> 3:", res_3
|
||||
print ">>> 4:", res_4
|
||||
|
||||
# We treat null values differently than in SQL. For instance in SQL:
|
||||
# SELECT id FROM res_partner WHERE parent_id NOT IN (0)
|
||||
# will return only the records with non-null parent_id.
|
||||
# SELECT id FROM res_partner WHERE parent_id IN (0)
|
||||
# will return expectedly nothing (our ids always begin at 1).
|
||||
# This means the union of those two results will give only some
|
||||
# records, but not all present in database.
|
||||
#
|
||||
# When using domains and the ORM's search method, we think it is
|
||||
# more intuitive that the union returns all the records, and that
|
||||
# a domain like ('parent_id', 'not in', [0]) will return all
|
||||
# the records. For instance, if you perform a search for the companies
|
||||
# that don't have OpenERP has a parent company, you expect to find,
|
||||
# among others, the companies that don't have parent company.
|
||||
#
|
||||
# ('parent_id', 'not in', [0]) must give the same result than
|
||||
# ('parent_id', 'not in', []), i.e. a empty set or a set with non-
|
||||
# existing values be treated similarly if we simply check that some
|
||||
# existing value belongs to them.
|
||||
|
||||
res_0 = self.search(cr, uid, [('parent_id', 'not like', 'probably_unexisting_name')]) # get all rows, included null parent_id
|
||||
res_0.sort()
|
||||
res_1 = self.search(cr, uid, [('parent_id', 'not in', [max_partner_id + 1])]) # get all rows, included null parent_id
|
||||
res_1.sort()
|
||||
res_2 = self.search(cr, uid, [('parent_id', 'not in', False)]) # get rows with not null parent_id, deprecated syntax
|
||||
res_2.sort()
|
||||
res_3 = self.search(cr, uid, [('parent_id', 'not in', [])]) # get all rows, included null parent_id
|
||||
res_3.sort()
|
||||
res_4 = self.search(cr, uid, [('parent_id', 'not in', [False])]) # get rows with not null parent_id
|
||||
res_4.sort()
|
||||
assert res_0 == partner_ids
|
||||
assert res_1 == partner_ids
|
||||
assert res_2 == with_parent
|
||||
assert res_3 == partner_ids
|
||||
assert res_4 == with_parent
|
||||
print ">>> ----------"
|
||||
# The results of these queries, when combined with queries 0..4 must
|
||||
# give the whole set of ids.
|
||||
res_5 = self.search(cr, uid, [('parent_id', 'like', 'probably_unexisting_name')])
|
||||
res_6 = self.search(cr, uid, [('parent_id', 'in', [max_partner_id + 1])])
|
||||
res_7 = self.search(cr, uid, [('parent_id', 'in', False)])
|
||||
|
@ -224,6 +254,8 @@
|
|||
print ">>> 8:", res_8
|
||||
print ">>> 9:", res_9
|
||||
print ">>> ----------"
|
||||
# These queries must return exactly the results than the queries 0..4,
|
||||
# i.e. not ... in ... must be the same as ... not in ... .
|
||||
res_10 = self.search(cr, uid, ['!', ('parent_id', 'like', 'probably_unexisting_name')])
|
||||
res_11 = self.search(cr, uid, ['!', ('parent_id', 'in', [max_partner_id + 1])])
|
||||
res_12 = self.search(cr, uid, ['!', ('parent_id', 'in', False)])
|
||||
|
|
|
@ -387,6 +387,8 @@ class expression(object):
|
|||
elif isinstance(right, list) and operator in ['!=','=']: #for domain (FIELD,'=',['value1','value2'])
|
||||
operator = dict_op[operator]
|
||||
res_ids = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator, limit=None, context=c)]
|
||||
if operator in NEGATIVE_OPS:
|
||||
res_ids.append(False) # TODO this should not be appended if False was in 'right'
|
||||
return (left, 'in', res_ids)
|
||||
|
||||
m2o_str = False
|
||||
|
@ -499,8 +501,13 @@ class expression(object):
|
|||
else:
|
||||
# The case for (left, 'in', []) or (left, 'not in', []).
|
||||
query = 'FALSE' if operator == 'in' else 'TRUE'
|
||||
if check_nulls:
|
||||
query = '(%s OR %s.%s IS NULL)' % (query, table._table, left) # TODO not necessary for TRUE
|
||||
|
||||
if check_nulls and operator == 'in':
|
||||
query = '(%s OR %s.%s IS NULL)' % (query, table._table, left)
|
||||
elif not check_nulls and operator == 'not in':
|
||||
query = '(%s OR %s.%s IS NULL)' % (query, table._table, left)
|
||||
elif check_nulls and operator == 'not in':
|
||||
query = '(%s AND %s.%s IS NOT NULL)' % (query, table._table, left) # needed only for TRUE.
|
||||
else: # Must not happen.
|
||||
pass
|
||||
|
||||
|
|
Loading…
Reference in New Issue