Loading django/db/models/sql/query.py +2 −19 Original line number Diff line number Diff line Loading @@ -24,7 +24,7 @@ from django.db.models.sql.constants import (QUERY_TERMS, ORDER_DIR, SINGLE, ORDER_PATTERN, INNER, LOUTER) from django.db.models.sql.datastructures import ( 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) from django.utils import six from django.utils.deprecation import RemovedInDjango20Warning Loading Loading @@ -1130,10 +1130,6 @@ class Query(object): clause = self.where_class() if reffed_aggregate: 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) return clause, [] Loading Loading @@ -1176,19 +1172,6 @@ class Query(object): else: col = targets[0].get_col(alias, field) condition = self.build_lookup(lookups, col, value) if not condition: # 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) Loading django/db/models/sql/where.py +6 −228 Original line number Diff line number Diff line """ Code to manage the creation and SQL rendering of 'where' constraints. """ 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.db.models.sql.datastructures import EmptyResultSet from django.utils.functional import cached_property from django.utils.six.moves import range from django.utils import timezone from django.utils import tree Loading @@ -37,10 +26,9 @@ class WhereNode(tree.Node): The class is tied to the Query class that created it (in order to create the correct SQL). A child is usually a tuple of: (Constraint(alias, targetcol, field), lookup_type, value) where value can be either raw Python value, or Query, ExpressionNode or something else knowing how to turn itself into SQL. A child is usually an expression producing boolean values. Most likely the expression is a Lookup instance, but other types of objects fulfilling the required API could be used too (for example, sql.where.EverythingNode). However, a child could also be any class with as_sql() and either relabeled_clone() method or relabel_aliases() and clone() methods. The Loading @@ -49,39 +37,6 @@ class WhereNode(tree.Node): """ 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): """ Returns the SQL version of the where clause and the value to be Loading @@ -102,11 +57,7 @@ class WhereNode(tree.Node): for child in self.children: try: if hasattr(child, 'as_sql'): sql, params = compiler.compile(child) else: # A leaf node in the tree. sql, params = self.make_atom(child, compiler, connection) except EmptyResultSet: nothing_childs += 1 else: Loading Loading @@ -157,125 +108,9 @@ class WhereNode(tree.Node): def get_group_by_cols(self): cols = [] for child in self.children: if hasattr(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 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): """ Relabels the alias values of any children. 'change_map' is a dictionary Loading @@ -287,13 +122,6 @@ class WhereNode(tree.Node): child.relabel_aliases(change_map) elif hasattr(child, 'relabeled_clone'): 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): """ Loading Loading @@ -372,56 +200,6 @@ class ExtraWhere(object): 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): def __init__(self, alias, columns, targets, query_object): self.alias = alias Loading django/utils/tree.py +0 −8 Original line number Diff line number Diff line Loading @@ -81,13 +81,6 @@ class Node(object): """ 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): """ Combines this tree and the data represented by data using the Loading @@ -105,7 +98,6 @@ class Node(object): """ if data in self.children: return data data = self._prepare_data(data) if not squash: self.children.append(data) return data Loading Loading
django/db/models/sql/query.py +2 −19 Original line number Diff line number Diff line Loading @@ -24,7 +24,7 @@ from django.db.models.sql.constants import (QUERY_TERMS, ORDER_DIR, SINGLE, ORDER_PATTERN, INNER, LOUTER) from django.db.models.sql.datastructures import ( 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) from django.utils import six from django.utils.deprecation import RemovedInDjango20Warning Loading Loading @@ -1130,10 +1130,6 @@ class Query(object): clause = self.where_class() if reffed_aggregate: 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) return clause, [] Loading Loading @@ -1176,19 +1172,6 @@ class Query(object): else: col = targets[0].get_col(alias, field) condition = self.build_lookup(lookups, col, value) if not condition: # 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) Loading
django/db/models/sql/where.py +6 −228 Original line number Diff line number Diff line """ Code to manage the creation and SQL rendering of 'where' constraints. """ 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.db.models.sql.datastructures import EmptyResultSet from django.utils.functional import cached_property from django.utils.six.moves import range from django.utils import timezone from django.utils import tree Loading @@ -37,10 +26,9 @@ class WhereNode(tree.Node): The class is tied to the Query class that created it (in order to create the correct SQL). A child is usually a tuple of: (Constraint(alias, targetcol, field), lookup_type, value) where value can be either raw Python value, or Query, ExpressionNode or something else knowing how to turn itself into SQL. A child is usually an expression producing boolean values. Most likely the expression is a Lookup instance, but other types of objects fulfilling the required API could be used too (for example, sql.where.EverythingNode). However, a child could also be any class with as_sql() and either relabeled_clone() method or relabel_aliases() and clone() methods. The Loading @@ -49,39 +37,6 @@ class WhereNode(tree.Node): """ 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): """ Returns the SQL version of the where clause and the value to be Loading @@ -102,11 +57,7 @@ class WhereNode(tree.Node): for child in self.children: try: if hasattr(child, 'as_sql'): sql, params = compiler.compile(child) else: # A leaf node in the tree. sql, params = self.make_atom(child, compiler, connection) except EmptyResultSet: nothing_childs += 1 else: Loading Loading @@ -157,125 +108,9 @@ class WhereNode(tree.Node): def get_group_by_cols(self): cols = [] for child in self.children: if hasattr(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 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): """ Relabels the alias values of any children. 'change_map' is a dictionary Loading @@ -287,13 +122,6 @@ class WhereNode(tree.Node): child.relabel_aliases(change_map) elif hasattr(child, 'relabeled_clone'): 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): """ Loading Loading @@ -372,56 +200,6 @@ class ExtraWhere(object): 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): def __init__(self, alias, columns, targets, query_object): self.alias = alias Loading
django/utils/tree.py +0 −8 Original line number Diff line number Diff line Loading @@ -81,13 +81,6 @@ class Node(object): """ 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): """ Combines this tree and the data represented by data using the Loading @@ -105,7 +98,6 @@ class Node(object): """ if data in self.children: return data data = self._prepare_data(data) if not squash: self.children.append(data) return data Loading