Merged the queryset-refactor branch into trunk.
[fdr-django.git] / django / db / models / sql / where.py
blobc8857a01fe468cc0df1687b0de358e8f6588d82d
1 """
2 Code to manage the creation and SQL rendering of 'where' constraints.
3 """
4 import datetime
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
12 # Connection types
13 AND = 'AND'
14 OR = 'OR'
16 class WhereNode(tree.Node):
17 """
18 Used to represent the SQL where-clause.
20 The class is tied to the Query class that created it (in order to create
21 the corret SQL).
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
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.
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 len(child.children) == 1:
55 format = '%s'
56 else:
57 format = '(%s)'
58 if child.negated:
59 format = 'NOT %s' % format
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
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.
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
105 if isinstance(value, datetime.datetime):
106 cast_sql = connection.ops.datetime_cast_sql()
107 else:
108 cast_sql = '%s'
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 = ''
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)
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
145 raise TypeError('Invalid lookup_type: %r' % lookup_type)
147 def relabel_aliases(self, change_map, node=None):
149 Relabels the alias values of any children. 'change_map' is a dictionary
150 mapping old (current) alias values to the new values.
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:]
163 class EverythingNode(object):
165 A node that matches everything.
167 def as_sql(self, qn=None):
168 raise FullResultSet
170 def relabel_aliases(self, change_map, node=None):
171 return