summaryrefslogtreecommitdiffstats
path: root/webapp/django/db/models/sql/where.py
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/django/db/models/sql/where.py')
-rw-r--r--webapp/django/db/models/sql/where.py213
1 files changed, 213 insertions, 0 deletions
diff --git a/webapp/django/db/models/sql/where.py b/webapp/django/db/models/sql/where.py
new file mode 100644
index 0000000000..662d99a4a2
--- /dev/null
+++ b/webapp/django/db/models/sql/where.py
@@ -0,0 +1,213 @@
+"""
+Code to manage the creation and SQL rendering of 'where' constraints.
+"""
+import datetime
+
+from django.utils import tree
+from django.db import connection
+from django.db.models.fields import Field
+from django.db.models.query_utils import QueryWrapper
+from datastructures import EmptyResultSet, FullResultSet
+
+# Connection types
+AND = 'AND'
+OR = 'OR'
+
+class WhereNode(tree.Node):
+ """
+ Used to represent the SQL where-clause.
+
+ The class is tied to the Query class that created it (in order to create
+ the correct SQL).
+
+ The children in this tree are usually either Q-like objects or lists of
+ [table_alias, field_name, db_type, lookup_type, value_annotation,
+ params]. However, a child could also be any class with as_sql() and
+ relabel_aliases() methods.
+ """
+ default = AND
+
+ def add(self, data, connector):
+ """
+ Add a node to the where-tree. If the data is a list or tuple, it is
+ expected to be of the form (alias, col_name, field_obj, lookup_type,
+ value), which is then slightly munged before being stored (to avoid
+ storing any reference to field objects). Otherwise, the 'data' is
+ stored unchanged and can be anything with an 'as_sql()' method.
+ """
+ # Because of circular imports, we need to import this here.
+ from django.db.models.base import ObjectDoesNotExist
+
+ if not isinstance(data, (list, tuple)):
+ super(WhereNode, self).add(data, connector)
+ return
+
+ alias, col, field, lookup_type, value = data
+ try:
+ if field:
+ params = field.get_db_prep_lookup(lookup_type, value)
+ db_type = field.db_type()
+ else:
+ # This is possible when we add a comparison to NULL sometimes
+ # (we don't really need to waste time looking up the associated
+ # field object).
+ params = Field().get_db_prep_lookup(lookup_type, value)
+ db_type = None
+ except ObjectDoesNotExist:
+ # This can happen when trying to insert a reference to a null pk.
+ # We break out of the normal path and indicate there's nothing to
+ # match.
+ super(WhereNode, self).add(NothingNode(), connector)
+ return
+ if isinstance(value, datetime.datetime):
+ annotation = datetime.datetime
+ else:
+ annotation = bool(value)
+ super(WhereNode, self).add((alias, col, db_type, lookup_type,
+ annotation, params), connector)
+
+ def as_sql(self, qn=None):
+ """
+ Returns the SQL version of the where clause and the value to be
+ substituted in. Returns None, None if this node is empty.
+
+ If 'node' is provided, that is the root of the SQL generation
+ (generally not needed except by the internal implementation for
+ recursion).
+ """
+ if not qn:
+ qn = connection.ops.quote_name
+ if not self.children:
+ return None, []
+ result = []
+ result_params = []
+ empty = True
+ for child in self.children:
+ try:
+ if hasattr(child, 'as_sql'):
+ sql, params = child.as_sql(qn=qn)
+ else:
+ # A leaf node in the tree.
+ sql, params = self.make_atom(child, qn)
+ except EmptyResultSet:
+ if self.connector == AND and not self.negated:
+ # We can bail out early in this particular case (only).
+ raise
+ elif self.negated:
+ empty = False
+ continue
+ except FullResultSet:
+ if self.connector == OR:
+ if self.negated:
+ empty = True
+ break
+ # We match everything. No need for any constraints.
+ return '', []
+ if self.negated:
+ empty = True
+ continue
+ empty = False
+ if sql:
+ result.append(sql)
+ result_params.extend(params)
+ if empty:
+ raise EmptyResultSet
+
+ conn = ' %s ' % self.connector
+ sql_string = conn.join(result)
+ if sql_string:
+ if self.negated:
+ sql_string = 'NOT (%s)' % sql_string
+ elif len(self.children) != 1:
+ sql_string = '(%s)' % sql_string
+ return sql_string, result_params
+
+ def make_atom(self, child, qn):
+ """
+ Turn a tuple (table_alias, column_name, db_type, lookup_type,
+ value_annot, params) into valid SQL.
+
+ Returns the string for the SQL fragment and the parameters to use for
+ it.
+ """
+ table_alias, name, db_type, lookup_type, value_annot, params = child
+ if table_alias:
+ lhs = '%s.%s' % (qn(table_alias), qn(name))
+ else:
+ lhs = qn(name)
+ field_sql = connection.ops.field_cast_sql(db_type) % lhs
+
+ if value_annot is datetime.datetime:
+ cast_sql = connection.ops.datetime_cast_sql()
+ else:
+ cast_sql = '%s'
+
+ if isinstance(params, QueryWrapper):
+ extra, params = params.data
+ else:
+ extra = ''
+
+ if lookup_type in connection.operators:
+ format = "%s %%s %s" % (connection.ops.lookup_cast(lookup_type),
+ extra)
+ return (format % (field_sql,
+ connection.operators[lookup_type] % cast_sql), params)
+
+ if lookup_type == 'in':
+ if not value_annot:
+ raise EmptyResultSet
+ if extra:
+ return ('%s IN %s' % (field_sql, extra), params)
+ return ('%s IN (%s)' % (field_sql, ', '.join(['%s'] * len(params))),
+ params)
+ elif lookup_type in ('range', 'year'):
+ return ('%s BETWEEN %%s and %%s' % field_sql, params)
+ elif lookup_type in ('month', 'day'):
+ return ('%s = %%s' % connection.ops.date_extract_sql(lookup_type,
+ field_sql), params)
+ elif lookup_type == 'isnull':
+ return ('%s IS %sNULL' % (field_sql,
+ (not value_annot and 'NOT ' or '')), ())
+ elif lookup_type == 'search':
+ return (connection.ops.fulltext_search_sql(field_sql), params)
+ elif lookup_type in ('regex', 'iregex'):
+ return connection.ops.regex_lookup(lookup_type) % (field_sql, cast_sql), params
+
+ raise TypeError('Invalid lookup_type: %r' % lookup_type)
+
+ def relabel_aliases(self, change_map, node=None):
+ """
+ Relabels the alias values of any children. 'change_map' is a dictionary
+ mapping old (current) alias values to the new values.
+ """
+ if not node:
+ node = self
+ for pos, child in enumerate(node.children):
+ if hasattr(child, 'relabel_aliases'):
+ child.relabel_aliases(change_map)
+ elif isinstance(child, tree.Node):
+ self.relabel_aliases(change_map, child)
+ else:
+ if child[0] in change_map:
+ node.children[pos] = (change_map[child[0]],) + child[1:]
+
+class EverythingNode(object):
+ """
+ A node that matches everything.
+ """
+ def as_sql(self, qn=None):
+ raise FullResultSet
+
+ def relabel_aliases(self, change_map, node=None):
+ return
+
+class NothingNode(object):
+ """
+ A node that matches nothing.
+ """
+ def as_sql(self, qn=None):
+ raise EmptyResultSet
+
+ def relabel_aliases(self, change_map, node=None):
+ return
+