mirror of
https://github.com/django/django.git
synced 2025-07-05 18:29:11 +00:00
queyrset-refactor: Some more speed-ups due to datastructure changes.
git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@7255 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
067d380e98
commit
133111e40b
@ -28,21 +28,21 @@ def add_lazy_relation(cls, field, relation):
|
||||
"""
|
||||
Adds a lookup on ``cls`` when a related field is defined using a string,
|
||||
i.e.::
|
||||
|
||||
|
||||
class MyModel(Model):
|
||||
fk = ForeignKey("AnotherModel")
|
||||
|
||||
|
||||
This string can be:
|
||||
|
||||
|
||||
* RECURSIVE_RELATIONSHIP_CONSTANT (i.e. "self") to indicate a recursive
|
||||
relation.
|
||||
|
||||
|
||||
* The name of a model (i.e "AnotherModel") to indicate another model in
|
||||
the same app.
|
||||
|
||||
|
||||
* An app-label and model name (i.e. "someapp.AnotherModel") to indicate
|
||||
another model in a different app.
|
||||
|
||||
|
||||
If the other model hasn't yet been loaded -- almost a given if you're using
|
||||
lazy relationships -- then the relation won't be set up until the
|
||||
class_prepared signal fires at the end of model initialization.
|
||||
@ -51,7 +51,7 @@ def add_lazy_relation(cls, field, relation):
|
||||
if relation == RECURSIVE_RELATIONSHIP_CONSTANT:
|
||||
app_label = cls._meta.app_label
|
||||
model_name = cls.__name__
|
||||
|
||||
|
||||
else:
|
||||
# Look for an "app.Model" relation
|
||||
try:
|
||||
@ -60,10 +60,10 @@ def add_lazy_relation(cls, field, relation):
|
||||
# If we can't split, assume a model in current app
|
||||
app_label = cls._meta.app_label
|
||||
model_name = relation
|
||||
|
||||
|
||||
# Try to look up the related model, and if it's already loaded resolve the
|
||||
# string right away. If get_model returns None, it means that the related
|
||||
# model isn't loaded yet, so we need to pend the relation until the class
|
||||
# model isn't loaded yet, so we need to pend the relation until the class
|
||||
# is prepared.
|
||||
model = get_model(app_label, model_name, False)
|
||||
if model:
|
||||
@ -73,7 +73,7 @@ def add_lazy_relation(cls, field, relation):
|
||||
key = (app_label, model_name)
|
||||
value = (cls, field)
|
||||
pending_lookups.setdefault(key, []).append(value)
|
||||
|
||||
|
||||
def do_pending_lookups(sender):
|
||||
"""
|
||||
Handle any pending relations to the sending model. Sent from class_prepared.
|
||||
@ -530,7 +530,11 @@ class ManyToOneRel(object):
|
||||
Returns the Field in the 'to' object to which this relationship is
|
||||
tied.
|
||||
"""
|
||||
return self.to._meta.get_field_by_name(self.field_name, True)[0]
|
||||
data = self.to._meta.get_field_by_name(self.field_name)
|
||||
if not data[2]:
|
||||
raise FieldDoesNotExist("No related field named '%s'" %
|
||||
self.field_name)
|
||||
return data[0]
|
||||
|
||||
class OneToOneRel(ManyToOneRel):
|
||||
def __init__(self, to, field_name, num_in_admin=0, min_num_in_admin=None,
|
||||
|
@ -231,7 +231,7 @@ class Options(object):
|
||||
return f
|
||||
raise FieldDoesNotExist, '%s has no field named %r' % (self.object_name, name)
|
||||
|
||||
def get_field_by_name(self, name, only_direct=False):
|
||||
def get_field_by_name(self, name):
|
||||
"""
|
||||
Returns the (field_object, model, direct, m2m), where field_object is
|
||||
the Field instance for the given name, model is the model containing
|
||||
@ -241,21 +241,17 @@ class Options(object):
|
||||
for this field (since the field doesn't have an instance associated
|
||||
with it).
|
||||
|
||||
If 'only_direct' is True, only forwards relations (and non-relations)
|
||||
are considered in the result.
|
||||
|
||||
Uses a cache internally, so after the first access, this is very fast.
|
||||
"""
|
||||
try:
|
||||
result = self._name_map.get(name)
|
||||
except AttributeError:
|
||||
cache = self.init_name_map()
|
||||
result = cache.get(name)
|
||||
|
||||
if not result or (only_direct and not result[2]):
|
||||
try:
|
||||
return self._name_map[name]
|
||||
except AttributeError:
|
||||
cache = self.init_name_map()
|
||||
return self._name_map[name]
|
||||
except KeyError:
|
||||
raise FieldDoesNotExist('%s has no field named %r'
|
||||
% (self.object_name, name))
|
||||
return result
|
||||
|
||||
def get_all_field_names(self):
|
||||
"""
|
||||
|
@ -46,7 +46,8 @@ class Query(object):
|
||||
self.alias_refcount = {}
|
||||
self.alias_map = {} # Maps alias to join information
|
||||
self.table_map = {} # Maps table names to list of aliases.
|
||||
self.rev_join_map = {} # Reverse of join_map. (FIXME: Update comment)
|
||||
self.join_map = {}
|
||||
self.rev_join_map = {} # Reverse of join_map.
|
||||
self.quote_cache = {}
|
||||
self.default_cols = True
|
||||
self.default_ordering = True
|
||||
@ -130,6 +131,7 @@ class Query(object):
|
||||
obj.alias_refcount = self.alias_refcount.copy()
|
||||
obj.alias_map = self.alias_map.copy()
|
||||
obj.table_map = self.table_map.copy()
|
||||
obj.join_map = self.join_map.copy()
|
||||
obj.rev_join_map = self.rev_join_map.copy()
|
||||
obj.quote_cache = {}
|
||||
obj.default_cols = self.default_cols
|
||||
@ -271,7 +273,7 @@ class Query(object):
|
||||
|
||||
# Work out how to relabel the rhs aliases, if necessary.
|
||||
change_map = {}
|
||||
used = {}
|
||||
used = set()
|
||||
conjunction = (connector == AND)
|
||||
first = True
|
||||
for alias in rhs.tables:
|
||||
@ -281,7 +283,7 @@ class Query(object):
|
||||
promote = (rhs.alias_map[alias][JOIN_TYPE] == self.LOUTER)
|
||||
new_alias = self.join(rhs.rev_join_map[alias],
|
||||
(conjunction and not first), used, promote, not conjunction)
|
||||
used[new_alias] = None
|
||||
used.add(new_alias)
|
||||
change_map[alias] = new_alias
|
||||
first = False
|
||||
|
||||
@ -411,22 +413,17 @@ class Query(object):
|
||||
"""
|
||||
result = []
|
||||
qn = self.quote_name_unless_alias
|
||||
qn2 = self.connection.ops.quote_name
|
||||
first = True
|
||||
for alias in self.tables:
|
||||
if not self.alias_refcount[alias]:
|
||||
continue
|
||||
join = self.alias_map[alias]
|
||||
if join:
|
||||
name, alias, join_type, lhs, lhs_col, col, nullable = join
|
||||
alias_str = (alias != name and ' AS %s' % alias or '')
|
||||
else:
|
||||
join_type = None
|
||||
alias_str = ''
|
||||
name = alias
|
||||
name, alias, join_type, lhs, lhs_col, col, nullable = self.alias_map[alias]
|
||||
alias_str = (alias != name and ' AS %s' % alias or '')
|
||||
if join_type and not first:
|
||||
result.append('%s %s%s ON (%s.%s = %s.%s)'
|
||||
% (join_type, qn(name), alias_str, qn(lhs),
|
||||
qn(lhs_col), qn(alias), qn(col)))
|
||||
qn2(lhs_col), qn(alias), qn2(col)))
|
||||
else:
|
||||
connector = not first and ', ' or ''
|
||||
result.append('%s%s%s' % (connector, qn(name), alias_str))
|
||||
@ -472,6 +469,7 @@ class Query(object):
|
||||
else:
|
||||
ordering = self.order_by or self.model._meta.ordering
|
||||
qn = self.quote_name_unless_alias
|
||||
qn2 = self.connection.ops.quote_name
|
||||
distinct = self.distinct
|
||||
select_aliases = self._select_aliases
|
||||
result = []
|
||||
@ -492,12 +490,11 @@ class Query(object):
|
||||
result.append('%s %s' % (field, order))
|
||||
continue
|
||||
if '.' in field:
|
||||
# This came in through an extra(ordering=...) addition. Pass it
|
||||
# on verbatim, after mapping the table name to an alias, if
|
||||
# necessary.
|
||||
# This came in through an extra(order_by=...) addition. Pass it
|
||||
# on verbatim.
|
||||
col, order = get_order_dir(field, asc)
|
||||
table, col = col.split('.', 1)
|
||||
elt = '%s.%s' % (qn(self.table_alias(table)[0]), col)
|
||||
elt = '%s.%s' % (qn(table), col)
|
||||
if not distinct or elt in select_aliases:
|
||||
result.append('%s %s' % (elt, order))
|
||||
elif get_order_dir(field)[0] not in self.extra_select:
|
||||
@ -505,7 +502,7 @@ class Query(object):
|
||||
# '-field1__field2__field', etc.
|
||||
for table, col, order in self.find_ordering_name(field,
|
||||
self.model._meta, default_order=asc):
|
||||
elt = '%s.%s' % (qn(table), qn(col))
|
||||
elt = '%s.%s' % (qn(table), qn2(col))
|
||||
if not distinct or elt in select_aliases:
|
||||
result.append('%s %s' % (elt, order))
|
||||
else:
|
||||
@ -527,11 +524,11 @@ class Query(object):
|
||||
if not alias:
|
||||
alias = self.get_initial_alias()
|
||||
try:
|
||||
field, target, opts, joins = self.setup_joins(pieces, opts, alias,
|
||||
False, False)
|
||||
field, target, opts, joins, last = self.setup_joins(pieces, opts,
|
||||
alias, False, False)
|
||||
except JoinError:
|
||||
raise FieldError("Cannot order by many-valued field: '%s'" % name)
|
||||
alias = joins[-1][-1]
|
||||
alias = joins[-1]
|
||||
col = target.column
|
||||
|
||||
# If we get to this point and the field is a relation to another model,
|
||||
@ -540,7 +537,7 @@ class Query(object):
|
||||
# Firstly, avoid infinite loops.
|
||||
if not already_seen:
|
||||
already_seen = set()
|
||||
join_tuple = tuple([tuple(j) for j in joins])
|
||||
join_tuple = tuple(joins)
|
||||
if join_tuple in already_seen:
|
||||
raise FieldError('Infinite loop caused by ordering.')
|
||||
already_seen.add(join_tuple)
|
||||
@ -570,20 +567,22 @@ class Query(object):
|
||||
If 'create' is true, a new alias is always created. Otherwise, the
|
||||
most recently created alias for the table (if one exists) is reused.
|
||||
"""
|
||||
if not create and table_name in self.table_map:
|
||||
alias = self.table_map[table_name][-1]
|
||||
current = self.table_map.get(table_name)
|
||||
if not create and current:
|
||||
alias = current[0]
|
||||
self.alias_refcount[alias] += 1
|
||||
return alias, False
|
||||
|
||||
# Create a new alias for this table.
|
||||
if table_name not in self.table_map:
|
||||
if current:
|
||||
alias = '%s%d' % (self.alias_prefix, len(self.alias_map) + 1)
|
||||
current.append(alias)
|
||||
else:
|
||||
# The first occurence of a table uses the table name directly.
|
||||
alias = table_name
|
||||
else:
|
||||
alias = '%s%d' % (self.alias_prefix, len(self.alias_map) + 1)
|
||||
self.table_map[alias] = [alias]
|
||||
self.alias_refcount[alias] = 1
|
||||
self.alias_map[alias] = None
|
||||
self.table_map.setdefault(table_name, []).append(alias)
|
||||
#self.alias_map[alias] = None
|
||||
self.tables.append(alias)
|
||||
return alias, True
|
||||
|
||||
@ -629,7 +628,9 @@ class Query(object):
|
||||
alias_data = list(self.alias_map[old_alias])
|
||||
alias_data[RHS_ALIAS] = new_alias
|
||||
|
||||
self.rev_join_map[new_alias] = self.rev_join_map[old_alias]
|
||||
t = self.rev_join_map[old_alias]
|
||||
self.join_map[t] = new_alias
|
||||
self.rev_join_map[new_alias] = t
|
||||
del self.rev_join_map[old_alias]
|
||||
self.alias_refcount[new_alias] = self.alias_refcount[old_alias]
|
||||
del self.alias_refcount[old_alias]
|
||||
@ -654,7 +655,6 @@ class Query(object):
|
||||
data[LHS_ALIAS] = change_map[lhs]
|
||||
self.alias_map[alias] = tuple(data)
|
||||
|
||||
|
||||
def bump_prefix(self):
|
||||
"""
|
||||
Changes the alias prefix to the next letter in the alphabet and
|
||||
@ -724,29 +724,19 @@ class Query(object):
|
||||
is a candidate for promotion (to "left outer") when combining querysets.
|
||||
"""
|
||||
lhs, table, lhs_col, col = connection
|
||||
if lhs is None:
|
||||
lhs_table = None
|
||||
is_table = False
|
||||
elif lhs not in self.alias_map:
|
||||
lhs_table = lhs
|
||||
is_table = True
|
||||
else:
|
||||
if lhs in self.alias_map:
|
||||
lhs_table = self.alias_map[lhs][TABLE_NAME]
|
||||
is_table = False
|
||||
else:
|
||||
lhs_table = lhs
|
||||
t_ident = (lhs_table, table, lhs_col, col)
|
||||
if not always_create:
|
||||
for alias, row in self.rev_join_map.items():
|
||||
if t_ident == row and alias not in exclusions:
|
||||
self.ref_alias(alias)
|
||||
if promote:
|
||||
self.promote_alias(alias)
|
||||
return alias
|
||||
# If we get to here (no non-excluded alias exists), we'll fall
|
||||
# through to creating a new alias.
|
||||
alias = self.join_map.get(t_ident)
|
||||
if alias and not always_create and alias not in exclusions:
|
||||
self.ref_alias(alias)
|
||||
if promote:
|
||||
self.promote_alias(alias)
|
||||
return alias
|
||||
|
||||
# No reuse is possible, so we need a new alias.
|
||||
assert not is_table, \
|
||||
"Must pass in lhs alias when creating a new join."
|
||||
alias, _ = self.table_alias(table, True)
|
||||
if not lhs:
|
||||
# Not all tables need to be joined to anything. No join type
|
||||
@ -758,6 +748,7 @@ class Query(object):
|
||||
join_type = self.INNER
|
||||
join = (table, alias, join_type, lhs, lhs_col, col, nullable)
|
||||
self.alias_map[alias] = join
|
||||
self.join_map[t_ident] = alias
|
||||
self.rev_join_map[alias] = t_ident
|
||||
return alias
|
||||
|
||||
@ -855,22 +846,28 @@ class Query(object):
|
||||
allow_many = trim or not negate
|
||||
|
||||
try:
|
||||
field, target, opts, join_list = self.setup_joins(parts, opts,
|
||||
field, target, opts, join_list, last = self.setup_joins(parts, opts,
|
||||
alias, (connector == AND), allow_many)
|
||||
except JoinError, e:
|
||||
self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]))
|
||||
return
|
||||
final = len(join_list)
|
||||
penultimate = last.pop()
|
||||
if penultimate == final:
|
||||
penultimate = last.pop()
|
||||
if trim and len(join_list) > 1:
|
||||
extra = join_list[-1]
|
||||
join_list = join_list[:-1]
|
||||
extra = join_list[penultimate:]
|
||||
join_list = join_list[:penultimate]
|
||||
final = penultimate
|
||||
penultimate = last.pop()
|
||||
col = self.alias_map[extra[0]][LHS_JOIN_COL]
|
||||
for alias in extra:
|
||||
self.unref_alias(alias)
|
||||
else:
|
||||
col = target.column
|
||||
alias = join_list[-1][-1]
|
||||
alias = join_list[-1]
|
||||
|
||||
if join_list:
|
||||
if final > 1:
|
||||
# An optimization: if the final join is against the same column as
|
||||
# we are comparing against, we can go back one step in the join
|
||||
# chain and compare against the lhs of the join instead. The result
|
||||
@ -880,18 +877,18 @@ class Query(object):
|
||||
self.unref_alias(alias)
|
||||
alias = join[LHS_ALIAS]
|
||||
col = join[LHS_JOIN_COL]
|
||||
if len(join_list[-1]) == 1:
|
||||
join_list = join_list[:-1]
|
||||
else:
|
||||
join_list[-1] = join_list[-1][:-1]
|
||||
join_list = join_list[:-1]
|
||||
final -= 1
|
||||
if final == penultimate:
|
||||
penultimate = last.pop()
|
||||
|
||||
if (lookup_type == 'isnull' and value is True and not negate and
|
||||
(len(join_list) > 1 or len(join_list[0]) > 1)):
|
||||
final > 1):
|
||||
# If the comparison is against NULL, we need to use a left outer
|
||||
# join when connecting to the previous model. We make that
|
||||
# adjustment here. We don't do this unless needed as it's less
|
||||
# efficient at the database level.
|
||||
self.promote_alias(join_list[-1][0])
|
||||
self.promote_alias(join_list[penultimate])
|
||||
|
||||
if connector == OR:
|
||||
# Some joins may need to be promoted when adding a new filter to a
|
||||
@ -899,7 +896,7 @@ class Query(object):
|
||||
# from any previous joins (ref count is 1 in the table list), we
|
||||
# make the new additions (and any existing ones not used in the new
|
||||
# join list) an outer join.
|
||||
join_it = itertools.chain(*join_list)
|
||||
join_it = iter(join_list)
|
||||
table_it = iter(self.tables)
|
||||
join_it.next(), table_it.next()
|
||||
for join in join_it:
|
||||
@ -931,14 +928,11 @@ class Query(object):
|
||||
merged = False
|
||||
|
||||
if negate:
|
||||
count = 0
|
||||
for join in join_list:
|
||||
count += len(join)
|
||||
for alias in join:
|
||||
self.promote_alias(alias)
|
||||
for alias in join_list:
|
||||
self.promote_alias(alias)
|
||||
if not merged:
|
||||
self.where.negate()
|
||||
if count > 1 and lookup_type != 'isnull':
|
||||
if final > 1 and lookup_type != 'isnull':
|
||||
j_col = self.alias_map[alias][RHS_JOIN_COL]
|
||||
entry = Node([(alias, j_col, None, 'isnull', True)])
|
||||
entry.negate()
|
||||
@ -989,10 +983,10 @@ class Query(object):
|
||||
column (used for any 'where' constraint), the final 'opts' value and the
|
||||
list of tables joined.
|
||||
"""
|
||||
joins = [[alias]]
|
||||
used = set()
|
||||
joins = [alias]
|
||||
last = [0]
|
||||
for pos, name in enumerate(names):
|
||||
used.update(joins[-1])
|
||||
last.append(len(joins))
|
||||
if name == 'pk':
|
||||
name = opts.pk.name
|
||||
|
||||
@ -1011,9 +1005,8 @@ class Query(object):
|
||||
raise FieldError("Cannot resolve keyword %r into field. "
|
||||
"Choices are: %s" % (name, ", ".join(names)))
|
||||
if not allow_many and (m2m or not direct):
|
||||
for join in joins:
|
||||
for alias in join:
|
||||
self.unref_alias(alias)
|
||||
for alias in joins:
|
||||
self.unref_alias(alias)
|
||||
raise JoinError(pos + 1)
|
||||
if model:
|
||||
# The field lives on a base class of the current model.
|
||||
@ -1022,9 +1015,8 @@ class Query(object):
|
||||
lhs_col = opts.parents[int_model].column
|
||||
opts = int_model._meta
|
||||
alias = self.join((alias, opts.db_table, lhs_col,
|
||||
opts.pk.column), exclusions=used)
|
||||
alias_list.append(alias)
|
||||
joins.append(alias_list)
|
||||
opts.pk.column), exclusions=joins)
|
||||
joins.append(alias)
|
||||
cached_data = opts._join_cache.get(name)
|
||||
orig_opts = opts
|
||||
|
||||
@ -1048,10 +1040,10 @@ class Query(object):
|
||||
target)
|
||||
|
||||
int_alias = self.join((alias, table1, from_col1, to_col1),
|
||||
dupe_multis, used, nullable=True)
|
||||
dupe_multis, joins, nullable=True)
|
||||
alias = self.join((int_alias, table2, from_col2, to_col2),
|
||||
dupe_multis, used, nullable=True)
|
||||
joins.append([int_alias, alias])
|
||||
dupe_multis, joins, nullable=True)
|
||||
joins.extend([int_alias, alias])
|
||||
elif field.rel:
|
||||
# One-to-one or many-to-one field
|
||||
if cached_data:
|
||||
@ -1066,8 +1058,8 @@ class Query(object):
|
||||
opts, target)
|
||||
|
||||
alias = self.join((alias, table, from_col, to_col),
|
||||
exclusions=used, nullable=field.null)
|
||||
joins.append([alias])
|
||||
exclusions=joins, nullable=field.null)
|
||||
joins.append(alias)
|
||||
else:
|
||||
# Non-relation fields.
|
||||
target = field
|
||||
@ -1094,10 +1086,10 @@ class Query(object):
|
||||
target)
|
||||
|
||||
int_alias = self.join((alias, table1, from_col1, to_col1),
|
||||
dupe_multis, used, nullable=True)
|
||||
dupe_multis, joins, nullable=True)
|
||||
alias = self.join((int_alias, table2, from_col2, to_col2),
|
||||
dupe_multis, used, nullable=True)
|
||||
joins.append([int_alias, alias])
|
||||
dupe_multis, joins, nullable=True)
|
||||
joins.extend([int_alias, alias])
|
||||
else:
|
||||
# One-to-many field (ForeignKey defined on the target model)
|
||||
if cached_data:
|
||||
@ -1114,13 +1106,13 @@ class Query(object):
|
||||
opts, target)
|
||||
|
||||
alias = self.join((alias, table, from_col, to_col),
|
||||
dupe_multis, used, nullable=True)
|
||||
joins.append([alias])
|
||||
dupe_multis, joins, nullable=True)
|
||||
joins.append(alias)
|
||||
|
||||
if pos != len(names) - 1:
|
||||
raise FieldError("Join on field %r not permitted." % name)
|
||||
|
||||
return field, target, opts, joins
|
||||
return field, target, opts, joins, last
|
||||
|
||||
def split_exclude(self, filter_expr, prefix):
|
||||
"""
|
||||
@ -1179,9 +1171,10 @@ class Query(object):
|
||||
opts = self.get_meta()
|
||||
try:
|
||||
for name in field_names:
|
||||
u1, target, u2, joins = self.setup_joins(name.split(LOOKUP_SEP),
|
||||
opts, alias, False, allow_m2m, True)
|
||||
self.select.append((joins[-1][-1], target.column))
|
||||
u1, target, u2, joins, u3 = self.setup_joins(
|
||||
name.split(LOOKUP_SEP), opts, alias, False, allow_m2m,
|
||||
True)
|
||||
self.select.append((joins[-1], target.column))
|
||||
except JoinError:
|
||||
raise FieldError("Invalid field name: '%s'" % name)
|
||||
|
||||
@ -1294,20 +1287,18 @@ class Query(object):
|
||||
"""
|
||||
opts = self.model._meta
|
||||
alias = self.get_initial_alias()
|
||||
field, col, opts, joins = self.setup_joins(start.split(LOOKUP_SEP),
|
||||
opts, alias, False)
|
||||
alias = joins[-1][0]
|
||||
field, col, opts, joins, last = self.setup_joins(
|
||||
start.split(LOOKUP_SEP), opts, alias, False)
|
||||
alias = joins[last[-1]]
|
||||
self.select = [(alias, self.alias_map[alias][RHS_JOIN_COL])]
|
||||
self.start_meta = opts
|
||||
|
||||
# The call to setup_joins add an extra reference to everything in
|
||||
# joins. So we need to unref everything once, and everything prior to
|
||||
# the final join a second time.
|
||||
for join in joins[:-1]:
|
||||
for alias in join:
|
||||
self.unref_alias(alias)
|
||||
self.unref_alias(alias)
|
||||
for alias in joins[-1]:
|
||||
for alias in joins:
|
||||
self.unref_alias(alias)
|
||||
for alias in joins[:last[-1]]:
|
||||
self.unref_alias(alias)
|
||||
|
||||
def execute_sql(self, result_type=MULTI):
|
||||
|
Loading…
x
Reference in New Issue
Block a user