[CLEAN] expression.py: cleaned and added comments, var names (table -> model notably).

bzr revid: tde@openerp.com-20121207123727-md65da41863y827e
This commit is contained in:
Thibault Delavallée 2012-12-07 13:37:27 +01:00
parent e36b44e82f
commit 390ff1540f
1 changed files with 173 additions and 158 deletions

View File

@ -325,7 +325,7 @@ def generate_table_alias(src_table_alias, joined_tables=[]):
- then, each joined table is added in the alias using a 'link field name'
that is used to render unique aliases for a given path
- returns a tuple composed of the alias, and the full table alias to be
added in a from condition
added in a from condition with quoting done
Examples:
- src_table_alias='res_users', join_tables=[]:
alias = ('res_users','"res_users"')
@ -336,7 +336,7 @@ def generate_table_alias(src_table_alias, joined_tables=[]):
:param list join_tables: list of tuples
(dst_model, link_field)
:return tuple: (table alias, alias statement for from clause with quotes added)
:return tuple: (table_alias, alias statement for from clause with quotes added)
"""
alias = src_table_alias
if not joined_tables:
@ -366,11 +366,10 @@ def normalize_leaf(element):
def is_operator(element):
"""Test whether an object is a valid domain operator. """
""" Test whether an object is a valid domain operator. """
return isinstance(element, basestring) and element in DOMAIN_OPERATORS
# TODO change the share wizard to use this function.
def is_leaf(element, internal=False):
""" Test whether an object is a valid domain term:
- is a list or tuple
@ -380,6 +379,8 @@ def is_leaf(element, internal=False):
:param tuple element: a leaf in form (left, operator, right)
:param boolean internal: allow or not the 'inselect' internal operator
in the term. This should be always left to False.
Note: OLD TODO change the share wizard to use this function.
"""
INTERNAL_OPS = TERM_OPERATORS + ('<>',)
if internal:
@ -421,39 +422,10 @@ def select_distinct_from_where_not_null(cr, select_field, from_table):
# -------------------------------------------------
class ExtendedLeaf(object):
def __init__(self, leaf, table, context_stack=None):
""" Initialize the ExtendedLeaf
:attr [string, tuple] leaf: operator or tuple-formatted domain
expression
:attr object table: table object
:attr list _tables: list of chained table objects, updated when
adding joins
:attr tuple elements: manipulation-friendly leaf
:attr object field: field obj, taken from table, not necessarily
found (inherits, 'id')
:attr list field_path: exploded left of elements
(partner_id.name -> ['partner_id', 'name'])
:attr object relational_table: distant table for relational fields
""" Class wrapping a domain leaf, and giving some services and management
features on it. In particular it managed join contexts to be able to
construct queries through multiple models.
"""
assert table, 'Invalid leaf creation without table'
self.context_stack = context_stack or []
# validate the leaf
self.leaf = leaf
# normalize the leaf's operator
self.normalize_leaf()
# set working variables; handle the context stack and previous tables
self.table = table
self._tables = []
for item in self.context_stack:
self._tables.append(item[0])
self._tables.append(table)
# check validity
self.check_leaf()
def __str__(self):
return '<osv.ExtendedLeaf: %s on %s (ctx: %s)>' % (str(self.leaf), self.table._table, ','.join(self._get_context_debug()))
# --------------------------------------------------
# Join / Context manipulation
@ -500,32 +472,61 @@ class ExtendedLeaf(object):
# i.e.: many2one: 'state_id': current field name
# --------------------------------------------------
def __init__(self, leaf, model, join_context=None):
""" Initialize the ExtendedLeaf
:attr [string, tuple] leaf: operator or tuple-formatted domain
expression
:attr obj model: current working model
:attr list _models: list of chained models, updated when
adding joins
:attr list join_context: list of join contexts. This is a list of
tuples like ``(lhs, table, lhs_col, col, link)``
:param obj lhs: source (left hand) model
:param obj model: destination (right hand) model
:param string lhs_col: source model column for join condition
:param string col: destination model column for join condition
:param link: link column between source and destination model
that is not necessarily (but generally) a real column used
in the condition (i.e. in many2one); this link is used to
compute aliases
"""
assert model, 'Invalid leaf creation without table'
self.join_context = join_context or []
self.leaf = leaf
# normalize the leaf's operator
self.normalize_leaf()
# set working variables; handle the context stack and previous tables
self.model = model
self._models = []
for item in self.join_context:
self._models.append(item[0])
self._models.append(model)
# check validity
self.check_leaf()
def __str__(self):
return '<osv.ExtendedLeaf: %s on %s (ctx: %s)>' % (str(self.leaf), self.model._table, ','.join(self._get_context_debug()))
def generate_alias(self):
links = [(context[1]._table, context[4]) for context in self.context_stack]
alias, alias_statement = generate_table_alias(self._tables[0]._table, links)
links = [(context[1]._table, context[4]) for context in self.join_context]
alias, alias_statement = generate_table_alias(self._models[0]._table, links)
return alias
def add_join_context(self, table, lhs_col, table_col, link):
def add_join_context(self, model, lhs_col, table_col, link):
""" See above comments for more details. A join context is a tuple like:
``(lhs, table, lhs_col, col, link)``
where
- lhs is the source table (self.table)
- table is the destination table
- lsh_col is the source table column name used for the condition
- table_col is the destination table column name used for the condition
- link is the field name source of the join used as context to
generate the destination table alias
``(lhs, model, lhs_col, col, link)``
After adding the join, the table of the current leaf is updated.
After adding the join, the model of the current leaf is updated.
"""
self.context_stack.append((self.table, table, lhs_col, table_col, link))
self._tables.append(table)
self.table = table
self.join_context.append((self.model, model, lhs_col, table_col, link))
self._models.append(model)
self.model = model
def get_join_conditions(self):
conditions = []
alias = self._tables[0]._table
for context in self.context_stack:
alias = self._models[0]._table
for context in self.join_context:
previous_alias = alias
alias += '__' + context[4]
conditions.append('"%s"."%s"="%s"."%s"' % (previous_alias, context[2], alias, context[3]))
@ -533,15 +534,15 @@ class ExtendedLeaf(object):
def get_tables(self):
tables = set()
alias = self._tables[0]._table
for context in self.context_stack:
alias += '__' + context[4]
table_full_alias = '"%s" as "%s"' % (context[1]._table, alias)
tables.add(table_full_alias)
links = []
for context in self.join_context:
links.append((context[1]._table, context[4]))
alias, alias_statement = generate_table_alias(self._models[0]._table, links)
tables.add(alias_statement)
return tables
def _get_context_debug(self):
names = ['"%s"."%s"="%s"."%s" (%s)' % (item[0]._table, item[2], item[1]._table, item[3], item[4]) for item in self.context_stack]
names = ['"%s"."%s"="%s"."%s" (%s)' % (item[0]._table, item[2], item[1]._table, item[3], item[4]) for item in self.join_context]
return names
# --------------------------------------------------
@ -589,20 +590,19 @@ class expression(object):
right after initialization.
:param exp: expression (using domain ('foo', '=', 'bar' format))
:param table: root table object
:param table: root model
:attr list result: list that will hold the result of the parsing
as a list of ExtendedLeaf
:attr list joins: list of join conditions, such as
(res_country_state."id" = res_partner."state_id")
:attr root_table: base table for the query
:attr root_model: base model for the query
:attr list expression: the domain expression, that will be normalized
and prepared
"""
self.has_unaccent = openerp.modules.registry.RegistryManager.get(cr.dbname).has_unaccent
self.result = []
self.joins = []
self.root_table = table
self.root_model = table
# normalize and prepare the expression for parsing
self.expression = distribute_not(normalize_domain(exp))
@ -621,7 +621,7 @@ class expression(object):
for table in leaf.get_tables():
if table not in tables:
tables.append(table)
table_name = '"%s"' % self.root_table._table
table_name = _quote(self.root_model._table)
if table_name not in tables:
tables.append(table_name)
return tables
@ -633,29 +633,30 @@ class expression(object):
def parse(self, cr, uid, context):
""" Transform the leaves of the expression
The principle is to pop elements from the left of a leaf stack. Each
leaf is processed. The processing is a if/elif list of various cases
that appear in the leafs (many2one, function fields, ...). Two results
can appear at the end of a leaf processing:
- the leaf is modified or new leafs introduced in the domain: they
are added at the left of the stack, to be processed next
The principle is to pop elements from a leaf stack one at a time.
Each leaf is processed. The processing is a if/elif list of various
cases that appear in the leafs (many2one, function fields, ...).
Two things can happen as a processing result:
- the leaf has been modified and/or new leafs have to be introduced
in the expression; they are pushed into the leaf stack, to be
processed right after
- the leaf is added to the result
Some var explanation:
:var obj working_table: table object, table containing the field
Some internal var explanation:
:var obj working_model: model object, model containing the field
(the name provided in the left operand)
:var list field_path: left operand seen as a path (foo.bar -> [foo, bar])
:var obj relational_table: relational table of a field (field._obj)
ex: res_partner.bank_ids -> res_partner_bank
:var obj relational_model: relational model of a field (field._obj)
ex: res_partner.bank_ids -> res.partner.bank
"""
def to_ids(value, relational_table, context=None, limit=None):
def to_ids(value, relational_model, context=None, limit=None):
""" Normalize a single id or name, or a list of those, into a list of ids
:param {int,long,basestring,list,tuple} value:
if int, long -> return [value]
if basestring, convert it into a list of basestrings, then
if list of basestring ->
perform a name_search on relational_table for each name
perform a name_search on relational_model for each name
return the list of related ids
"""
names = []
@ -666,7 +667,7 @@ class expression(object):
elif isinstance(value, (int, long)):
return [value]
if names:
name_get_list = [name_get[0] for name in names for name_get in relational_table.name_search(cr, uid, name, [], 'ilike', context=context, limit=limit)]
name_get_list = [name_get[0] for name in names for name_get in relational_model.name_search(cr, uid, name, [], 'ilike', context=context, limit=limit)]
return list(set(name_get_list))
return list(value)
@ -693,24 +694,31 @@ class expression(object):
return ids + recursive_children(ids2, model, parent_field)
return [(left, 'in', recursive_children(ids, left_model, parent or left_model._parent_name))]
def create_substitution_leaf(leaf, new_elements, new_table=None):
if new_table is None:
new_table = leaf.table
new_context_stack = [tuple(context) for context in leaf.context_stack]
new_leaf = ExtendedLeaf(new_elements, new_table, context_stack=new_context_stack)
def create_substitution_leaf(leaf, new_elements, new_model=None):
""" From a leaf, create a new leaf (based on the new_elements tuple
and new_model), that will have the same join context. Used to
insert equivalent leafs in the processing stack. """
if new_model is None:
new_model = leaf.model
new_join_context = [tuple(context) for context in leaf.join_context]
new_leaf = ExtendedLeaf(new_elements, new_model, join_context=new_join_context)
return new_leaf
def pop():
""" Pop a leaf to process. """
return self.stack.pop()
def push(leaf):
""" Push a leaf to be processed right after. """
self.stack.append(leaf)
def push_result(leaf):
""" Push a leaf to the results. This leaf has been fully processed
and validated. """
self.result.append(leaf)
self.result = []
self.stack = [ExtendedLeaf(leaf, self.root_table) for leaf in self.expression]
self.stack = [ExtendedLeaf(leaf, self.root_model) for leaf in self.expression]
# process from right to left; expression is from left to right
self.stack.reverse()
@ -719,7 +727,7 @@ class expression(object):
leaf = pop()
# Get working variables
working_table = leaf.table
working_model = leaf.model
if leaf.is_operator():
left, operator, right = leaf.leaf, None, None
elif leaf.is_true_leaf() or leaf.is_false_leaf():
@ -728,11 +736,11 @@ class expression(object):
else:
left, operator, right = leaf.leaf
field_path = left.split('.', 1)
field = working_table._columns.get(field_path[0])
field = working_model._columns.get(field_path[0])
if field and field._obj:
relational_table = working_table.pool.get(field._obj)
relational_model = working_model.pool.get(field._obj)
else:
relational_table = None
relational_model = None
# ----------------------------------------
# SIMPLE CASE
@ -746,7 +754,7 @@ class expression(object):
# ----------------------------------------
# FIELD NOT FOUND
# -> from inherits'd fields -> work on the related table, and add
# -> from inherits'd fields -> work on the related model, and add
# a join condition
# -> ('id', 'child_of', '..') -> use a 'to_ids'
# -> but is one on the _log_access special fields, add directly to
@ -755,19 +763,19 @@ class expression(object):
# -> else: crash
# ----------------------------------------
elif not field and field_path[0] in working_table._inherit_fields:
elif not field and field_path[0] in working_model._inherit_fields:
# comments about inherits'd fields
# { 'field_name': ('parent_model', 'm2o_field_to_reach_parent',
# field_column_obj, origina_parent_model), ... }
next_table = working_table.pool.get(working_table._inherit_fields[field_path[0]][0])
leaf.add_join_context(next_table, working_table._inherits[next_table._name], 'id', working_table._inherits[next_table._name])
next_model = working_model.pool.get(working_model._inherit_fields[field_path[0]][0])
leaf.add_join_context(next_model, working_model._inherits[next_model._name], 'id', working_model._inherits[next_model._name])
push(leaf)
elif not field and left == 'id' and operator == 'child_of':
ids2 = to_ids(right, working_table, context)
dom = child_of_domain(left, ids2, working_table)
ids2 = to_ids(right, working_model, context)
dom = child_of_domain(left, ids2, working_model)
for dom_leaf in reversed(dom):
new_leaf = create_substitution_leaf(leaf, dom_leaf, working_table)
new_leaf = create_substitution_leaf(leaf, dom_leaf, working_model)
push(new_leaf)
elif not field and field_path[0] in MAGIC_COLUMNS:
@ -791,32 +799,32 @@ class expression(object):
elif len(field_path) > 1 and field._type == 'many2one' and field._auto_join:
# res_partner.state_id = res_partner__state_id.id
leaf.add_join_context(relational_table, field_path[0], 'id', field_path[0])
push(create_substitution_leaf(leaf, (field_path[1], operator, right), relational_table))
leaf.add_join_context(relational_model, field_path[0], 'id', field_path[0])
push(create_substitution_leaf(leaf, (field_path[1], operator, right), relational_model))
elif len(field_path) > 1 and field._type == 'one2many' and field._auto_join:
# res_partner.id = res_partner__bank_ids.partner_id
leaf.add_join_context(relational_table, 'id', field._fields_id, field_path[0])
domain = field._domain(working_table) if callable(field._domain) else field._domain
push(create_substitution_leaf(leaf, (field_path[1], operator, right), relational_table))
leaf.add_join_context(relational_model, 'id', field._fields_id, field_path[0])
domain = field._domain(working_model) if callable(field._domain) else field._domain
push(create_substitution_leaf(leaf, (field_path[1], operator, right), relational_model))
if domain:
domain = normalize_domain(domain)
for elem in reversed(domain):
push(create_substitution_leaf(leaf, elem, relational_table))
push(create_substitution_leaf(leaf, AND_OPERATOR, relational_table))
push(create_substitution_leaf(leaf, elem, relational_model))
push(create_substitution_leaf(leaf, AND_OPERATOR, relational_model))
elif len(field_path) > 1 and field._auto_join:
raise NotImplementedError('_auto_join attribute not supported on many2many field %s' % (left))
elif len(field_path) > 1 and field._type == 'many2one':
right_ids = relational_table.search(cr, uid, [(field_path[1], operator, right)], context=context)
right_ids = relational_model.search(cr, uid, [(field_path[1], operator, right)], context=context)
leaf.leaf = (field_path[0], 'in', right_ids)
push(leaf)
# Making search easier when there is a left operand as field.o2m or field.m2m
elif len(field_path) > 1 and field._type in ['many2many', 'one2many']:
right_ids = relational_table.search(cr, uid, [(field_path[1], operator, right)], context=context)
table_ids = working_table.search(cr, uid, [(field_path[0], 'in', right_ids)], context=dict(context, active_test=False))
right_ids = relational_model.search(cr, uid, [(field_path[1], operator, right)], context=context)
table_ids = working_model.search(cr, uid, [(field_path[0], 'in', right_ids)], context=dict(context, active_test=False))
leaf.leaf = ('id', 'in', table_ids)
push(leaf)
@ -842,7 +850,7 @@ class expression(object):
elif isinstance(field, fields.function) and not field.store:
# this is a function field that is not stored
fct_domain = field.search(cr, uid, working_table, left, [leaf.leaf], context=context)
fct_domain = field.search(cr, uid, working_model, left, [leaf.leaf], context=context)
if not fct_domain:
leaf.leaf = TRUE_LEAF
push(leaf)
@ -850,26 +858,30 @@ class expression(object):
# we assume that the expression is valid
# we create a dummy leaf for forcing the parsing of the resulting expression
for domain_element in reversed(fct_domain):
push(create_substitution_leaf(leaf, domain_element, working_table))
# self.push(create_substitution_leaf(leaf, TRUE_LEAF, working_table))
# self.push(create_substitution_leaf(leaf, AND_OPERATOR, working_table))
push(create_substitution_leaf(leaf, domain_element, working_model))
# self.push(create_substitution_leaf(leaf, TRUE_LEAF, working_model))
# self.push(create_substitution_leaf(leaf, AND_OPERATOR, working_model))
# -------------------------------------------------
# RELATIONAL FIELDS
# -------------------------------------------------
# Applying recursivity on field(one2many)
elif field._type == 'one2many' and operator == 'child_of':
ids2 = to_ids(right, relational_table, context)
if field._obj != working_table._name:
dom = child_of_domain(left, ids2, relational_table, prefix=field._obj)
ids2 = to_ids(right, relational_model, context)
if field._obj != working_model._name:
dom = child_of_domain(left, ids2, relational_model, prefix=field._obj)
else:
dom = child_of_domain('id', ids2, working_table, parent=left)
dom = child_of_domain('id', ids2, working_model, parent=left)
for dom_leaf in reversed(dom):
push(create_substitution_leaf(leaf, dom_leaf, working_table))
push(create_substitution_leaf(leaf, dom_leaf, working_model))
elif field._type == 'one2many':
call_null = True
if right is not False:
if isinstance(right, basestring):
ids2 = [x[0] for x in relational_table.name_search(cr, uid, right, [], operator, context=context, limit=None)]
ids2 = [x[0] for x in relational_model.name_search(cr, uid, right, [], operator, context=context, limit=None)]
if ids2:
operator = 'in'
else:
@ -881,36 +893,36 @@ class expression(object):
if operator in ['like', 'ilike', 'in', '=']:
#no result found with given search criteria
call_null = False
push(create_substitution_leaf(leaf, FALSE_LEAF, working_table))
push(create_substitution_leaf(leaf, FALSE_LEAF, working_model))
else:
ids2 = select_from_where(cr, field._fields_id, relational_table._table, 'id', ids2, operator)
ids2 = select_from_where(cr, field._fields_id, relational_model._table, 'id', ids2, operator)
if ids2:
call_null = False
o2m_op = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in'
push(create_substitution_leaf(leaf, ('id', o2m_op, ids2), working_table))
push(create_substitution_leaf(leaf, ('id', o2m_op, ids2), working_model))
if call_null:
o2m_op = 'in' if operator in NEGATIVE_TERM_OPERATORS else 'not in'
push(create_substitution_leaf(leaf, ('id', o2m_op, select_distinct_from_where_not_null(cr, field._fields_id, relational_table._table)), working_table))
push(create_substitution_leaf(leaf, ('id', o2m_op, select_distinct_from_where_not_null(cr, field._fields_id, relational_model._table)), working_model))
elif field._type == 'many2many':
rel_table, rel_id1, rel_id2 = field._sql_names(working_table)
rel_table, rel_id1, rel_id2 = field._sql_names(working_model)
#FIXME
if operator == 'child_of':
def _rec_convert(ids):
if relational_table == working_table:
if relational_model == working_model:
return ids
return select_from_where(cr, rel_id1, rel_table, rel_id2, ids, operator)
ids2 = to_ids(right, relational_table, context)
dom = child_of_domain('id', ids2, relational_table)
ids2 = relational_table.search(cr, uid, dom, context=context)
push(create_substitution_leaf(leaf, ('id', 'in', _rec_convert(ids2)), working_table))
ids2 = to_ids(right, relational_model, context)
dom = child_of_domain('id', ids2, relational_model)
ids2 = relational_model.search(cr, uid, dom, context=context)
push(create_substitution_leaf(leaf, ('id', 'in', _rec_convert(ids2)), working_model))
else:
call_null_m2m = True
if right is not False:
if isinstance(right, basestring):
res_ids = [x[0] for x in relational_table.name_search(cr, uid, right, [], operator, context=context)]
res_ids = [x[0] for x in relational_model.name_search(cr, uid, right, [], operator, context=context)]
if res_ids:
operator = 'in'
else:
@ -922,29 +934,29 @@ class expression(object):
if operator in ['like', 'ilike', 'in', '=']:
#no result found with given search criteria
call_null_m2m = False
push(create_substitution_leaf(leaf, FALSE_LEAF, working_table))
push(create_substitution_leaf(leaf, FALSE_LEAF, working_model))
else:
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]), working_table))
push(create_substitution_leaf(leaf, ('id', m2m_op, select_from_where(cr, rel_id1, rel_table, rel_id2, res_ids, operator) or [0]), working_model))
if call_null_m2m:
m2m_op = 'in' if operator in NEGATIVE_TERM_OPERATORS else 'not in'
push(create_substitution_leaf(leaf, ('id', m2m_op, select_distinct_from_where_not_null(cr, rel_id1, rel_table)), working_table))
push(create_substitution_leaf(leaf, ('id', m2m_op, select_distinct_from_where_not_null(cr, rel_id1, rel_table)), working_model))
elif field._type == 'many2one':
if operator == 'child_of':
ids2 = to_ids(right, relational_table, context)
if field._obj != working_table._name:
dom = child_of_domain(left, ids2, relational_table, prefix=field._obj)
ids2 = to_ids(right, relational_model, context)
if field._obj != working_model._name:
dom = child_of_domain(left, ids2, relational_model, prefix=field._obj)
else:
dom = child_of_domain('id', ids2, working_table, parent=left)
dom = child_of_domain('id', ids2, working_model, parent=left)
for dom_leaf in reversed(dom):
push(create_substitution_leaf(leaf, dom_leaf, working_table))
push(create_substitution_leaf(leaf, dom_leaf, working_model))
else:
def _get_expression(relational_table, cr, uid, left, right, operator, context=None):
def _get_expression(relational_model, cr, uid, left, right, operator, context=None):
if context is None:
context = {}
c = context.copy()
@ -959,29 +971,32 @@ class expression(object):
operator = dict_op[operator]
elif isinstance(right, list) and operator in ['!=', '=']: # for domain (FIELD,'=',['value1','value2'])
operator = dict_op[operator]
res_ids = [x[0] for x in relational_table.name_search(cr, uid, right, [], operator, limit=None, context=c)]
res_ids = [x[0] for x in relational_model.name_search(cr, uid, right, [], operator, limit=None, context=c)]
if operator in NEGATIVE_TERM_OPERATORS:
res_ids.append(False) # TODO this should not be appended if False was in 'right'
return (left, 'in', res_ids)
# resolve string-based m2o criterion into IDs
if isinstance(right, basestring) or \
right and isinstance(right, (tuple, list)) and all(isinstance(item, basestring) for item in right):
push(create_substitution_leaf(leaf, _get_expression(relational_table, cr, uid, left, right, operator, context=context), working_table))
push(create_substitution_leaf(leaf, _get_expression(relational_model, cr, uid, left, right, operator, context=context), working_model))
else:
# right == [] or right == False and all other cases are handled by __leaf_to_sql()
push_result(leaf)
else:
# other field type
# add the time part to datetime field when it's not there:
if field._type == 'datetime' and right and len(right) == 10:
# -------------------------------------------------
# OTHER FIELDS
# -> datetime fields: manage time part of the datetime
# field when it is not there
# -> manage translatable fields
# -------------------------------------------------
else:
if field._type == 'datetime' and right and len(right) == 10:
if operator in ('>', '>='):
right += ' 00:00:00'
elif operator in ('<', '<='):
right += ' 23:59:59'
push(create_substitution_leaf(leaf, (left, operator, right), working_table))
push(create_substitution_leaf(leaf, (left, operator, right), working_model))
elif field.translate:
need_wildcard = operator in ('like', 'ilike', 'not like', 'not ilike')
@ -1001,45 +1016,45 @@ class expression(object):
subselect += ' AND value ' + sql_operator + ' ' + " (" + instr + ")" \
') UNION (' \
' SELECT id' \
' FROM "' + working_table._table + '"' \
' FROM "' + working_model._table + '"' \
' WHERE "' + left + '" ' + sql_operator + ' ' + " (" + instr + "))"
else:
subselect += ' AND value ' + sql_operator + instr + \
') UNION (' \
' SELECT id' \
' FROM "' + working_table._table + '"' \
' FROM "' + working_model._table + '"' \
' WHERE "' + left + '" ' + sql_operator + instr + ")"
params = [working_table._name + ',' + left,
params = [working_model._name + ',' + left,
context.get('lang', False) or 'en_US',
'model',
right,
right,
]
push(create_substitution_leaf(leaf, ('id', 'inselect', (subselect, params)), working_table))
push(create_substitution_leaf(leaf, ('id', 'inselect', (subselect, params)), working_model))
else:
push_result(leaf)
# ----------------------------------------
# END OF PARSING FULL DOMAIN
# -> generate joins
# ----------------------------------------
# Generate joins
joins = set()
for leaf in self.result:
joins |= set(leaf.get_join_conditions())
self.joins = list(joins)
def __leaf_to_sql(self, eleaf):
table = eleaf.table
model = eleaf.model
leaf = eleaf.leaf
left, operator, right = leaf
# final sanity checks - should never fail
assert operator in (TERM_OPERATORS + ('inselect',)), \
"Invalid operator %r in domain term %r" % (operator, leaf)
assert leaf in (TRUE_LEAF, FALSE_LEAF) or left in table._all_columns \
assert leaf in (TRUE_LEAF, FALSE_LEAF) or left in model._all_columns \
or left in MAGIC_COLUMNS, "Invalid field %r in domain term %r" % (left, leaf)
table_alias = '"%s"' % (eleaf.generate_alias())
@ -1079,7 +1094,7 @@ class expression(object):
if left == 'id':
instr = ','.join(['%s'] * len(params))
else:
instr = ','.join([table._columns[left]._symbol_set[0]] * len(params))
instr = ','.join([model._columns[left]._symbol_set[0]] * len(params))
query = '(%s."%s" %s (%s))' % (table_alias, left, operator, instr)
else:
# The case for (left, 'in', []) or (left, 'not in', []).
@ -1094,7 +1109,7 @@ class expression(object):
else: # Must not happen
raise ValueError("Invalid domain term %r" % (leaf,))
elif right == False and (left in table._columns) and table._columns[left]._type == "boolean" and (operator == '='):
elif right == False and (left in model._columns) and model._columns[left]._type == "boolean" and (operator == '='):
query = '(%s."%s" IS NULL or %s."%s" = false )' % (table_alias, left, table_alias, left)
params = []
@ -1102,7 +1117,7 @@ class expression(object):
query = '%s."%s" IS NULL ' % (table_alias, left)
params = []
elif right == False and (left in table._columns) and table._columns[left]._type == "boolean" and (operator == '!='):
elif right == False and (left in model._columns) and model._columns[left]._type == "boolean" and (operator == '!='):
query = '(%s."%s" IS NOT NULL and %s."%s" != false)' % (table_alias, left, table_alias, left)
params = []
@ -1117,7 +1132,7 @@ class expression(object):
params = []
else:
# '=?' behaves like '=' in other cases
query, params = self.__leaf_to_sql((left, '=', right), table)
query, params = self.__leaf_to_sql((left, '=', right), model)
elif left == 'id':
query = '%s.id %s %%s' % (table_alias, operator)
@ -1127,8 +1142,8 @@ class expression(object):
need_wildcard = operator in ('like', 'ilike', 'not like', 'not ilike')
sql_operator = {'=like': 'like', '=ilike': 'ilike'}.get(operator, operator)
if left in table._columns:
format = need_wildcard and '%s' or table._columns[left]._symbol_set[0]
if left in model._columns:
format = need_wildcard and '%s' or model._columns[left]._symbol_set[0]
if self.has_unaccent and sql_operator in ('ilike', 'not ilike'):
query = '(unaccent(%s."%s") %s unaccent(%s))' % (table_alias, left, sql_operator, format)
else:
@ -1149,8 +1164,8 @@ class expression(object):
str_utf8 = str(right)
params = '%%%s%%' % str_utf8
add_null = not str_utf8
elif left in table._columns:
params = table._columns[left]._symbol_set[1](right)
elif left in model._columns:
params = model._columns[left]._symbol_set[1](right)
if add_null:
query = '(%s OR %s."%s" IS NULL)' % (query, table_alias, left)