116 строки
4.3 KiB
Python
116 строки
4.3 KiB
Python
from django.db import models
|
|
from django.db.models.sql import compiler
|
|
|
|
import caching.base as caching
|
|
|
|
|
|
class IndexQuerySet(caching.CachingQuerySet):
|
|
|
|
def with_index(self, **kw):
|
|
"""
|
|
Suggest indexes that should be used with this query as key-value pairs.
|
|
|
|
qs.with_index(t1='xxx') => INNER JOIN t1 USE INDEX (`xxx`)
|
|
"""
|
|
q = self._clone()
|
|
if not isinstance(q.query, IndexQuery):
|
|
q.query = self.query.clone(IndexQuery)
|
|
q.query.index_map.update(kw)
|
|
return q
|
|
|
|
def fetch_missed(self, pks):
|
|
# Remove the indexes before doing the id query.
|
|
if hasattr(self.query, 'index_map'):
|
|
index_map = self.query.index_map
|
|
self.query.index_map = {}
|
|
rv = super(IndexQuerySet, self).fetch_missed(pks)
|
|
self.query.index_map = index_map
|
|
return rv
|
|
else:
|
|
return super(IndexQuerySet, self).fetch_missed(pks)
|
|
|
|
|
|
class IndexQuery(models.query.sql.Query):
|
|
"""
|
|
Extends sql.Query to make it possible to specify indexes to use.
|
|
"""
|
|
|
|
def clone(self, klass=None, **kwargs):
|
|
# Maintain index_map across clones.
|
|
c = super(IndexQuery, self).clone(klass, **kwargs)
|
|
c.index_map = dict(self.index_map)
|
|
return c
|
|
|
|
def get_compiler(self, using=None, connection=None):
|
|
# Call super to figure out using and connection.
|
|
c = super(IndexQuery, self).get_compiler(using, connection)
|
|
return IndexCompiler(self, c.connection, c.using)
|
|
|
|
def _setup_query(self):
|
|
if not hasattr(self, 'index_map'):
|
|
self.index_map = {}
|
|
|
|
def get_count(self, using):
|
|
# Don't use the index for counts, it's slower.
|
|
index_map = self.index_map
|
|
self.index_map = {}
|
|
count = super(IndexQuery, self).get_count(using)
|
|
self.index_map = index_map
|
|
return count
|
|
|
|
|
|
class IndexCompiler(compiler.SQLCompiler):
|
|
|
|
def get_from_clause(self):
|
|
"""
|
|
Returns a list of strings that are joined together to go after the
|
|
"FROM" part of the query, as well as a list any extra parameters that
|
|
need to be included. Sub-classes, can override this to create a
|
|
from-clause via a "select".
|
|
|
|
This should only be called after any SQL construction methods that
|
|
might change the tables we need. This means the select columns and
|
|
ordering must be done first.
|
|
"""
|
|
result = []
|
|
qn = self.quote_name_unless_alias
|
|
qn2 = self.connection.ops.quote_name
|
|
index_map = self.query.index_map
|
|
first = True
|
|
for alias in self.query.tables:
|
|
if not self.query.alias_refcount[alias]:
|
|
continue
|
|
try:
|
|
name, alias, join_type, lhs, lhs_col, col, nullable = self.query.alias_map[alias]
|
|
except KeyError:
|
|
# Extra tables can end up in self.tables, but not in the
|
|
# alias_map if they aren't in a join. That's OK. We skip them.
|
|
continue
|
|
alias_str = (alias != name and ' %s' % alias or '')
|
|
### jbalogh wuz here. ###
|
|
if name in index_map:
|
|
use_index = 'USE INDEX (%s)' % qn(index_map[name])
|
|
else:
|
|
use_index = ''
|
|
if join_type and not first:
|
|
# If you really need a LEFT OUTER JOIN, file a bug.
|
|
join_type = 'INNER JOIN'
|
|
result.append('%s %s%s %s ON (%s.%s = %s.%s)'
|
|
% (join_type, qn(name), alias_str, use_index, qn(lhs),
|
|
qn2(lhs_col), qn(alias), qn2(col)))
|
|
else:
|
|
connector = not first and ', ' or ''
|
|
result.append('%s%s%s %s' % (connector, qn(name), alias_str, use_index))
|
|
### jbalogh out. ###
|
|
first = False
|
|
for t in self.query.extra_tables:
|
|
alias, unused = self.query.table_alias(t)
|
|
# Only add the alias if it's not already present (the table_alias()
|
|
# calls increments the refcount, so an alias refcount of one means
|
|
# this is the only reference.
|
|
if alias not in self.query.alias_map or self.query.alias_refcount[alias] == 1:
|
|
connector = not first and ', ' or ''
|
|
result.append('%s%s' % (connector, qn(alias)))
|
|
first = False
|
|
return result, []
|