+
+ # --------------------------------------------------
+ # Join / Context manipulation
+ # running examples:
+ # - res_users.name, like, foo: name is on res_partner, not on res_users
+ # - res_partner.bank_ids.name, like, foo: bank_ids is a one2many with _auto_join
+ # - res_partner.state_id.name, like, foo: state_id is a many2one with _auto_join
+ # A join:
+ # - link between src_table and dst_table, using src_field and dst_field
+ # i.e.: inherits: res_users.partner_id = res_partner.id
+ # i.e.: one2many: res_partner.id = res_partner_bank.partner_id
+ # i.e.: many2one: res_partner.state_id = res_country_state.id
+ # - done in the context of a field
+ # i.e.: inherits: 'partner_id'
+ # i.e.: one2many: 'bank_ids'
+ # i.e.: many2one: 'state_id'
+ # - table names use aliases: initial table followed by the context field
+ # names, joined using a '__'
+ # i.e.: inherits: res_partner as res_users__partner_id
+ # i.e.: one2many: res_partner_bank as res_partner__bank_ids
+ # i.e.: many2one: res_country_state as res_partner__state_id
+ # - join condition use aliases
+ # i.e.: inherits: res_users.partner_id = res_users__partner_id.id
+ # i.e.: one2many: res_partner.id = res_partner__bank_ids.parr_id
+ # i.e.: many2one: res_partner.state_id = res_partner__state_id.id
+ # Variables explanation:
+ # - src_table: working table before the join
+ # -> res_users, res_partner, res_partner
+ # - dst_table: working table after the join
+ # -> res_partner, res_partner_bank, res_country_state
+ # - src_table_link_name: field name used to link the src table, not
+ # necessarily a field (because 'id' is not a field instance)
+ # i.e.: inherits: 'partner_id', found in the inherits of the current table
+ # i.e.: one2many: 'id', not a field
+ # i.e.: many2one: 'state_id', the current field name
+ # - dst_table_link_name: field name used to link the dst table, not
+ # necessarily a field (because 'id' is not a field instance)
+ # i.e.: inherits: 'id', not a field
+ # i.e.: one2many: 'partner_id', _fields_id of the current field
+ # i.e.: many2one: 'id', not a field
+ # - context_field_name: field name used as a context to make the alias
+ # i.e.: inherits: 'partner_id': found in the inherits of the current table
+ # i.e.: one2many: 'bank_ids': current field name
+ # 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)``
+
+ where
+
+ lhs
+ source (left hand) model
+ model
+ destination (right hand) model
+ lhs_col
+ source model column for join condition
+ col
+ destination model column for join condition
+ 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.join_context]
+ alias, alias_statement = generate_table_alias(self._models[0]._table, links)
+ return alias
+
+ 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, model, lhs_col, col, link)``
+
+ After adding the join, the model of the current leaf is updated.
+ """
+ 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._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]))
+ return conditions
+
+ def get_tables(self):
+ tables = set()
+ 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.join_context]
+ return names
+
+ # --------------------------------------------------
+ # Leaf manipulation
+ # --------------------------------------------------
+
+ def check_leaf(self):
+ """ Leaf validity rules:
+ - a valid leaf is an operator or a leaf
+ - a valid leaf has a field objects unless
+ - it is not a tuple
+ - it is an inherited field
+ - left is id, operator is 'child_of'
+ - left is in MAGIC_COLUMNS
+ """
+ if not is_operator(self.leaf) and not is_leaf(self.leaf, True):
+ raise ValueError("Invalid leaf %s" % str(self.leaf))
+
+ def is_operator(self):
+ return is_operator(self.leaf)
+
+ def is_true_leaf(self):
+ return self.leaf == TRUE_LEAF
+
+ def is_false_leaf(self):
+ return self.leaf == FALSE_LEAF
+
+ def is_leaf(self, internal=False):
+ return is_leaf(self.leaf, internal=internal)
+
+ def normalize_leaf(self):
+ self.leaf = normalize_leaf(self.leaf)
+ return True
+
+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
+
+class expression(object):
+ """ Parse a domain expression
+ Use a real polish notation
+ Leafs are still in a ('foo', '=', 'bar') format
+ For more info: http://christophe-simonis-at-tiny.blogspot.com/2008/08/new-new-domain-notation.html