web/lib/django/db/models/sql/where.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     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