|
1 """ |
|
2 Code to manage the creation and SQL rendering of 'where' constraints. |
|
3 """ |
|
4 import datetime |
|
5 |
|
6 from django.utils import tree |
|
7 from django.db import connection |
|
8 from django.db.models.fields import Field |
|
9 from django.db.models.query_utils import QueryWrapper |
|
10 from datastructures import EmptyResultSet, FullResultSet |
|
11 |
|
12 # Connection types |
|
13 AND = 'AND' |
|
14 OR = 'OR' |
|
15 |
|
16 class EmptyShortCircuit(Exception): |
|
17 """ |
|
18 Internal exception used to indicate that a "matches nothing" node should be |
|
19 added to the where-clause. |
|
20 """ |
|
21 pass |
|
22 |
|
23 class WhereNode(tree.Node): |
|
24 """ |
|
25 Used to represent the SQL where-clause. |
|
26 |
|
27 The class is tied to the Query class that created it (in order to create |
|
28 the correct SQL). |
|
29 |
|
30 The children in this tree are usually either Q-like objects or lists of |
|
31 [table_alias, field_name, db_type, lookup_type, value_annotation, |
|
32 params]. However, a child could also be any class with as_sql() and |
|
33 relabel_aliases() methods. |
|
34 """ |
|
35 default = AND |
|
36 |
|
37 def add(self, data, connector): |
|
38 """ |
|
39 Add a node to the where-tree. If the data is a list or tuple, it is |
|
40 expected to be of the form (alias, col_name, field_obj, lookup_type, |
|
41 value), which is then slightly munged before being stored (to avoid |
|
42 storing any reference to field objects). Otherwise, the 'data' is |
|
43 stored unchanged and can be anything with an 'as_sql()' method. |
|
44 """ |
|
45 if not isinstance(data, (list, tuple)): |
|
46 super(WhereNode, self).add(data, connector) |
|
47 return |
|
48 |
|
49 obj, lookup_type, value = data |
|
50 if hasattr(value, '__iter__') and hasattr(value, 'next'): |
|
51 # Consume any generators immediately, so that we can determine |
|
52 # emptiness and transform any non-empty values correctly. |
|
53 value = list(value) |
|
54 if hasattr(obj, "process"): |
|
55 try: |
|
56 obj, params = obj.process(lookup_type, value) |
|
57 except (EmptyShortCircuit, EmptyResultSet): |
|
58 # There are situations where we want to short-circuit any |
|
59 # comparisons and make sure that nothing is returned. One |
|
60 # example is when checking for a NULL pk value, or the |
|
61 # equivalent. |
|
62 super(WhereNode, self).add(NothingNode(), connector) |
|
63 return |
|
64 else: |
|
65 params = Field().get_db_prep_lookup(lookup_type, value) |
|
66 |
|
67 # The "annotation" parameter is used to pass auxilliary information |
|
68 # about the value(s) to the query construction. Specifically, datetime |
|
69 # and empty values need special handling. Other types could be used |
|
70 # here in the future (using Python types is suggested for consistency). |
|
71 if isinstance(value, datetime.datetime): |
|
72 annotation = datetime.datetime |
|
73 elif hasattr(value, 'value_annotation'): |
|
74 annotation = value.value_annotation |
|
75 else: |
|
76 annotation = bool(value) |
|
77 |
|
78 super(WhereNode, self).add((obj, lookup_type, annotation, params), |
|
79 connector) |
|
80 |
|
81 def as_sql(self, qn=None): |
|
82 """ |
|
83 Returns the SQL version of the where clause and the value to be |
|
84 substituted in. Returns None, None if this node is empty. |
|
85 |
|
86 If 'node' is provided, that is the root of the SQL generation |
|
87 (generally not needed except by the internal implementation for |
|
88 recursion). |
|
89 """ |
|
90 if not qn: |
|
91 qn = connection.ops.quote_name |
|
92 if not self.children: |
|
93 return None, [] |
|
94 result = [] |
|
95 result_params = [] |
|
96 empty = True |
|
97 for child in self.children: |
|
98 try: |
|
99 if hasattr(child, 'as_sql'): |
|
100 sql, params = child.as_sql(qn=qn) |
|
101 else: |
|
102 # A leaf node in the tree. |
|
103 sql, params = self.make_atom(child, qn) |
|
104 |
|
105 except EmptyResultSet: |
|
106 if self.connector == AND and not self.negated: |
|
107 # We can bail out early in this particular case (only). |
|
108 raise |
|
109 elif self.negated: |
|
110 empty = False |
|
111 continue |
|
112 except FullResultSet: |
|
113 if self.connector == OR: |
|
114 if self.negated: |
|
115 empty = True |
|
116 break |
|
117 # We match everything. No need for any constraints. |
|
118 return '', [] |
|
119 if self.negated: |
|
120 empty = True |
|
121 continue |
|
122 |
|
123 empty = False |
|
124 if sql: |
|
125 result.append(sql) |
|
126 result_params.extend(params) |
|
127 if empty: |
|
128 raise EmptyResultSet |
|
129 |
|
130 conn = ' %s ' % self.connector |
|
131 sql_string = conn.join(result) |
|
132 if sql_string: |
|
133 if self.negated: |
|
134 sql_string = 'NOT (%s)' % sql_string |
|
135 elif len(self.children) != 1: |
|
136 sql_string = '(%s)' % sql_string |
|
137 return sql_string, result_params |
|
138 |
|
139 def make_atom(self, child, qn): |
|
140 """ |
|
141 Turn a tuple (table_alias, column_name, db_type, lookup_type, |
|
142 value_annot, params) into valid SQL. |
|
143 |
|
144 Returns the string for the SQL fragment and the parameters to use for |
|
145 it. |
|
146 """ |
|
147 lvalue, lookup_type, value_annot, params = child |
|
148 if isinstance(lvalue, tuple): |
|
149 # A direct database column lookup. |
|
150 field_sql = self.sql_for_columns(lvalue, qn) |
|
151 else: |
|
152 # A smart object with an as_sql() method. |
|
153 field_sql = lvalue.as_sql(quote_func=qn) |
|
154 |
|
155 if value_annot is datetime.datetime: |
|
156 cast_sql = connection.ops.datetime_cast_sql() |
|
157 else: |
|
158 cast_sql = '%s' |
|
159 |
|
160 if hasattr(params, 'as_sql'): |
|
161 extra, params = params.as_sql(qn) |
|
162 cast_sql = '' |
|
163 else: |
|
164 extra = '' |
|
165 |
|
166 if lookup_type in connection.operators: |
|
167 format = "%s %%s %%s" % (connection.ops.lookup_cast(lookup_type),) |
|
168 return (format % (field_sql, |
|
169 connection.operators[lookup_type] % cast_sql, |
|
170 extra), params) |
|
171 |
|
172 if lookup_type == 'in': |
|
173 if not value_annot: |
|
174 raise EmptyResultSet |
|
175 if extra: |
|
176 return ('%s IN %s' % (field_sql, extra), params) |
|
177 return ('%s IN (%s)' % (field_sql, ', '.join(['%s'] * len(params))), |
|
178 params) |
|
179 elif lookup_type in ('range', 'year'): |
|
180 return ('%s BETWEEN %%s and %%s' % field_sql, params) |
|
181 elif lookup_type in ('month', 'day', 'week_day'): |
|
182 return ('%s = %%s' % connection.ops.date_extract_sql(lookup_type, field_sql), |
|
183 params) |
|
184 elif lookup_type == 'isnull': |
|
185 return ('%s IS %sNULL' % (field_sql, |
|
186 (not value_annot and 'NOT ' or '')), ()) |
|
187 elif lookup_type == 'search': |
|
188 return (connection.ops.fulltext_search_sql(field_sql), params) |
|
189 elif lookup_type in ('regex', 'iregex'): |
|
190 return connection.ops.regex_lookup(lookup_type) % (field_sql, cast_sql), params |
|
191 |
|
192 raise TypeError('Invalid lookup_type: %r' % lookup_type) |
|
193 |
|
194 def sql_for_columns(self, data, qn): |
|
195 """ |
|
196 Returns the SQL fragment used for the left-hand side of a column |
|
197 constraint (for example, the "T1.foo" portion in the clause |
|
198 "WHERE ... T1.foo = 6"). |
|
199 """ |
|
200 table_alias, name, db_type = data |
|
201 if table_alias: |
|
202 lhs = '%s.%s' % (qn(table_alias), qn(name)) |
|
203 else: |
|
204 lhs = qn(name) |
|
205 return connection.ops.field_cast_sql(db_type) % lhs |
|
206 |
|
207 def relabel_aliases(self, change_map, node=None): |
|
208 """ |
|
209 Relabels the alias values of any children. 'change_map' is a dictionary |
|
210 mapping old (current) alias values to the new values. |
|
211 """ |
|
212 if not node: |
|
213 node = self |
|
214 for pos, child in enumerate(node.children): |
|
215 if hasattr(child, 'relabel_aliases'): |
|
216 child.relabel_aliases(change_map) |
|
217 elif isinstance(child, tree.Node): |
|
218 self.relabel_aliases(change_map, child) |
|
219 else: |
|
220 if isinstance(child[0], (list, tuple)): |
|
221 elt = list(child[0]) |
|
222 if elt[0] in change_map: |
|
223 elt[0] = change_map[elt[0]] |
|
224 node.children[pos] = (tuple(elt),) + child[1:] |
|
225 else: |
|
226 child[0].relabel_aliases(change_map) |
|
227 |
|
228 # Check if the query value also requires relabelling |
|
229 if hasattr(child[3], 'relabel_aliases'): |
|
230 child[3].relabel_aliases(change_map) |
|
231 |
|
232 class EverythingNode(object): |
|
233 """ |
|
234 A node that matches everything. |
|
235 """ |
|
236 def as_sql(self, qn=None): |
|
237 raise FullResultSet |
|
238 |
|
239 def relabel_aliases(self, change_map, node=None): |
|
240 return |
|
241 |
|
242 class NothingNode(object): |
|
243 """ |
|
244 A node that matches nothing. |
|
245 """ |
|
246 def as_sql(self, qn=None): |
|
247 raise EmptyResultSet |
|
248 |
|
249 def relabel_aliases(self, change_map, node=None): |
|
250 return |
|
251 |
|
252 class Constraint(object): |
|
253 """ |
|
254 An object that can be passed to WhereNode.add() and knows how to |
|
255 pre-process itself prior to including in the WhereNode. |
|
256 """ |
|
257 def __init__(self, alias, col, field): |
|
258 self.alias, self.col, self.field = alias, col, field |
|
259 |
|
260 def process(self, lookup_type, value): |
|
261 """ |
|
262 Returns a tuple of data suitable for inclusion in a WhereNode |
|
263 instance. |
|
264 """ |
|
265 # Because of circular imports, we need to import this here. |
|
266 from django.db.models.base import ObjectDoesNotExist |
|
267 try: |
|
268 if self.field: |
|
269 params = self.field.get_db_prep_lookup(lookup_type, value) |
|
270 db_type = self.field.db_type() |
|
271 else: |
|
272 # This branch is used at times when we add a comparison to NULL |
|
273 # (we don't really want to waste time looking up the associated |
|
274 # field object at the calling location). |
|
275 params = Field().get_db_prep_lookup(lookup_type, value) |
|
276 db_type = None |
|
277 except ObjectDoesNotExist: |
|
278 raise EmptyShortCircuit |
|
279 |
|
280 return (self.alias, self.col, db_type), params |
|
281 |