|
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 WhereNode(tree.Node): |
|
17 """ |
|
18 Used to represent the SQL where-clause. |
|
19 |
|
20 The class is tied to the Query class that created it (in order to create |
|
21 the corret SQL). |
|
22 |
|
23 The children in this tree are usually either Q-like objects or lists of |
|
24 [table_alias, field_name, field_class, lookup_type, value]. However, a |
|
25 child could also be any class with as_sql() and relabel_aliases() methods. |
|
26 """ |
|
27 default = AND |
|
28 |
|
29 def as_sql(self, node=None, qn=None): |
|
30 """ |
|
31 Returns the SQL version of the where clause and the value to be |
|
32 substituted in. Returns None, None if this node is empty. |
|
33 |
|
34 If 'node' is provided, that is the root of the SQL generation |
|
35 (generally not needed except by the internal implementation for |
|
36 recursion). |
|
37 """ |
|
38 if node is None: |
|
39 node = self |
|
40 if not qn: |
|
41 qn = connection.ops.quote_name |
|
42 if not node.children: |
|
43 return None, [] |
|
44 result = [] |
|
45 result_params = [] |
|
46 empty = True |
|
47 for child in node.children: |
|
48 try: |
|
49 if hasattr(child, 'as_sql'): |
|
50 sql, params = child.as_sql(qn=qn) |
|
51 format = '(%s)' |
|
52 elif isinstance(child, tree.Node): |
|
53 sql, params = self.as_sql(child, qn) |
|
54 if child.negated: |
|
55 format = 'NOT (%s)' |
|
56 elif len(child.children) == 1: |
|
57 format = '%s' |
|
58 else: |
|
59 format = '(%s)' |
|
60 else: |
|
61 sql, params = self.make_atom(child, qn) |
|
62 format = '%s' |
|
63 except EmptyResultSet: |
|
64 if node.connector == AND and not node.negated: |
|
65 # We can bail out early in this particular case (only). |
|
66 raise |
|
67 elif node.negated: |
|
68 empty = False |
|
69 continue |
|
70 except FullResultSet: |
|
71 if self.connector == OR: |
|
72 if node.negated: |
|
73 empty = True |
|
74 break |
|
75 # We match everything. No need for any constraints. |
|
76 return '', [] |
|
77 if node.negated: |
|
78 empty = True |
|
79 continue |
|
80 empty = False |
|
81 if sql: |
|
82 result.append(format % sql) |
|
83 result_params.extend(params) |
|
84 if empty: |
|
85 raise EmptyResultSet |
|
86 conn = ' %s ' % node.connector |
|
87 return conn.join(result), result_params |
|
88 |
|
89 def make_atom(self, child, qn): |
|
90 """ |
|
91 Turn a tuple (table_alias, field_name, field_class, lookup_type, value) |
|
92 into valid SQL. |
|
93 |
|
94 Returns the string for the SQL fragment and the parameters to use for |
|
95 it. |
|
96 """ |
|
97 table_alias, name, field, lookup_type, value = child |
|
98 if table_alias: |
|
99 lhs = '%s.%s' % (qn(table_alias), qn(name)) |
|
100 else: |
|
101 lhs = qn(name) |
|
102 db_type = field and field.db_type() or None |
|
103 field_sql = connection.ops.field_cast_sql(db_type) % lhs |
|
104 |
|
105 if isinstance(value, datetime.datetime): |
|
106 cast_sql = connection.ops.datetime_cast_sql() |
|
107 else: |
|
108 cast_sql = '%s' |
|
109 |
|
110 if field: |
|
111 params = field.get_db_prep_lookup(lookup_type, value) |
|
112 else: |
|
113 params = Field().get_db_prep_lookup(lookup_type, value) |
|
114 if isinstance(params, QueryWrapper): |
|
115 extra, params = params.data |
|
116 else: |
|
117 extra = '' |
|
118 |
|
119 if lookup_type in connection.operators: |
|
120 format = "%s %%s %s" % (connection.ops.lookup_cast(lookup_type), |
|
121 extra) |
|
122 return (format % (field_sql, |
|
123 connection.operators[lookup_type] % cast_sql), params) |
|
124 |
|
125 if lookup_type == 'in': |
|
126 if not value: |
|
127 raise EmptyResultSet |
|
128 if extra: |
|
129 return ('%s IN %s' % (field_sql, extra), params) |
|
130 return ('%s IN (%s)' % (field_sql, ', '.join(['%s'] * len(value))), |
|
131 params) |
|
132 elif lookup_type in ('range', 'year'): |
|
133 return ('%s BETWEEN %%s and %%s' % field_sql, params) |
|
134 elif lookup_type in ('month', 'day'): |
|
135 return ('%s = %%s' % connection.ops.date_extract_sql(lookup_type, |
|
136 field_sql), params) |
|
137 elif lookup_type == 'isnull': |
|
138 return ('%s IS %sNULL' % (field_sql, (not value and 'NOT ' or '')), |
|
139 params) |
|
140 elif lookup_type == 'search': |
|
141 return (connection.ops.fulltext_search_sql(field_sql), params) |
|
142 elif lookup_type in ('regex', 'iregex'): |
|
143 return connection.ops.regex_lookup(lookup_type) % (field_sql, cast_sql), params |
|
144 |
|
145 raise TypeError('Invalid lookup_type: %r' % lookup_type) |
|
146 |
|
147 def relabel_aliases(self, change_map, node=None): |
|
148 """ |
|
149 Relabels the alias values of any children. 'change_map' is a dictionary |
|
150 mapping old (current) alias values to the new values. |
|
151 """ |
|
152 if not node: |
|
153 node = self |
|
154 for pos, child in enumerate(node.children): |
|
155 if hasattr(child, 'relabel_aliases'): |
|
156 child.relabel_aliases(change_map) |
|
157 elif isinstance(child, tree.Node): |
|
158 self.relabel_aliases(change_map, child) |
|
159 else: |
|
160 if child[0] in change_map: |
|
161 node.children[pos] = (change_map[child[0]],) + child[1:] |
|
162 |
|
163 class EverythingNode(object): |
|
164 """ |
|
165 A node that matches everything. |
|
166 """ |
|
167 def as_sql(self, qn=None): |
|
168 raise FullResultSet |
|
169 |
|
170 def relabel_aliases(self, change_map, node=None): |
|
171 return |