1
0
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:
Malcolm Tredinnick 2008-03-17 13:32:11 +00:00
parent 067d380e98
commit 133111e40b
3 changed files with 111 additions and 120 deletions

View File

@ -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,

View File

@ -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):
"""

View File

@ -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):