[FIX] expression: use sub-select when searching on many2many field
Avoid pathological performance issue caused by injecting ids retrieved with another query. Consider a domain like `[('m2m', 'in', ids)]` on a many2many field. The current implementation will perform the subquery: SELECT m2m_id1 FROM m2m_table WHERE m2m_id2 IN (ids) and inject its result into the main query as: SELECT id FROM ... WHERE id IN (result_ids) The latter may be very slow if `result_ids` is a huge list of ids. The fix injects the first query into the main query as: SELECT id FROM ... WHERE id IN ( SELECT m2m_id1 FROM m2m_table WHERE m2m_id2 IN (ids) ) As a result, the database will typically JOIN both tables, and avoid generating the whole list from the subquery.
This commit is contained in:
parent
3128e84243
commit
3c2065c89d
|
@ -994,15 +994,16 @@ class expression(object):
|
|||
rel_table, rel_id1, rel_id2 = column._sql_names(model)
|
||||
#FIXME
|
||||
if operator == 'child_of':
|
||||
def _rec_convert(ids):
|
||||
if comodel == model:
|
||||
return ids
|
||||
return select_from_where(cr, rel_id1, rel_table, rel_id2, ids, operator)
|
||||
|
||||
ids2 = to_ids(right, comodel, context)
|
||||
dom = child_of_domain('id', ids2, comodel)
|
||||
ids2 = comodel.search(cr, uid, dom, context=context)
|
||||
push(create_substitution_leaf(leaf, ('id', 'in', _rec_convert(ids2)), model))
|
||||
if comodel == model:
|
||||
push(create_substitution_leaf(leaf, ('id', 'in', ids2), model))
|
||||
else:
|
||||
subquery = 'SELECT "%s" FROM "%s" WHERE "%s" IN %%s' % (rel_id1, rel_table, rel_id2)
|
||||
# avoid flattening of argument in to_sql()
|
||||
subquery = cr.mogrify(subquery, [tuple(ids2)])
|
||||
push(create_substitution_leaf(leaf, ('id', 'inselect', (subquery, [])), internal=True))
|
||||
else:
|
||||
call_null_m2m = True
|
||||
if right is not False:
|
||||
|
@ -1024,8 +1025,11 @@ class expression(object):
|
|||
operator = 'in' # operator changed because ids are directly related to main object
|
||||
else:
|
||||
call_null_m2m = False
|
||||
m2m_op = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in'
|
||||
push(create_substitution_leaf(leaf, ('id', m2m_op, select_from_where(cr, rel_id1, rel_table, rel_id2, res_ids, operator) or [0]), model))
|
||||
subop = 'not inselect' if operator in NEGATIVE_TERM_OPERATORS else 'inselect'
|
||||
subquery = 'SELECT "%s" FROM "%s" WHERE "%s" IN %%s' % (rel_id1, rel_table, rel_id2)
|
||||
# avoid flattening of argument in to_sql()
|
||||
subquery = cr.mogrify(subquery, [tuple(filter(None, res_ids))])
|
||||
push(create_substitution_leaf(leaf, ('id', subop, (subquery, [])), internal=True))
|
||||
|
||||
if call_null_m2m:
|
||||
m2m_op = 'in' if operator in NEGATIVE_TERM_OPERATORS else 'not in'
|
||||
|
|
Loading…
Reference in New Issue