Rely on the tables dict to choose when to apply the OR-to-UNION optimization

The dict changes in size if non-local columns are used to order the search
results.

Closes #13346
This commit is contained in:
Nicolas Évrard
2024-06-13 18:57:14 +02:00
parent a11522ffd6
commit 6512d36bd1
2 changed files with 45 additions and 35 deletions
+37 -34
View File
@@ -1701,19 +1701,7 @@ class ModelSQL(ModelStorage):
local_domains.insert(0, 'OR')
joined_domains.append(local_domains)
def get_local_columns(order_exprs):
local_columns = []
for order_expr in order_exprs:
if (isinstance(order_expr, Column)
and isinstance(order_expr._from, Table)
and order_expr._from._name == cls._table):
local_columns.append(order_expr._name)
else:
raise NotImplementedError
return local_columns
# The UNION optimization needs the columns used to order the query
extra_columns = set()
if order and joined_domains:
tables = {
None: (cls.__table__(), None),
@@ -1721,18 +1709,17 @@ class ModelSQL(ModelStorage):
for oexpr, otype in order:
fname = oexpr.partition('.')[0]
field = cls._fields[fname]
field_orders = field.convert_order(oexpr, tables, cls)
try:
order_columns = get_local_columns(field_orders)
extra_columns.update(order_columns)
except NotImplementedError:
field.convert_order(oexpr, tables, cls)
if len(tables) > 1:
joined_domains = None
break
# In case the search uses subqueries it's more efficient to use a UNION
# of queries than using clauses with some JOIN because databases can
# used indexes
orderings = []
if joined_domains is not None:
done_orderings = False
union_tables = []
for sub_domain in joined_domains:
sub_domain = [sub_domain] # it may be a clause
@@ -1743,9 +1730,22 @@ class ModelSQL(ModelStorage):
expression &= domain_exp
main_table, _ = tables[None]
table = convert_from(None, tables)
columns = cls.__searched_columns(main_table,
eager=not count and not query,
extra_columns=extra_columns)
columns = cls.__searched_columns(
main_table, eager=not count and not query)
o_idx = 0
for oexpr, otype in order:
column_name, _, extra_expr = oexpr.partition('.')
field = cls._fields[column_name]
# By construction tables is left untouched
forder = field.convert_order(oexpr, tables, cls)
columns.extend(o.as_(f'_order_{o_idx + idx}')
for idx, o in enumerate(forder))
o_idx += len(forder)
if not done_orderings:
orderings.extend([otype] * len(forder))
done_orderings = True
union_tables.append(table.select(
*columns, where=expression))
expression = None
@@ -1759,30 +1759,21 @@ class ModelSQL(ModelStorage):
rule_domain, active_test=False, tables=tables)
expression &= domain_exp
return tables, expression
return tables, expression, orderings
@classmethod
def __searched_columns(
cls, table, *, eager=False, history=False, extra_columns=None):
if extra_columns is None:
extra_columns = []
else:
extra_columns = sorted(extra_columns - {'id', '__id', '_datetime'})
def __searched_columns(cls, table, *, eager=False, history=False):
columns = [table.id.as_('id')]
if (cls._history and Transaction().context.get('_datetime')
and (eager or history)):
columns.append(
Coalesce(table.write_date, table.create_date).as_('_datetime'))
columns.append(Column(table, '__id').as_('__id'))
for column_name in extra_columns:
field = cls._fields[column_name]
sql_column = field.sql_column(table).as_(column_name)
columns.append(sql_column)
if eager:
columns += [f.sql_column(table).as_(n)
for n, f in sorted(cls._fields.items())
if not hasattr(f, 'get')
and n not in extra_columns
and n != 'id'
and not getattr(f, 'translate', False)
and f.loading == 'eager']
@@ -1814,7 +1805,8 @@ class ModelSQL(ModelStorage):
if order is None or order is False:
order = cls._order
tables, expression = cls.__search_query(domain, count, query, order)
tables, expression, union_orderings = cls.__search_query(
domain, count, query, order)
main_table, _ = tables[None]
if count:
@@ -1831,7 +1823,18 @@ class ModelSQL(ModelStorage):
cursor.execute(*select)
return cursor.fetchone()[0]
order_by = cls.__search_order(order, tables)
if union_orderings:
# union_orderings is not empty only when the OR-to-UNION
# optimization has been applied. In this case we must rely on the
# _order_XXX columns that were added to the subqueries to properly
# sort the results.
order_by = []
for idx, otype in enumerate(union_orderings):
column = getattr(main_table, f'_order_{idx}')
order_by.append(apply_sorting(otype)(column))
else:
order_by = cls.__search_order(order, tables)
# compute it here because __search_order might modify tables
table = convert_from(None, tables)
if query:
+8 -1
View File
@@ -142,8 +142,15 @@ class ModelSQLSearchOR2Union(ModelSQL):
@classmethod
def order_integer(cls, tables):
pool = Pool()
Target = pool.get('test.modelsql.search.or2union.target')
target = Target.__table__()
table, _ = tables[None]
return [table.integer + 1]
tables['target'] = {
None: (target, (target.id == table.target)),
}
return [table.integer + target.id]
class ModelSQLSearchOR2UnionTarget(ModelSQL):