web/lib/django/db/models/sql/where.py
author ymh <ymh.work@gmail.com>
Thu, 05 Aug 2010 17:28:09 +0200
changeset 50 012451a812f1
parent 38 77b6da96e6f1
permissions -rw-r--r--
Merge with a2711e44ba5de8b1675d7e0ee6aaa4a6c56a9b46
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
38
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     1
"""
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     2
Code to manage the creation and SQL rendering of 'where' constraints.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     3
"""
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     4
import datetime
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     5
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     6
from django.utils import tree
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     7
from django.db.models.fields import Field
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     8
from django.db.models.query_utils import QueryWrapper
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     9
from datastructures import EmptyResultSet, FullResultSet
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    10
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    11
# Connection types
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    12
AND = 'AND'
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    13
OR = 'OR'
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    14
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    15
class EmptyShortCircuit(Exception):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    16
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    17
    Internal exception used to indicate that a "matches nothing" node should be
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    18
    added to the where-clause.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    19
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    20
    pass
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    21
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    22
class WhereNode(tree.Node):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    23
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    24
    Used to represent the SQL where-clause.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    25
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    26
    The class is tied to the Query class that created it (in order to create
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    27
    the correct SQL).
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    28
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    29
    The children in this tree are usually either Q-like objects or lists of
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    30
    [table_alias, field_name, db_type, lookup_type, value_annotation,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    31
    params]. However, a child could also be any class with as_sql() and
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    32
    relabel_aliases() methods.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    33
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    34
    default = AND
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    35
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    36
    def add(self, data, connector):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    37
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    38
        Add a node to the where-tree. If the data is a list or tuple, it is
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    39
        expected to be of the form (alias, col_name, field_obj, lookup_type,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    40
        value), which is then slightly munged before being stored (to avoid
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    41
        storing any reference to field objects). Otherwise, the 'data' is
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    42
        stored unchanged and can be anything with an 'as_sql()' method.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    43
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    44
        if not isinstance(data, (list, tuple)):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    45
            super(WhereNode, self).add(data, connector)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    46
            return
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    47
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    48
        obj, lookup_type, value = data
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    49
        if hasattr(value, '__iter__') and hasattr(value, 'next'):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    50
            # Consume any generators immediately, so that we can determine
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    51
            # emptiness and transform any non-empty values correctly.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    52
            value = list(value)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    53
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    54
        # The "annotation" parameter is used to pass auxilliary information
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    55
        # about the value(s) to the query construction. Specifically, datetime
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    56
        # and empty values need special handling. Other types could be used
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    57
        # here in the future (using Python types is suggested for consistency).
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    58
        if isinstance(value, datetime.datetime):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    59
            annotation = datetime.datetime
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    60
        elif hasattr(value, 'value_annotation'):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    61
            annotation = value.value_annotation
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    62
        else:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    63
            annotation = bool(value)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    64
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    65
        if hasattr(obj, "prepare"):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    66
            value = obj.prepare(lookup_type, value)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    67
            super(WhereNode, self).add((obj, lookup_type, annotation, value),
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    68
                connector)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    69
            return
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    70
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    71
        super(WhereNode, self).add((obj, lookup_type, annotation, value),
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    72
                connector)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    73
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    74
    def as_sql(self, qn, connection):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    75
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    76
        Returns the SQL version of the where clause and the value to be
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    77
        substituted in. Returns None, None if this node is empty.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    78
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    79
        If 'node' is provided, that is the root of the SQL generation
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    80
        (generally not needed except by the internal implementation for
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    81
        recursion).
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    82
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    83
        if not self.children:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    84
            return None, []
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    85
        result = []
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    86
        result_params = []
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    87
        empty = True
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    88
        for child in self.children:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    89
            try:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    90
                if hasattr(child, 'as_sql'):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    91
                    sql, params = child.as_sql(qn=qn, connection=connection)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    92
                else:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    93
                    # A leaf node in the tree.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    94
                    sql, params = self.make_atom(child, qn, connection)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    95
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    96
            except EmptyResultSet:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    97
                if self.connector == AND and not self.negated:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    98
                    # We can bail out early in this particular case (only).
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    99
                    raise
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   100
                elif self.negated:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   101
                    empty = False
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   102
                continue
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   103
            except FullResultSet:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   104
                if self.connector == OR:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   105
                    if self.negated:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   106
                        empty = True
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   107
                        break
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   108
                    # We match everything. No need for any constraints.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   109
                    return '', []
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   110
                if self.negated:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   111
                    empty = True
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   112
                continue
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   113
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   114
            empty = False
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   115
            if sql:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   116
                result.append(sql)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   117
                result_params.extend(params)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   118
        if empty:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   119
            raise EmptyResultSet
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   120
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   121
        conn = ' %s ' % self.connector
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   122
        sql_string = conn.join(result)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   123
        if sql_string:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   124
            if self.negated:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   125
                sql_string = 'NOT (%s)' % sql_string
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   126
            elif len(self.children) != 1:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   127
                sql_string = '(%s)' % sql_string
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   128
        return sql_string, result_params
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   129
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   130
    def make_atom(self, child, qn, connection):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   131
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   132
        Turn a tuple (table_alias, column_name, db_type, lookup_type,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   133
        value_annot, params) into valid SQL.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   134
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   135
        Returns the string for the SQL fragment and the parameters to use for
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   136
        it.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   137
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   138
        lvalue, lookup_type, value_annot, params_or_value = child
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   139
        if hasattr(lvalue, 'process'):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   140
            try:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   141
                lvalue, params = lvalue.process(lookup_type, params_or_value, connection)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   142
            except EmptyShortCircuit:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   143
                raise EmptyResultSet
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   144
        else:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   145
            params = Field().get_db_prep_lookup(lookup_type, params_or_value,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   146
                connection=connection, prepared=True)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   147
        if isinstance(lvalue, tuple):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   148
            # A direct database column lookup.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   149
            field_sql = self.sql_for_columns(lvalue, qn, connection)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   150
        else:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   151
            # A smart object with an as_sql() method.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   152
            field_sql = lvalue.as_sql(qn, connection)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   153
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   154
        if value_annot is datetime.datetime:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   155
            cast_sql = connection.ops.datetime_cast_sql()
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   156
        else:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   157
            cast_sql = '%s'
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   158
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   159
        if hasattr(params, 'as_sql'):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   160
            extra, params = params.as_sql(qn, connection)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   161
            cast_sql = ''
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   162
        else:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   163
            extra = ''
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   164
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   165
        if (len(params) == 1 and params[0] == '' and lookup_type == 'exact'
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   166
            and connection.features.interprets_empty_strings_as_nulls):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   167
            lookup_type = 'isnull'
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   168
            value_annot = True
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   169
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   170
        if lookup_type in connection.operators:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   171
            format = "%s %%s %%s" % (connection.ops.lookup_cast(lookup_type),)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   172
            return (format % (field_sql,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   173
                              connection.operators[lookup_type] % cast_sql,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   174
                              extra), params)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   175
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   176
        if lookup_type == 'in':
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   177
            if not value_annot:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   178
                raise EmptyResultSet
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   179
            if extra:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   180
                return ('%s IN %s' % (field_sql, extra), params)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   181
            return ('%s IN (%s)' % (field_sql, ', '.join(['%s'] * len(params))),
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   182
                    params)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   183
        elif lookup_type in ('range', 'year'):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   184
            return ('%s BETWEEN %%s and %%s' % field_sql, params)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   185
        elif lookup_type in ('month', 'day', 'week_day'):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   186
            return ('%s = %%s' % connection.ops.date_extract_sql(lookup_type, field_sql),
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   187
                    params)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   188
        elif lookup_type == 'isnull':
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   189
            return ('%s IS %sNULL' % (field_sql,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   190
                (not value_annot and 'NOT ' or '')), ())
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   191
        elif lookup_type == 'search':
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   192
            return (connection.ops.fulltext_search_sql(field_sql), params)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   193
        elif lookup_type in ('regex', 'iregex'):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   194
            return connection.ops.regex_lookup(lookup_type) % (field_sql, cast_sql), params
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   195
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   196
        raise TypeError('Invalid lookup_type: %r' % lookup_type)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   197
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   198
    def sql_for_columns(self, data, qn, connection):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   199
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   200
        Returns the SQL fragment used for the left-hand side of a column
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   201
        constraint (for example, the "T1.foo" portion in the clause
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   202
        "WHERE ... T1.foo = 6").
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   203
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   204
        table_alias, name, db_type = data
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   205
        if table_alias:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   206
            lhs = '%s.%s' % (qn(table_alias), qn(name))
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   207
        else:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   208
            lhs = qn(name)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   209
        return connection.ops.field_cast_sql(db_type) % lhs
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   210
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   211
    def relabel_aliases(self, change_map, node=None):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   212
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   213
        Relabels the alias values of any children. 'change_map' is a dictionary
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   214
        mapping old (current) alias values to the new values.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   215
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   216
        if not node:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   217
            node = self
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   218
        for pos, child in enumerate(node.children):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   219
            if hasattr(child, 'relabel_aliases'):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   220
                child.relabel_aliases(change_map)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   221
            elif isinstance(child, tree.Node):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   222
                self.relabel_aliases(change_map, child)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   223
            elif isinstance(child, (list, tuple)):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   224
                if isinstance(child[0], (list, tuple)):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   225
                    elt = list(child[0])
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   226
                    if elt[0] in change_map:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   227
                        elt[0] = change_map[elt[0]]
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   228
                        node.children[pos] = (tuple(elt),) + child[1:]
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   229
                else:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   230
                    child[0].relabel_aliases(change_map)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   231
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   232
                # Check if the query value also requires relabelling
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   233
                if hasattr(child[3], 'relabel_aliases'):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   234
                    child[3].relabel_aliases(change_map)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   235
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   236
class EverythingNode(object):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   237
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   238
    A node that matches everything.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   239
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   240
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   241
    def as_sql(self, qn=None, connection=None):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   242
        raise FullResultSet
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   243
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   244
    def relabel_aliases(self, change_map, node=None):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   245
        return
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   246
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   247
class NothingNode(object):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   248
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   249
    A node that matches nothing.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   250
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   251
    def as_sql(self, qn=None, connection=None):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   252
        raise EmptyResultSet
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   253
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   254
    def relabel_aliases(self, change_map, node=None):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   255
        return
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   256
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   257
class ExtraWhere(object):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   258
    def __init__(self, sqls, params):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   259
        self.sqls = sqls
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   260
        self.params = params
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   261
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   262
    def as_sql(self, qn=None, connection=None):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   263
        return " AND ".join(self.sqls), tuple(self.params or ())
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   264
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   265
class Constraint(object):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   266
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   267
    An object that can be passed to WhereNode.add() and knows how to
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   268
    pre-process itself prior to including in the WhereNode.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   269
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   270
    def __init__(self, alias, col, field):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   271
        self.alias, self.col, self.field = alias, col, field
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   272
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   273
    def __getstate__(self):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   274
        """Save the state of the Constraint for pickling.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   275
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   276
        Fields aren't necessarily pickleable, because they can have
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   277
        callable default values. So, instead of pickling the field
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   278
        store a reference so we can restore it manually
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   279
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   280
        obj_dict = self.__dict__.copy()
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   281
        if self.field:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   282
            obj_dict['model'] = self.field.model
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   283
            obj_dict['field_name'] = self.field.name
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   284
        del obj_dict['field']
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   285
        return obj_dict
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   286
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   287
    def __setstate__(self, data):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   288
        """Restore the constraint """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   289
        model = data.pop('model', None)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   290
        field_name = data.pop('field_name', None)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   291
        self.__dict__.update(data)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   292
        if model is not None:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   293
            self.field = model._meta.get_field(field_name)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   294
        else:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   295
            self.field = None
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   296
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   297
    def prepare(self, lookup_type, value):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   298
        if self.field:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   299
            return self.field.get_prep_lookup(lookup_type, value)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   300
        return value
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   301
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   302
    def process(self, lookup_type, value, connection):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   303
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   304
        Returns a tuple of data suitable for inclusion in a WhereNode
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   305
        instance.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   306
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   307
        # Because of circular imports, we need to import this here.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   308
        from django.db.models.base import ObjectDoesNotExist
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   309
        try:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   310
            if self.field:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   311
                params = self.field.get_db_prep_lookup(lookup_type, value,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   312
                    connection=connection, prepared=True)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   313
                db_type = self.field.db_type(connection=connection)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   314
            else:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   315
                # This branch is used at times when we add a comparison to NULL
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   316
                # (we don't really want to waste time looking up the associated
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   317
                # field object at the calling location).
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   318
                params = Field().get_db_prep_lookup(lookup_type, value,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   319
                    connection=connection, prepared=True)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   320
                db_type = None
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   321
        except ObjectDoesNotExist:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   322
            raise EmptyShortCircuit
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   323
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   324
        return (self.alias, self.col, db_type), params
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   325
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   326
    def relabel_aliases(self, change_map):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   327
        if self.alias in change_map:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   328
            self.alias = change_map[self.alias]