mirror of https://github.com/django/django.git
Removed legacy ORM lookup support per deprecation timeline; refs #16187.
This commit is contained in:
parent
5792e6a88c
commit
5008a4db44
|
@ -24,7 +24,7 @@ from django.db.models.sql.constants import (QUERY_TERMS, ORDER_DIR, SINGLE,
|
||||||
ORDER_PATTERN, INNER, LOUTER)
|
ORDER_PATTERN, INNER, LOUTER)
|
||||||
from django.db.models.sql.datastructures import (
|
from django.db.models.sql.datastructures import (
|
||||||
EmptyResultSet, Empty, MultiJoin, Join, BaseTable)
|
EmptyResultSet, Empty, MultiJoin, Join, BaseTable)
|
||||||
from django.db.models.sql.where import (WhereNode, Constraint, EverythingNode,
|
from django.db.models.sql.where import (WhereNode, EverythingNode,
|
||||||
ExtraWhere, AND, OR, EmptyWhere)
|
ExtraWhere, AND, OR, EmptyWhere)
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.deprecation import RemovedInDjango20Warning
|
from django.utils.deprecation import RemovedInDjango20Warning
|
||||||
|
@ -1130,10 +1130,6 @@ class Query(object):
|
||||||
clause = self.where_class()
|
clause = self.where_class()
|
||||||
if reffed_aggregate:
|
if reffed_aggregate:
|
||||||
condition = self.build_lookup(lookups, reffed_aggregate, value)
|
condition = self.build_lookup(lookups, reffed_aggregate, value)
|
||||||
if not condition:
|
|
||||||
# Backwards compat for custom lookups
|
|
||||||
assert len(lookups) == 1
|
|
||||||
condition = (reffed_aggregate, lookups[0], value)
|
|
||||||
clause.add(condition, AND)
|
clause.add(condition, AND)
|
||||||
return clause, []
|
return clause, []
|
||||||
|
|
||||||
|
@ -1176,20 +1172,7 @@ class Query(object):
|
||||||
else:
|
else:
|
||||||
col = targets[0].get_col(alias, field)
|
col = targets[0].get_col(alias, field)
|
||||||
condition = self.build_lookup(lookups, col, value)
|
condition = self.build_lookup(lookups, col, value)
|
||||||
if not condition:
|
lookup_type = condition.lookup_name
|
||||||
# Backwards compat for custom lookups
|
|
||||||
if lookups[0] not in self.query_terms:
|
|
||||||
raise FieldError(
|
|
||||||
"Join on field '%s' not permitted. Did you "
|
|
||||||
"misspell '%s' for the lookup type?" %
|
|
||||||
(col.output_field.name, lookups[0]))
|
|
||||||
if len(lookups) > 1:
|
|
||||||
raise FieldError("Nested lookup '%s' not supported." %
|
|
||||||
LOOKUP_SEP.join(lookups))
|
|
||||||
condition = (Constraint(alias, targets[0].column, field), lookups[0], value)
|
|
||||||
lookup_type = lookups[-1]
|
|
||||||
else:
|
|
||||||
lookup_type = condition.lookup_name
|
|
||||||
|
|
||||||
clause.add(condition, AND)
|
clause.add(condition, AND)
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,8 @@
|
||||||
"""
|
"""
|
||||||
Code to manage the creation and SQL rendering of 'where' constraints.
|
Code to manage the creation and SQL rendering of 'where' constraints.
|
||||||
"""
|
"""
|
||||||
|
from django.db.models.sql.datastructures import EmptyResultSet
|
||||||
import collections
|
|
||||||
import datetime
|
|
||||||
from itertools import repeat
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db.models.fields import DateTimeField, Field
|
|
||||||
from django.db.models.sql.datastructures import EmptyResultSet, Empty
|
|
||||||
from django.utils.deprecation import RemovedInDjango19Warning
|
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.six.moves import range
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils import tree
|
from django.utils import tree
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,10 +26,9 @@ class WhereNode(tree.Node):
|
||||||
The class is tied to the Query class that created it (in order to create
|
The class is tied to the Query class that created it (in order to create
|
||||||
the correct SQL).
|
the correct SQL).
|
||||||
|
|
||||||
A child is usually a tuple of:
|
A child is usually an expression producing boolean values. Most likely the
|
||||||
(Constraint(alias, targetcol, field), lookup_type, value)
|
expression is a Lookup instance, but other types of objects fulfilling the
|
||||||
where value can be either raw Python value, or Query, ExpressionNode or
|
required API could be used too (for example, sql.where.EverythingNode).
|
||||||
something else knowing how to turn itself into SQL.
|
|
||||||
|
|
||||||
However, a child could also be any class with as_sql() and either
|
However, a child could also be any class with as_sql() and either
|
||||||
relabeled_clone() method or relabel_aliases() and clone() methods. The
|
relabeled_clone() method or relabel_aliases() and clone() methods. The
|
||||||
|
@ -49,39 +37,6 @@ class WhereNode(tree.Node):
|
||||||
"""
|
"""
|
||||||
default = AND
|
default = AND
|
||||||
|
|
||||||
def _prepare_data(self, data):
|
|
||||||
"""
|
|
||||||
Prepare data for addition to the tree. If the data is a list or tuple,
|
|
||||||
it is expected to be of the form (obj, lookup_type, value), where obj
|
|
||||||
is a Constraint object, and is then slightly munged before being
|
|
||||||
stored (to avoid storing any reference to field objects). Otherwise,
|
|
||||||
the 'data' is stored unchanged and can be any class with an 'as_sql()'
|
|
||||||
method.
|
|
||||||
"""
|
|
||||||
if not isinstance(data, (list, tuple)):
|
|
||||||
return data
|
|
||||||
obj, lookup_type, value = data
|
|
||||||
if isinstance(value, collections.Iterator):
|
|
||||||
# Consume any generators immediately, so that we can determine
|
|
||||||
# emptiness and transform any non-empty values correctly.
|
|
||||||
value = list(value)
|
|
||||||
|
|
||||||
# The "value_annotation" parameter is used to pass auxiliary information
|
|
||||||
# about the value(s) to the query construction. Specifically, datetime
|
|
||||||
# and empty values need special handling. Other types could be used
|
|
||||||
# here in the future (using Python types is suggested for consistency).
|
|
||||||
if (isinstance(value, datetime.datetime)
|
|
||||||
or (isinstance(obj.field, DateTimeField) and lookup_type != 'isnull')):
|
|
||||||
value_annotation = datetime.datetime
|
|
||||||
elif hasattr(value, 'value_annotation'):
|
|
||||||
value_annotation = value.value_annotation
|
|
||||||
else:
|
|
||||||
value_annotation = bool(value)
|
|
||||||
|
|
||||||
if hasattr(obj, 'prepare'):
|
|
||||||
value = obj.prepare(lookup_type, value)
|
|
||||||
return (obj, lookup_type, value_annotation, value)
|
|
||||||
|
|
||||||
def as_sql(self, compiler, connection):
|
def as_sql(self, compiler, connection):
|
||||||
"""
|
"""
|
||||||
Returns the SQL version of the where clause and the value to be
|
Returns the SQL version of the where clause and the value to be
|
||||||
|
@ -102,11 +57,7 @@ class WhereNode(tree.Node):
|
||||||
|
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
try:
|
try:
|
||||||
if hasattr(child, 'as_sql'):
|
sql, params = compiler.compile(child)
|
||||||
sql, params = compiler.compile(child)
|
|
||||||
else:
|
|
||||||
# A leaf node in the tree.
|
|
||||||
sql, params = self.make_atom(child, compiler, connection)
|
|
||||||
except EmptyResultSet:
|
except EmptyResultSet:
|
||||||
nothing_childs += 1
|
nothing_childs += 1
|
||||||
else:
|
else:
|
||||||
|
@ -157,125 +108,9 @@ class WhereNode(tree.Node):
|
||||||
def get_group_by_cols(self):
|
def get_group_by_cols(self):
|
||||||
cols = []
|
cols = []
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
if hasattr(child, 'get_group_by_cols'):
|
cols.extend(child.get_group_by_cols())
|
||||||
cols.extend(child.get_group_by_cols())
|
|
||||||
else:
|
|
||||||
if isinstance(child[0], Constraint):
|
|
||||||
cols.append((child[0].alias, child[0].col))
|
|
||||||
if hasattr(child[3], 'get_group_by_cols'):
|
|
||||||
cols.extend(child[3].get_group_by_cols())
|
|
||||||
return cols
|
return cols
|
||||||
|
|
||||||
def make_atom(self, child, compiler, connection):
|
|
||||||
"""
|
|
||||||
Turn a tuple (Constraint(table_alias, column_name, db_type),
|
|
||||||
lookup_type, value_annotation, params) into valid SQL.
|
|
||||||
|
|
||||||
The first item of the tuple may also be an Aggregate.
|
|
||||||
|
|
||||||
Returns the string for the SQL fragment and the parameters to use for
|
|
||||||
it.
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
"The make_atom() method will be removed in Django 1.9. Use Lookup class instead.",
|
|
||||||
RemovedInDjango19Warning)
|
|
||||||
lvalue, lookup_type, value_annotation, params_or_value = child
|
|
||||||
field_internal_type = lvalue.field.get_internal_type() if lvalue.field else None
|
|
||||||
|
|
||||||
if isinstance(lvalue, Constraint):
|
|
||||||
try:
|
|
||||||
lvalue, params = lvalue.process(lookup_type, params_or_value, connection)
|
|
||||||
except EmptyShortCircuit:
|
|
||||||
raise EmptyResultSet
|
|
||||||
else:
|
|
||||||
raise TypeError("'make_atom' expects a Constraint as the first "
|
|
||||||
"item of its 'child' argument.")
|
|
||||||
|
|
||||||
if isinstance(lvalue, tuple):
|
|
||||||
# A direct database column lookup.
|
|
||||||
field_sql, field_params = self.sql_for_columns(lvalue, compiler, connection, field_internal_type), []
|
|
||||||
else:
|
|
||||||
# A smart object with an as_sql() method.
|
|
||||||
field_sql, field_params = compiler.compile(lvalue)
|
|
||||||
|
|
||||||
is_datetime_field = value_annotation is datetime.datetime
|
|
||||||
cast_sql = connection.ops.datetime_cast_sql() if is_datetime_field else '%s'
|
|
||||||
|
|
||||||
if hasattr(params, 'as_sql'):
|
|
||||||
extra, params = compiler.compile(params)
|
|
||||||
cast_sql = ''
|
|
||||||
else:
|
|
||||||
extra = ''
|
|
||||||
|
|
||||||
params = field_params + params
|
|
||||||
|
|
||||||
if (len(params) == 1 and params[0] == '' and lookup_type == 'exact'
|
|
||||||
and connection.features.interprets_empty_strings_as_nulls):
|
|
||||||
lookup_type = 'isnull'
|
|
||||||
value_annotation = True
|
|
||||||
|
|
||||||
if lookup_type in connection.operators:
|
|
||||||
format = "%s %%s %%s" % (connection.ops.lookup_cast(lookup_type),)
|
|
||||||
return (format % (field_sql,
|
|
||||||
connection.operators[lookup_type] % cast_sql,
|
|
||||||
extra), params)
|
|
||||||
|
|
||||||
if lookup_type == 'in':
|
|
||||||
if not value_annotation:
|
|
||||||
raise EmptyResultSet
|
|
||||||
if extra:
|
|
||||||
return ('%s IN %s' % (field_sql, extra), params)
|
|
||||||
max_in_list_size = connection.ops.max_in_list_size()
|
|
||||||
if max_in_list_size and len(params) > max_in_list_size:
|
|
||||||
# Break up the params list into an OR of manageable chunks.
|
|
||||||
in_clause_elements = ['(']
|
|
||||||
for offset in range(0, len(params), max_in_list_size):
|
|
||||||
if offset > 0:
|
|
||||||
in_clause_elements.append(' OR ')
|
|
||||||
in_clause_elements.append('%s IN (' % field_sql)
|
|
||||||
group_size = min(len(params) - offset, max_in_list_size)
|
|
||||||
param_group = ', '.join(repeat('%s', group_size))
|
|
||||||
in_clause_elements.append(param_group)
|
|
||||||
in_clause_elements.append(')')
|
|
||||||
in_clause_elements.append(')')
|
|
||||||
return ''.join(in_clause_elements), params
|
|
||||||
else:
|
|
||||||
return ('%s IN (%s)' % (field_sql,
|
|
||||||
', '.join(repeat('%s', len(params)))),
|
|
||||||
params)
|
|
||||||
elif lookup_type in ('range', 'year'):
|
|
||||||
return ('%s BETWEEN %%s and %%s' % field_sql, params)
|
|
||||||
elif is_datetime_field and lookup_type in ('month', 'day', 'week_day',
|
|
||||||
'hour', 'minute', 'second'):
|
|
||||||
tzname = timezone.get_current_timezone_name() if settings.USE_TZ else None
|
|
||||||
sql, tz_params = connection.ops.datetime_extract_sql(lookup_type, field_sql, tzname)
|
|
||||||
return ('%s = %%s' % sql, tz_params + params)
|
|
||||||
elif lookup_type in ('month', 'day', 'week_day'):
|
|
||||||
return ('%s = %%s'
|
|
||||||
% connection.ops.date_extract_sql(lookup_type, field_sql), params)
|
|
||||||
elif lookup_type == 'isnull':
|
|
||||||
assert value_annotation in (True, False), "Invalid value_annotation for isnull"
|
|
||||||
return ('%s IS %sNULL' % (field_sql, ('' if value_annotation else 'NOT ')), ())
|
|
||||||
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 sql_for_columns(self, data, qn, connection, internal_type=None):
|
|
||||||
"""
|
|
||||||
Returns the SQL fragment used for the left-hand side of a column
|
|
||||||
constraint (for example, the "T1.foo" portion in the clause
|
|
||||||
"WHERE ... T1.foo = 6") and a list of parameters.
|
|
||||||
"""
|
|
||||||
table_alias, name, db_type = data
|
|
||||||
if table_alias:
|
|
||||||
lhs = '%s.%s' % (qn(table_alias), qn(name))
|
|
||||||
else:
|
|
||||||
lhs = qn(name)
|
|
||||||
return connection.ops.field_cast_sql(db_type, internal_type) % lhs
|
|
||||||
|
|
||||||
def relabel_aliases(self, change_map):
|
def relabel_aliases(self, change_map):
|
||||||
"""
|
"""
|
||||||
Relabels the alias values of any children. 'change_map' is a dictionary
|
Relabels the alias values of any children. 'change_map' is a dictionary
|
||||||
|
@ -287,13 +122,6 @@ class WhereNode(tree.Node):
|
||||||
child.relabel_aliases(change_map)
|
child.relabel_aliases(change_map)
|
||||||
elif hasattr(child, 'relabeled_clone'):
|
elif hasattr(child, 'relabeled_clone'):
|
||||||
self.children[pos] = child.relabeled_clone(change_map)
|
self.children[pos] = child.relabeled_clone(change_map)
|
||||||
elif isinstance(child, (list, tuple)):
|
|
||||||
# tuple starting with Constraint
|
|
||||||
child = (child[0].relabeled_clone(change_map),) + child[1:]
|
|
||||||
if hasattr(child[3], 'relabeled_clone'):
|
|
||||||
child = (child[0], child[1], child[2]) + (
|
|
||||||
child[3].relabeled_clone(change_map),)
|
|
||||||
self.children[pos] = child
|
|
||||||
|
|
||||||
def clone(self):
|
def clone(self):
|
||||||
"""
|
"""
|
||||||
|
@ -372,56 +200,6 @@ class ExtraWhere(object):
|
||||||
return " AND ".join(sqls), list(self.params or ())
|
return " AND ".join(sqls), list(self.params or ())
|
||||||
|
|
||||||
|
|
||||||
class Constraint(object):
|
|
||||||
"""
|
|
||||||
An object that can be passed to WhereNode.add() and knows how to
|
|
||||||
pre-process itself prior to including in the WhereNode.
|
|
||||||
"""
|
|
||||||
def __init__(self, alias, col, field):
|
|
||||||
warnings.warn(
|
|
||||||
"The Constraint class will be removed in Django 1.9. Use Lookup class instead.",
|
|
||||||
RemovedInDjango19Warning)
|
|
||||||
self.alias, self.col, self.field = alias, col, field
|
|
||||||
|
|
||||||
def prepare(self, lookup_type, value):
|
|
||||||
if self.field and not hasattr(value, 'as_sql'):
|
|
||||||
return self.field.get_prep_lookup(lookup_type, value)
|
|
||||||
return value
|
|
||||||
|
|
||||||
def process(self, lookup_type, value, connection):
|
|
||||||
"""
|
|
||||||
Returns a tuple of data suitable for inclusion in a WhereNode
|
|
||||||
instance.
|
|
||||||
"""
|
|
||||||
# Because of circular imports, we need to import this here.
|
|
||||||
from django.db.models.base import ObjectDoesNotExist
|
|
||||||
try:
|
|
||||||
if self.field:
|
|
||||||
params = self.field.get_db_prep_lookup(lookup_type, value,
|
|
||||||
connection=connection, prepared=True)
|
|
||||||
db_type = self.field.db_type(connection=connection)
|
|
||||||
else:
|
|
||||||
# This branch is used at times when we add a comparison to NULL
|
|
||||||
# (we don't really want to waste time looking up the associated
|
|
||||||
# field object at the calling location).
|
|
||||||
params = Field().get_db_prep_lookup(lookup_type, value,
|
|
||||||
connection=connection, prepared=True)
|
|
||||||
db_type = None
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
raise EmptyShortCircuit
|
|
||||||
|
|
||||||
return (self.alias, self.col, db_type), params
|
|
||||||
|
|
||||||
def relabeled_clone(self, change_map):
|
|
||||||
if self.alias not in change_map:
|
|
||||||
return self
|
|
||||||
else:
|
|
||||||
new = Empty()
|
|
||||||
new.__class__ = self.__class__
|
|
||||||
new.alias, new.col, new.field = change_map[self.alias], self.col, self.field
|
|
||||||
return new
|
|
||||||
|
|
||||||
|
|
||||||
class SubqueryConstraint(object):
|
class SubqueryConstraint(object):
|
||||||
def __init__(self, alias, columns, targets, query_object):
|
def __init__(self, alias, columns, targets, query_object):
|
||||||
self.alias = alias
|
self.alias = alias
|
||||||
|
|
|
@ -81,13 +81,6 @@ class Node(object):
|
||||||
"""
|
"""
|
||||||
return other in self.children
|
return other in self.children
|
||||||
|
|
||||||
def _prepare_data(self, data):
|
|
||||||
"""
|
|
||||||
A subclass hook for doing subclass specific transformations of the
|
|
||||||
given data on combine() or add().
|
|
||||||
"""
|
|
||||||
return data
|
|
||||||
|
|
||||||
def add(self, data, conn_type, squash=True):
|
def add(self, data, conn_type, squash=True):
|
||||||
"""
|
"""
|
||||||
Combines this tree and the data represented by data using the
|
Combines this tree and the data represented by data using the
|
||||||
|
@ -105,7 +98,6 @@ class Node(object):
|
||||||
"""
|
"""
|
||||||
if data in self.children:
|
if data in self.children:
|
||||||
return data
|
return data
|
||||||
data = self._prepare_data(data)
|
|
||||||
if not squash:
|
if not squash:
|
||||||
self.children.append(data)
|
self.children.append(data)
|
||||||
return data
|
return data
|
||||||
|
|
Loading…
Reference in New Issue