1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2010 OpenERP S.A. http://www.openerp.com
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
25 if '"' not in to_quote:
26 return '"%s"' % to_quote
32 Dumb implementation of a Query object, using 3 string lists so far
33 for backwards compatibility with the (table, where_clause, where_params) previously used.
35 TODO: To be improved after v6.0 to rewrite part of the ORM and add support for:
36 - auto-generated multiple table aliases
37 - multiple joins to the same table with different conditions
38 - dynamic right-hand-side values in domains (e.g. a.name = a.description)
42 def __init__(self, tables=None, where_clause=None, where_clause_params=None, joins=None):
44 # holds the list of tables joined using default JOIN.
45 # the table names are stored double-quoted (backwards compatibility)
46 self.tables = tables or []
48 # holds the list of WHERE clause elements, to be joined with
49 # 'AND' when generating the final query
50 self.where_clause = where_clause or []
52 # holds the parameters for the formatting of `where_clause`, to be
53 # passed to psycopg's execute method.
54 self.where_clause_params = where_clause_params or []
56 # holds table joins done explicitly, supporting outer joins. The JOIN
57 # condition should not be in `where_clause`. The dict is used as follows:
60 # ('table_b', 'table_a_col1', 'table_b_col', 'LEFT JOIN'),
61 # ('table_c', 'table_a_col2', 'table_c_col', 'LEFT JOIN'),
62 # ('table_d', 'table_a_col3', 'table_d_col', 'JOIN'),
65 # which should lead to the following SQL:
66 # SELECT ... FROM "table_a" LEFT JOIN "table_b" ON ("table_a"."table_a_col1" = "table_b"."table_b_col")
67 # LEFT JOIN "table_c" ON ("table_a"."table_a_col2" = "table_c"."table_c_col")
68 self.joins = joins or {}
70 def _get_table_aliases(self):
71 from openerp.osv.expression import get_alias_from_query
72 return [get_alias_from_query(from_statement)[1] for from_statement in self.tables]
74 def _get_alias_mapping(self):
75 from openerp.osv.expression import get_alias_from_query
77 for table in self.tables:
78 alias, statement = get_alias_from_query(table)
79 mapping[statement] = table
82 def add_join(self, connection, implicit=True, outer=False):
83 """ Join a destination table to the current table.
85 :param implicit: False if the join is an explicit join. This allows
86 to fall back on the previous implementation of ``join`` before
87 OpenERP 7.0. It therefore adds the JOIN specified in ``connection``
88 If True, the join is done implicitely, by adding the table alias
89 in the from clause and the join condition in the where clause
90 of the query. Implicit joins do not handle outer parameter.
91 :param connection: a tuple ``(lhs, table, lhs_col, col, link)``.
92 The join corresponds to the SQL equivalent of::
94 (lhs.lhs_col = table.col)
96 Note that all connection elements are strings. Please refer to expression.py for more details about joins.
98 :param outer: True if a LEFT OUTER JOIN should be used, if possible
99 (no promotion to OUTER JOIN is supported in case the JOIN
100 was already present in the query, as for the moment
101 implicit INNER JOINs are only connected from NON-NULL
102 columns so it would not be correct (e.g. for
103 ``_inherits`` or when a domain criterion explicitly
106 from openerp.osv.expression import generate_table_alias
107 (lhs, table, lhs_col, col, link) = connection
108 alias, alias_statement = generate_table_alias(lhs, [(table, link)])
111 if alias_statement not in self.tables:
112 self.tables.append(alias_statement)
113 condition = '("%s"."%s" = "%s"."%s")' % (lhs, lhs_col, alias, col)
114 self.where_clause.append(condition)
118 return alias, alias_statement
120 aliases = self._get_table_aliases()
121 assert lhs in aliases, "Left-hand-side table %s must already be part of the query tables %s!" % (lhs, str(self.tables))
122 if alias_statement in self.tables:
123 # already joined, must ignore (promotion to outer and multiple joins not supported yet)
127 self.tables.append(alias_statement)
128 self.joins.setdefault(lhs, []).append((alias, lhs_col, col, outer and 'LEFT JOIN' or 'JOIN'))
129 return alias, alias_statement
132 """ Returns (query_from, query_where, query_params). """
133 from openerp.osv.expression import get_alias_from_query
135 tables_to_process = list(self.tables)
136 alias_mapping = self._get_alias_mapping()
138 def add_joins_for_table(table, query_from):
139 for (dest_table, lhs_col, col, join) in self.joins.get(table, []):
140 tables_to_process.remove(alias_mapping[dest_table])
141 query_from += ' %s %s ON ("%s"."%s" = "%s"."%s")' % \
142 (join, alias_mapping[dest_table], table, lhs_col, dest_table, col)
143 query_from = add_joins_for_table(dest_table, query_from)
146 for table in tables_to_process:
148 table_alias = get_alias_from_query(table)[1]
149 if table_alias in self.joins:
150 query_from = add_joins_for_table(table_alias, query_from)
152 query_from = query_from[:-1] # drop last comma
153 return query_from, " AND ".join(self.where_clause), self.where_clause_params
156 return '<osv.Query: "SELECT ... FROM %s WHERE %s" with params: %r>' % self.get_sql()
158 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: