app/django/db/models/sql/where.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     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