[IMP] base : Improved the typos.
[odoo/odoo.git] / openerp / osv / query.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2010 OpenERP S.A. http://www.openerp.com
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21
22 #.apidoc title: Query object
23
24 def _quote(to_quote):
25     if '"' not in to_quote:
26         return '"%s"' % to_quote
27     return to_quote
28
29
30 class Query(object):
31     """
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.
34
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)
39       - etc.
40     """
41
42     def __init__(self, tables=None, where_clause=None, where_clause_params=None, joins=None):
43
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 []
47
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 []
51
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 []
55
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:
58         #   self.joins = {
59         #                    'table_a': [
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'),
63         #                               ]
64         #                 }
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 {}
69
70     def join(self, connection, outer=False):
71         """Adds the JOIN specified in ``connection``.
72
73         :param connection: a tuple ``(lhs, table, lhs_col, col)``.
74                            The join corresponds to the SQL equivalent of::
75
76                                 ``(lhs.lhs_col = table.col)``
77
78         :param outer: True if a LEFT OUTER JOIN should be used, if possible
79                       (no promotion to OUTER JOIN is supported in case the JOIN
80                        was already present in the query, as for the moment
81                        implicit INNER JOINs are only connected from NON-NULL
82                        columns so it would not be correct (e.g. for
83                        ``_inherits`` or when a domain criterion explicitly
84                        adds filtering)
85         """
86         (lhs, table, lhs_col, col) = connection
87         lhs = _quote(lhs)
88         table = _quote(table)
89         assert lhs in self.tables, "Left-hand-side table must already be part of the query!"
90         if table in self.tables:
91             # already joined, must ignore (promotion to outer and multiple joins not supported yet)
92             pass
93         else:
94             # add JOIN
95             self.tables.append(table)
96             self.joins.setdefault(lhs, []).append((table, lhs_col, col, outer and 'LEFT JOIN' or 'JOIN'))
97         return self
98
99     def get_sql(self):
100         """Returns (query_from, query_where, query_params)"""
101         query_from = ''
102         tables_to_process = list(self.tables)
103
104         def add_joins_for_table(table, query_from):
105             for (dest_table, lhs_col, col, join) in self.joins.get(table,[]):
106                 tables_to_process.remove(dest_table)
107                 query_from += ' %s %s ON (%s."%s" = %s."%s")' % \
108                     (join, dest_table, table, lhs_col, dest_table, col)
109                 query_from = add_joins_for_table(dest_table, query_from)
110             return query_from
111
112         for table in tables_to_process:
113             query_from += table
114             if table in self.joins:
115                 query_from = add_joins_for_table(table, query_from)
116             query_from += ','
117         query_from = query_from[:-1] # drop last comma
118         return (query_from, " AND ".join(self.where_clause), self.where_clause_params)
119
120     def __str__(self):
121         return '<osv.Query: "SELECT ... FROM %s WHERE %s" with params: %r>' % self.get_sql()
122
123 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: