mirror of
https://github.com/django/django.git
synced 2025-07-06 02:39:12 +00:00
Initial portion of queryset rewrite. This breaks a lot more than it fixes in
the current state. Committing so that Adrian and I can work on it together. Do not try to use this yet! git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@6116 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
b791612779
commit
a7a517f329
@ -117,6 +117,13 @@ class Field(object):
|
||||
# This is needed because bisect does not take a comparison function.
|
||||
return cmp(self.creation_counter, other.creation_counter)
|
||||
|
||||
def __deepcopy__(self, memodict):
|
||||
# Slight hack; deepcopy() is difficult to do on classes with
|
||||
# dynamically created methods. Fortunately, we can get away with doing
|
||||
# a shallow copy in this particular case.
|
||||
import copy
|
||||
return copy.copy(self)
|
||||
|
||||
def to_python(self, value):
|
||||
"""
|
||||
Converts the input value into the expected Python data type, raising
|
||||
|
File diff suppressed because it is too large
Load Diff
52
django/db/models/query_utils.py
Normal file
52
django/db/models/query_utils.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""
|
||||
Various data structures used in query construction.
|
||||
|
||||
Factored out from django.db.models.query so that they can also be used by other
|
||||
modules without getting into circular import difficulties.
|
||||
"""
|
||||
from django.utils import tree
|
||||
|
||||
class EmptyResultSet(Exception):
|
||||
"""
|
||||
Raised when a QuerySet cannot contain any data.
|
||||
"""
|
||||
pass
|
||||
|
||||
class Q(tree.Node):
|
||||
"""
|
||||
Encapsulates filters as objects that can then be combined logically (using
|
||||
& and |).
|
||||
"""
|
||||
# Connection types
|
||||
AND = 'AND'
|
||||
OR = 'OR'
|
||||
default = AND
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if args and kwargs:
|
||||
raise TypeError('Use positional *or* kwargs; not both!')
|
||||
nodes = list(args) + kwargs.items()
|
||||
super(Q, self).__init__(children=nodes)
|
||||
|
||||
def _combine(self, other, conn):
|
||||
if not isinstance(other, Q):
|
||||
raise TypeError(other)
|
||||
self.add(other, conn)
|
||||
return self
|
||||
|
||||
def __or__(self, other):
|
||||
return self._combine(other, self.OR)
|
||||
|
||||
def __and__(self, other):
|
||||
return self._combine(other, self.AND)
|
||||
|
||||
class QNot(Q):
|
||||
"""
|
||||
Encapsulates the negation of a Q object.
|
||||
"""
|
||||
def __init__(self, q):
|
||||
"""Creates the negation of the Q object passed in."""
|
||||
super(QNot, self).__init__()
|
||||
self.add(q, self.AND)
|
||||
self.negate()
|
||||
|
5
django/db/models/sql/__init__.py
Normal file
5
django/db/models/sql/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
from query import *
|
||||
from where import AND, OR
|
||||
|
||||
__all__ = ['Query', 'AND', 'OR']
|
||||
|
59
django/db/models/sql/datastructures.py
Normal file
59
django/db/models/sql/datastructures.py
Normal file
@ -0,0 +1,59 @@
|
||||
"""
|
||||
Useful auxilliary data structures for query construction. Not useful outside
|
||||
the SQL domain.
|
||||
"""
|
||||
|
||||
class EmptyResultSet(Exception):
|
||||
pass
|
||||
|
||||
class Aggregate(object):
|
||||
"""
|
||||
Base class for all aggregate-related classes (min, max, avg, count, sum).
|
||||
"""
|
||||
def relabel_aliases(self, change_map):
|
||||
"""
|
||||
Relabel the column alias, if necessary. Must be implemented by
|
||||
subclasses.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def as_sql(self, quote_func=None):
|
||||
"""
|
||||
Returns the SQL string fragment for this object.
|
||||
|
||||
The quote_func function is used to quote the column components. If
|
||||
None, it defaults to doing nothing.
|
||||
|
||||
Must be implemented by subclasses.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
class Count(Aggregate):
|
||||
"""
|
||||
Perform a count on the given column.
|
||||
"""
|
||||
def __init__(self, col=None, distinct=False):
|
||||
"""
|
||||
Set the column to count on (defaults to '*') and set whether the count
|
||||
should be distinct or not.
|
||||
"""
|
||||
self.col = col and col or '*'
|
||||
self.distinct = distinct
|
||||
|
||||
def relabel_aliases(self, change_map):
|
||||
c = self.col
|
||||
if isinstance(c, (list, tuple)):
|
||||
self.col = (change_map.get(c[0], c[0]), c[1])
|
||||
|
||||
def as_sql(self, quote_func=None):
|
||||
if not quote_func:
|
||||
quote_func = lambda x: x
|
||||
if isinstance(self.col, (list, tuple)):
|
||||
col = '%s.%s' % tuple([quote_func(c) for c in self.col])
|
||||
else:
|
||||
col = self.col
|
||||
if self.distinct:
|
||||
return 'COUNT(DISTINCT(%s))' % col
|
||||
else:
|
||||
return 'COUNT(%s)' % col
|
||||
|
880
django/db/models/sql/query.py
Normal file
880
django/db/models/sql/query.py
Normal file
@ -0,0 +1,880 @@
|
||||
"""
|
||||
Create SQL statements for QuerySets.
|
||||
|
||||
The code in here encapsulates all of the SQL construction so that QuerySets
|
||||
themselves do not have to (and could be backed by things other than SQL
|
||||
databases). The abstraction barrier only works one way: this module has to know
|
||||
all about the internals of models in order to get the information it needs.
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
from django.utils import tree
|
||||
from django.db.models.sql.where import WhereNode, AND
|
||||
from django.db.models.sql.datastructures import Count
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
from django.contrib.contenttypes import generic
|
||||
from datastructures import EmptyResultSet
|
||||
from utils import handle_legacy_orderlist
|
||||
|
||||
try:
|
||||
reversed
|
||||
except NameError:
|
||||
from django.utils.itercompat import reversed # For python 2.3.
|
||||
|
||||
# Valid query types (a dictionary is used for speedy lookups).
|
||||
QUERY_TERMS = dict([(x, None) for x in (
|
||||
'exact', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte', 'in',
|
||||
'startswith', 'istartswith', 'endswith', 'iendswith', 'range', 'year',
|
||||
'month', 'day', 'isnull', 'search', 'regex', 'iregex',
|
||||
)])
|
||||
|
||||
# Size of each "chunk" for get_iterator calls.
|
||||
# Larger values are slightly faster at the expense of more storage space.
|
||||
GET_ITERATOR_CHUNK_SIZE = 100
|
||||
|
||||
# Separator used to split filter strings apart.
|
||||
LOOKUP_SEP = '__'
|
||||
|
||||
# Constants to make looking up tuple values clearerer.
|
||||
# Join lists
|
||||
TABLE_NAME = 0
|
||||
RHS_ALIAS = 1
|
||||
JOIN_TYPE = 2
|
||||
LHS_ALIAS = 3
|
||||
LHS_JOIN_COL = 4
|
||||
RHS_JOIN_COL = 5
|
||||
# Alias maps lists
|
||||
ALIAS_TABLE = 0
|
||||
ALIAS_REFCOUNT = 1
|
||||
ALIAS_JOIN = 2
|
||||
|
||||
# How many results to expect from a cursor.execute call
|
||||
MULTI = 'multi'
|
||||
SINGLE = 'single'
|
||||
NONE = None
|
||||
|
||||
class Query(object):
|
||||
"""
|
||||
A single SQL query.
|
||||
"""
|
||||
# SQL join types. These are part of the class because their string forms
|
||||
# vary from database to database and can be customised by a subclass.
|
||||
INNER = 'INNER JOIN'
|
||||
LOUTER = 'LEFT OUTER JOIN'
|
||||
|
||||
alias_prefix = 'T'
|
||||
|
||||
def __init__(self, model, connection):
|
||||
self.model = model
|
||||
self.connection = connection
|
||||
self.alias_map = {} # Maps alias to table name
|
||||
self.table_map = {} # Maps table names to list of aliases.
|
||||
self.join_map = {} # Maps join_tuple to list of aliases.
|
||||
self.rev_join_map = {} # Reverse of join_map.
|
||||
|
||||
# SQL-related attributes
|
||||
self.select = []
|
||||
self.tables = [] # Aliases in the order they are created.
|
||||
self.where = WhereNode(self)
|
||||
self.having = []
|
||||
self.group_by = []
|
||||
self.order_by = []
|
||||
self.low_mark, self.high_mark = 0, None # Used for offset/limit
|
||||
self.distinct = False
|
||||
self.select_related = False
|
||||
self.max_depth = 0
|
||||
|
||||
# These are for extensions. The contents are more or less appended
|
||||
# verbatim to the appropriate clause.
|
||||
self.extra_select = {} # Maps col_alias -> col_sql.
|
||||
self.extra_tables = []
|
||||
self.extra_where = []
|
||||
self.extra_params = []
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Returns the query as a string of SQL with the parameter values
|
||||
substituted in.
|
||||
|
||||
Parameter values won't necessarily be quoted correctly, since that is
|
||||
done by the database interface at execution time.
|
||||
"""
|
||||
sql, params = self.as_sql()
|
||||
return sql % params
|
||||
|
||||
def clone(self, **kwargs):
|
||||
"""
|
||||
Creates a copy of the current instance. The 'kwargs' parameter can be
|
||||
used by clients to update attributes after copying has taken place.
|
||||
"""
|
||||
obj = self.__class__(self.model, self.connection)
|
||||
obj.table_map = self.table_map.copy()
|
||||
obj.alias_map = copy.deepcopy(self.alias_map)
|
||||
obj.join_map = copy.deepcopy(self.join_map)
|
||||
obj.rev_join_map = copy.deepcopy(self.rev_join_map)
|
||||
obj.select = self.select[:]
|
||||
obj.tables = self.tables[:]
|
||||
obj.where = copy.deepcopy(self.where)
|
||||
obj.having = self.having[:]
|
||||
obj.group_by = self.group_by[:]
|
||||
obj.order_by = self.order_by[:]
|
||||
obj.low_mark, obj.high_mark = self.low_mark, self.high_mark
|
||||
obj.distinct = self.distinct
|
||||
obj.select_related = self.select_related
|
||||
obj.max_depth = self.max_depth
|
||||
obj.extra_select = self.extra_select.copy()
|
||||
obj.extra_tables = self.extra_tables[:]
|
||||
obj.extra_where = self.extra_where[:]
|
||||
obj.extra_params = self.extra_params[:]
|
||||
obj.__dict__.update(kwargs)
|
||||
return obj
|
||||
|
||||
def results_iter(self):
|
||||
"""
|
||||
Returns an iterator over the results from executing this query.
|
||||
"""
|
||||
fields = self.model._meta.fields
|
||||
resolve_columns = hasattr(self, 'resolve_columns')
|
||||
for rows in self.execute_sql(MULTI):
|
||||
for row in rows:
|
||||
if resolve_columns:
|
||||
row = self.resolve_columns(row, fields)
|
||||
yield row
|
||||
|
||||
def get_count(self):
|
||||
"""
|
||||
Performs a COUNT() or COUNT(DISTINCT()) query, as appropriate, using
|
||||
the current filter constraints.
|
||||
"""
|
||||
counter = self.clone()
|
||||
counter.clear_ordering()
|
||||
counter.clear_limits()
|
||||
counter.select_related = False
|
||||
counter.add_count_column()
|
||||
data = counter.execute_sql(SINGLE)
|
||||
if not data:
|
||||
return 0
|
||||
number = data[0]
|
||||
|
||||
# Apply offset and limit constraints manually, since using LIMIT/OFFSET
|
||||
# in SQL doesn't change the COUNT output.
|
||||
number = max(0, number - self.low_mark)
|
||||
if self.high_mark:
|
||||
number = min(number, self.high_mark - self.low_mark)
|
||||
|
||||
return number
|
||||
|
||||
def as_sql(self, with_limits=True):
|
||||
"""
|
||||
Creates the SQL for this query. Returns the SQL string and list of
|
||||
parameters.
|
||||
|
||||
If 'with_limits' is False, any limit/offset information is not included
|
||||
in the query.
|
||||
"""
|
||||
self.pre_sql_setup()
|
||||
result = ['SELECT']
|
||||
if self.distinct:
|
||||
result.append('DISTINCT')
|
||||
out_cols = self.get_columns()
|
||||
result.append(', '.join(out_cols))
|
||||
|
||||
result.append('FROM')
|
||||
for alias in self.tables:
|
||||
if not self.alias_map[alias][ALIAS_REFCOUNT]:
|
||||
continue
|
||||
name, alias, join_type, lhs, lhs_col, col = \
|
||||
self.alias_map[alias][ALIAS_JOIN]
|
||||
alias_str = (alias != name and ' AS %s' % alias or '')
|
||||
if join_type:
|
||||
result.append('%s %s%s ON (%s.%s = %s.%s)'
|
||||
% (join_type, name, alias_str, lhs, lhs_col, alias,
|
||||
col))
|
||||
else:
|
||||
result.append('%s%s' % (name, alias_str))
|
||||
result.extend(self.extra_tables)
|
||||
|
||||
where, params = self.where.as_sql()
|
||||
if where:
|
||||
result.append('WHERE %s' % where)
|
||||
result.append(' AND'.join(self.extra_where))
|
||||
|
||||
ordering = self.get_ordering()
|
||||
if ordering:
|
||||
result.append('ORDER BY %s' % ', '.join(ordering))
|
||||
|
||||
if with_limits:
|
||||
if self.high_mark:
|
||||
result.append('LIMIT %d' % (self.high_mark - self.low_mark))
|
||||
if self.low_mark:
|
||||
assert self.high_mark, "OFFSET not allowed without LIMIT."
|
||||
result.append('OFFSET %d' % self.low_mark)
|
||||
|
||||
params.extend(self.extra_params)
|
||||
return ' '.join(result), tuple(params)
|
||||
|
||||
def combine(self, rhs, connection):
|
||||
"""
|
||||
Merge the 'rhs' query into the current one (with any 'rhs' effects
|
||||
being applied *after* (that is, "to the right of") anything in the
|
||||
current query. 'rhs' is not modified during a call to this function.
|
||||
|
||||
The 'connection' parameter describes how to connect filters from the
|
||||
'rhs' query.
|
||||
"""
|
||||
assert self.model == rhs.model, \
|
||||
"Cannot combine queries on two different base models."
|
||||
assert self.can_filter(), \
|
||||
"Cannot combine queries once a slice has been taken."
|
||||
assert self.distinct == rhs.distinct, \
|
||||
"Cannot combine a unique query with a non-unique query."
|
||||
|
||||
# Work out how to relabel the rhs aliases, if necessary.
|
||||
change_map = {}
|
||||
used = {}
|
||||
for alias in rhs.tables:
|
||||
promote = (rhs.alias_map[alias][ALIAS_JOIN][JOIN_TYPE] ==
|
||||
self.LOUTER)
|
||||
new_alias = self.join(rhs.rev_join_map[alias], exclusions=used,
|
||||
promote=promote)
|
||||
used[new_alias] = None
|
||||
change_map[alias] = new_alias
|
||||
|
||||
# Now relabel a copy of the rhs where-clause and add it to the current
|
||||
# one.
|
||||
if rhs.where:
|
||||
w = copy.deepcopy(rhs.where)
|
||||
w.relabel_aliases(change_map)
|
||||
if not self.where:
|
||||
# Since 'self' matches everything, add an explicit "include
|
||||
# everything" (pk is not NULL) where-constraint so that
|
||||
# connections between the where clauses won't exclude valid
|
||||
# results.
|
||||
alias = self.join((None, self.model._meta.db_table, None, None))
|
||||
pk = self.model._meta.pk
|
||||
self.where.add((alias, pk.column, pk, 'isnull', False), AND)
|
||||
elif self.where:
|
||||
# rhs has an empty where clause. Make it match everything (see
|
||||
# above for reasoning).
|
||||
w = WhereNode()
|
||||
alias = self.join((None, self.model._meta.db_table, None, None))
|
||||
pk = self.model._meta.pk
|
||||
w.add((alias, pk.column, pk, 'isnull', False), AND)
|
||||
else:
|
||||
w = WhereNode()
|
||||
self.where.add(w, connection)
|
||||
|
||||
# Selection columns and extra extensions are those provided by 'rhs'.
|
||||
self.select = []
|
||||
for col in rhs.select:
|
||||
if isinstance(col, (list, tuple)):
|
||||
self.select.append((change_map.get(col[0], col[0]), col[1]))
|
||||
else:
|
||||
item = copy.deepcopy(col)
|
||||
item.relabel_aliases(change_map)
|
||||
self.select.append(item)
|
||||
self.extra_select = rhs.extra_select.copy()
|
||||
self.extra_tables = rhs.extra_tables[:]
|
||||
self.extra_where = rhs.extra_where[:]
|
||||
self.extra_params = rhs.extra_params[:]
|
||||
|
||||
# Ordering uses the 'rhs' ordering, unless it has none, in which case
|
||||
# the current ordering is used.
|
||||
self.order_by = rhs.order_by and rhs.order_by[:] or self.order_by
|
||||
|
||||
def pre_sql_setup(self):
|
||||
"""
|
||||
Does any necessary class setup prior to producing SQL. This is for
|
||||
things that can't necessarily be done in __init__.
|
||||
"""
|
||||
if not self.tables:
|
||||
self.join((None, self.model._meta.db_table, None, None))
|
||||
|
||||
def get_columns(self):
|
||||
"""
|
||||
Return the list of columns to use in the select statement. If no
|
||||
columns have been specified, returns all columns relating to fields in
|
||||
the model.
|
||||
"""
|
||||
qn = self.connection.ops.quote_name
|
||||
result = []
|
||||
if self.select:
|
||||
for col in self.select:
|
||||
if isinstance(col, (list, tuple)):
|
||||
result.append('%s.%s' % (qn(col[0]), qn(col[1])))
|
||||
else:
|
||||
result.append(col.as_sql())
|
||||
else:
|
||||
table_alias = self.tables[0]
|
||||
result = ['%s.%s' % (table_alias, qn(f.column))
|
||||
for f in self.model._meta.fields]
|
||||
|
||||
# We sort extra_select so that the result columns are in a well-defined
|
||||
# order (and thus QuerySet.iterator can extract them correctly).
|
||||
extra_select = self.extra_select.items()
|
||||
extra_select.sort()
|
||||
result.extend(['(%s) AS %s' % (col, alias)
|
||||
for alias, col in extra_select])
|
||||
return result
|
||||
|
||||
def get_ordering(self):
|
||||
"""
|
||||
Returns a tuple representing the SQL elements in the "order by" clause.
|
||||
"""
|
||||
ordering = self.order_by or self.model._meta.ordering
|
||||
qn = self.connection.ops.quote_name
|
||||
opts = self.model._meta
|
||||
result = []
|
||||
for field in handle_legacy_orderlist(ordering):
|
||||
if field == '?':
|
||||
result.append(self.connection.ops.random_function_sql())
|
||||
continue
|
||||
if field[0] == '-':
|
||||
col = field[1:]
|
||||
order = 'DESC'
|
||||
else:
|
||||
col = field
|
||||
order = 'ASC'
|
||||
if '.' in col:
|
||||
table, col = col.split('.', 1)
|
||||
table = '%s.' % self.table_alias[table]
|
||||
elif col not in self.extra_select:
|
||||
# Use the root model's database table as the referenced table.
|
||||
table = '%s.' % self.tables[0]
|
||||
else:
|
||||
table = ''
|
||||
result.append('%s%s %s' % (table,
|
||||
qn(orderfield_to_column(col, opts)), order))
|
||||
return result
|
||||
|
||||
def table_alias(self, table_name, create=False):
|
||||
"""
|
||||
Returns a table alias for the given table_name and whether this is a
|
||||
new alias or not.
|
||||
|
||||
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]
|
||||
self.alias_map[alias][ALIAS_REFCOUNT] += 1
|
||||
return alias, False
|
||||
|
||||
# Create a new alias for this table.
|
||||
if table_name not in self.table_map:
|
||||
# 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.alias_map[alias] = [table_name, 1, None]
|
||||
self.table_map.setdefault(table_name, []).append(alias)
|
||||
self.tables.append(alias)
|
||||
return alias, True
|
||||
|
||||
def ref_alias(self, alias):
|
||||
""" Increases the reference count for this alias. """
|
||||
self.alias_map[alias][ALIAS_REFCOUNT] += 1
|
||||
|
||||
def unref_alias(self, alias):
|
||||
""" Decreases the reference count for this alias. """
|
||||
self.alias_map[alias][ALIAS_REFCOUNT] -= 1
|
||||
|
||||
def join(self, (lhs, table, lhs_col, col), always_create=False,
|
||||
exclusions=(), promote=False):
|
||||
"""
|
||||
Returns an alias for a join between 'table' and 'lhs' on the given
|
||||
columns, either reusing an existing alias for that join or creating a
|
||||
new one.
|
||||
|
||||
'lhs' is either an existing table alias or a table name. If
|
||||
'always_create' is True, a new alias is always created, regardless of
|
||||
whether one already exists or not.
|
||||
|
||||
If 'exclusions' is specified, it is something satisfying the container
|
||||
protocol ("foo in exclusions" must work) and specifies a list of
|
||||
aliases that should not be returned, even if they satisfy the join.
|
||||
|
||||
If 'promote' is True, the join type for the alias will be LOUTER (if
|
||||
the alias previously existed, the join type will be promoted from INNER
|
||||
to LOUTER, if necessary).
|
||||
"""
|
||||
if lhs not in self.alias_map:
|
||||
lhs_table = lhs
|
||||
is_table = (lhs is not None)
|
||||
else:
|
||||
lhs_table = self.alias_map[lhs][ALIAS_TABLE]
|
||||
is_table = False
|
||||
t_ident = (lhs_table, table, lhs_col, col)
|
||||
aliases = self.join_map.get(t_ident)
|
||||
if aliases and not always_create:
|
||||
for alias in aliases:
|
||||
if alias not in exclusions:
|
||||
self.ref_alias(alias)
|
||||
if promote:
|
||||
self.alias_map[alias][ALIAS_JOIN][JOIN_TYPE] = \
|
||||
self.LOUTER
|
||||
return alias
|
||||
# If we get to here (no non-excluded alias exists), we'll fall
|
||||
# through to creating a new 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)
|
||||
join_type = promote and self.LOUTER or self.INNER
|
||||
join = [table, alias, join_type, lhs, lhs_col, col]
|
||||
if not lhs:
|
||||
# Not all tables need to be joined to anything. No join type
|
||||
# means the later columns are ignored.
|
||||
join[JOIN_TYPE] = None
|
||||
self.alias_map[alias][ALIAS_JOIN] = join
|
||||
self.join_map.setdefault(t_ident, []).append(alias)
|
||||
self.rev_join_map[alias] = t_ident
|
||||
return alias
|
||||
|
||||
def fill_table_cache(self, opts=None, root_alias=None, cur_depth=0,
|
||||
used=None):
|
||||
"""
|
||||
Fill in the information needed for a select_related query.
|
||||
"""
|
||||
if self.max_depth and cur_depth > self.max_depth:
|
||||
# We've recursed too deeply; bail out.
|
||||
return
|
||||
if not opts:
|
||||
opts = self.model._meta
|
||||
root_alias = self.tables[0]
|
||||
self.select.extend([(root_alias, f) for f in opts.fields])
|
||||
if not used:
|
||||
used = []
|
||||
|
||||
for f in opts.fields:
|
||||
if not f.rel or f.null:
|
||||
continue
|
||||
table = f.rel.to._meta.db_table
|
||||
alias = self.join((root_alias, table, f.column,
|
||||
f.rel.get_related_field().column), exclusion=used)
|
||||
used.append(alias)
|
||||
self.select.extend([(table, f2.column)
|
||||
for f2 in f.rel.to._meta.fields])
|
||||
self.fill_table_cache(f.rel.to._meta, alias, cur_depth + 1, used)
|
||||
|
||||
def add_filter(self, filter_expr, connection=AND, negate=False):
|
||||
"""
|
||||
Add a single filter to the query.
|
||||
"""
|
||||
arg, value = filter_expr
|
||||
parts = arg.split(LOOKUP_SEP)
|
||||
if not parts:
|
||||
raise TypeError("Cannot parse keyword query %r" % arg)
|
||||
|
||||
# Work out the lookup type and remove it from 'parts', if necessary.
|
||||
if len(parts) == 1 or parts[-1] not in QUERY_TERMS:
|
||||
lookup_type = 'exact'
|
||||
else:
|
||||
lookup_type = parts.pop()
|
||||
|
||||
# Interpret '__exact=None' as the sql '= NULL'; otherwise, reject all
|
||||
# uses of None as a query value.
|
||||
# FIXME: Weren't we going to change this so that '__exact=None' was the
|
||||
# same as '__isnull=True'? Need to check the conclusion of the mailing
|
||||
# list thread.
|
||||
if value is None and lookup_type != 'exact':
|
||||
raise ValueError("Cannot use None as a query value")
|
||||
elif callable(value):
|
||||
value = value()
|
||||
|
||||
opts = self.model._meta
|
||||
alias = self.join((None, opts.db_table, None, None))
|
||||
dupe_multis = (connection == AND)
|
||||
last = None
|
||||
|
||||
# FIXME: Using enumerate() here is expensive. We only need 'i' to
|
||||
# check we aren't joining against a non-joinable field. Find a
|
||||
# better way to do this!
|
||||
for i, name in enumerate(parts):
|
||||
joins, opts, orig_field, target_field, target_col = \
|
||||
self.get_next_join(name, opts, alias, dupe_multis)
|
||||
if name == 'pk':
|
||||
name = target_field.name
|
||||
if joins is not None:
|
||||
last = joins
|
||||
alias = joins[-1]
|
||||
else:
|
||||
# Normal field lookup must be the last field in the filter.
|
||||
if i != len(parts) - 1:
|
||||
raise TypeError("Joins on field %r not permitted."
|
||||
% name)
|
||||
|
||||
name = target_col or name
|
||||
|
||||
if target_field is opts.pk and last:
|
||||
# An optimization: if the final join is against a primary key,
|
||||
# we can go back one step in the join chain and compare against
|
||||
# the lhs of the join instead. The result (potentially) involves
|
||||
# one less table join.
|
||||
self.unref_alias(alias)
|
||||
join = self.alias_map[last[-1]][ALIAS_JOIN]
|
||||
alias = join[LHS_ALIAS]
|
||||
name = join[LHS_JOIN_COL]
|
||||
|
||||
if (lookup_type == 'isnull' and value is True):
|
||||
# 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 because it's less
|
||||
# efficient at the database level.
|
||||
self.alias_map[joins[0]][ALIAS_JOIN][JOIN_TYPE] = self.LOUTER
|
||||
|
||||
self.where.add([alias, name, orig_field, lookup_type, value],
|
||||
connection)
|
||||
if negate:
|
||||
self.alias_map[last[0]][ALIAS_JOIN][JOIN_TYPE] = self.LOUTER
|
||||
self.where.negate()
|
||||
|
||||
def add_q(self, q_object):
|
||||
"""
|
||||
Adds a Q-object to the current filter.
|
||||
|
||||
Can also be used to add anything that has an 'add_to_query()' method.
|
||||
"""
|
||||
if hasattr(q_object, 'add_to_query'):
|
||||
# Complex custom objects are responsible for adding themselves.
|
||||
q_object.add_to_query(self)
|
||||
return
|
||||
|
||||
for child in q_object.children:
|
||||
if isinstance(child, tree.Node):
|
||||
self.where.start_subtree(q_object.connection)
|
||||
self.add_q(child)
|
||||
self.where.end_subtree()
|
||||
else:
|
||||
self.add_filter(child, q_object.connection, q_object.negated)
|
||||
|
||||
def get_next_join(self, name, opts, root_alias, dupe_multis):
|
||||
"""
|
||||
Compute the necessary table joins for the field called 'name'. 'opts'
|
||||
is the Options class for the current model (which gives the table we
|
||||
are joining to), root_alias is the alias for the table we are joining
|
||||
to. If dupe_multis is True, any many-to-many or many-to-one joins will
|
||||
always create a new alias (necessary for disjunctive filters).
|
||||
|
||||
Returns a list of aliases involved in the join, the next value for
|
||||
'opts' and the field class that was matched. For a non-joining field,
|
||||
the first value (join alias) is None.
|
||||
"""
|
||||
if name == 'pk':
|
||||
name = opts.pk.name
|
||||
|
||||
field = find_field(name, opts.many_to_many, False)
|
||||
if field:
|
||||
# Many-to-many field defined on the current model.
|
||||
remote_opts = field.rel.to._meta
|
||||
int_alias = self.join((root_alias, field.m2m_db_table(),
|
||||
opts.pk.column, field.m2m_column_name()), dupe_multis)
|
||||
far_alias = self.join((int_alias, remote_opts.db_table,
|
||||
field.m2m_reverse_name(), remote_opts.pk.column),
|
||||
dupe_multis)
|
||||
return ([int_alias, far_alias], remote_opts, field, remote_opts.pk,
|
||||
None)
|
||||
|
||||
field = find_field(name, opts.get_all_related_many_to_many_objects(),
|
||||
True)
|
||||
if field:
|
||||
# Many-to-many field defined on the target model.
|
||||
remote_opts = field.opts
|
||||
field = field.field
|
||||
int_alias = self.join((root_alias, field.m2m_db_table(),
|
||||
opts.pk.column, field.m2m_reverse_name()), dupe_multis)
|
||||
far_alias = self.join((int_alias, remote_opts.db_table,
|
||||
field.m2m_column_name(), remote_opts.pk.column),
|
||||
dupe_multis)
|
||||
# XXX: Why is the final component able to be None here?
|
||||
return ([int_alias, far_alias], remote_opts, field, remote_opts.pk,
|
||||
None)
|
||||
|
||||
field = find_field(name, opts.get_all_related_objects(), True)
|
||||
if field:
|
||||
# One-to-many field (ForeignKey defined on the target model)
|
||||
remote_opts = field.opts
|
||||
field = field.field
|
||||
local_field = opts.get_field(field.rel.field_name)
|
||||
alias = self.join((root_alias, remote_opts.db_table,
|
||||
local_field.column, field.column), dupe_multis)
|
||||
return ([alias], remote_opts, field, field, remote_opts.pk.column)
|
||||
|
||||
|
||||
field = find_field(name, opts.fields, False)
|
||||
if not field:
|
||||
raise TypeError, \
|
||||
("Cannot resolve keyword '%s' into field. Choices are: %s"
|
||||
% (name, ", ".join(get_legal_fields(opts))))
|
||||
|
||||
if field.rel:
|
||||
# One-to-one or many-to-one field
|
||||
remote_opts = field.rel.to._meta
|
||||
alias = self.join((root_alias, remote_opts.db_table, field.column,
|
||||
field.rel.field_name))
|
||||
target = remote_opts.get_field(field.rel.field_name)
|
||||
return [alias], remote_opts, field, target, target.column
|
||||
|
||||
# Only remaining possibility is a normal (direct lookup) field. No
|
||||
# join is required.
|
||||
return None, opts, field, field, None
|
||||
|
||||
def set_limits(self, low=None, high=None):
|
||||
"""
|
||||
Adjusts the limits on the rows retrieved. We use low/high to set these,
|
||||
as it makes it more Pythonic to read and write. When the SQL query is
|
||||
created, they are converted to the appropriate offset and limit values.
|
||||
|
||||
Any limits passed in here are applied relative to the existing
|
||||
constraints. So low is added to the current low value and both will be
|
||||
clamped to any existing high value.
|
||||
"""
|
||||
if high:
|
||||
# None (high_mark's default) is less than any number, so this works.
|
||||
self.high_mark = max(self.high_mark, high)
|
||||
if low:
|
||||
self.low_mark = max(self.high_mark, self.low_mark + low)
|
||||
|
||||
def clear_limits(self):
|
||||
"""
|
||||
Clears any existing limits.
|
||||
"""
|
||||
self.low_mark, self.high_mark = 0, None
|
||||
|
||||
def add_ordering(self, *ordering):
|
||||
"""
|
||||
Adds items from the 'ordering' sequence to the query's "order by"
|
||||
clause.
|
||||
"""
|
||||
self.order_by.extend(ordering)
|
||||
|
||||
def clear_ordering(self):
|
||||
"""
|
||||
Removes any ordering settings.
|
||||
"""
|
||||
self.order_by = []
|
||||
|
||||
def can_filter(self):
|
||||
"""
|
||||
Returns True if adding filters to this instance is still possible.
|
||||
|
||||
Typically, this means no limits or offsets have been put on the results.
|
||||
"""
|
||||
return not (self.low_mark or self.high_mark)
|
||||
|
||||
def add_count_column(self):
|
||||
"""
|
||||
Converts the query to do count(*) or count(distinct(pk)) in order to
|
||||
get its size.
|
||||
"""
|
||||
# TODO: When group_by support is added, this needs to be adjusted so
|
||||
# that it doesn't totally overwrite the select list.
|
||||
if not self.distinct:
|
||||
select = Count()
|
||||
# Distinct handling is now done in Count(), so don't do it at this
|
||||
# level.
|
||||
self.distinct = False
|
||||
else:
|
||||
select = Count((self.table_map[self.model._meta.db_table][0],
|
||||
self.model._meta.pk.column), True)
|
||||
self.select = [select]
|
||||
self.extra_select = {}
|
||||
|
||||
def execute_sql(self, result_type=MULTI):
|
||||
"""
|
||||
Run the query against the database and returns the result(s). The
|
||||
return value is a single data item if result_type is SINGLE, or an
|
||||
iterator over the results if the result_type is MULTI.
|
||||
|
||||
result_type is either MULTI (use fetchmany() to retrieve all rows),
|
||||
SINGLE (only retrieve a single row), or NONE (no results expected).
|
||||
"""
|
||||
try:
|
||||
sql, params = self.as_sql()
|
||||
except EmptyResultSet:
|
||||
raise StopIteration
|
||||
|
||||
cursor = self.connection.cursor()
|
||||
cursor.execute(sql, params)
|
||||
|
||||
if result_type == NONE:
|
||||
return
|
||||
|
||||
if result_type == SINGLE:
|
||||
return cursor.fetchone()
|
||||
|
||||
# The MULTI case.
|
||||
def it():
|
||||
while 1:
|
||||
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
|
||||
if not rows:
|
||||
raise StopIteration
|
||||
yield rows
|
||||
return it()
|
||||
|
||||
class DeleteQuery(Query):
|
||||
"""
|
||||
Delete queries are done through this class, since they are more constrained
|
||||
than general queries.
|
||||
"""
|
||||
def as_sql(self):
|
||||
"""
|
||||
Creates the SQL for this query. Returns the SQL string and list of
|
||||
parameters.
|
||||
"""
|
||||
assert len(self.tables) == 1, \
|
||||
"Can only delete from one table at a time."
|
||||
result = ['DELETE FROM %s' % self.tables[0]]
|
||||
where, params = self.where.as_sql()
|
||||
result.append('WHERE %s' % where)
|
||||
return ' '.join(result), tuple(params)
|
||||
|
||||
def do_query(self, table, where):
|
||||
self.tables = [table]
|
||||
self.where = where
|
||||
self.execute_sql(NONE)
|
||||
|
||||
def delete_batch_related(self, pk_list):
|
||||
"""
|
||||
Set up and execute delete queries for all the objects related to the
|
||||
primary key values in pk_list. To delete the objects themselves, use
|
||||
the delete_batch() method.
|
||||
|
||||
More than one physical query may be executed if there are a
|
||||
lot of values in pk_list.
|
||||
"""
|
||||
cls = self.model
|
||||
for related in cls._meta.get_all_related_many_to_many_objects():
|
||||
if not isinstance(related.field, generic.GenericRelation):
|
||||
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
|
||||
where = WhereNode(self)
|
||||
where.add((None, related.field.m2m_reverse_name(), None,
|
||||
'in',
|
||||
pk_list[offset : offset+GET_ITERATOR_CHUNK_SIZE]),
|
||||
AND)
|
||||
self.do_query(related.field.m2m_db_table(), where)
|
||||
|
||||
for f in cls._meta.many_to_many:
|
||||
w1 = WhereNode(self)
|
||||
if isinstance(f, generic.GenericRelation):
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
field = f.rel.to._meta.get_field(f.content_type_field_name)
|
||||
w1.add((None, field.column, field, 'exact',
|
||||
ContentType.objects.get_for_model(cls).id), AND)
|
||||
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
|
||||
where = WhereNode(self)
|
||||
where.add((None, f.m2m_column_name(), None, 'in',
|
||||
pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]),
|
||||
AND)
|
||||
if w1:
|
||||
where.add(w1, AND)
|
||||
self.do_query(f.m2m_db_table(), where)
|
||||
|
||||
def delete_batch(self, pk_list):
|
||||
"""
|
||||
Set up and execute delete queries for all the objects in pk_list. This
|
||||
should be called after delete_batch_related(), if necessary.
|
||||
|
||||
More than one physical query may be executed if there are a
|
||||
lot of values in pk_list.
|
||||
"""
|
||||
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
|
||||
where = WhereNode(self)
|
||||
field = self.model._meta.pk
|
||||
where.add((None, field.column, field, 'in',
|
||||
pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND)
|
||||
self.do_query(self.model._meta.db_table, where)
|
||||
|
||||
class UpdateQuery(Query):
|
||||
"""
|
||||
Represents an "update" SQL query.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UpdateQuery, self).__init__(*args, **kwargs)
|
||||
self.values = []
|
||||
|
||||
def as_sql(self):
|
||||
"""
|
||||
Creates the SQL for this query. Returns the SQL string and list of
|
||||
parameters.
|
||||
"""
|
||||
assert len(self.tables) == 1, \
|
||||
"Can only update one table at a time."
|
||||
result = ['UPDATE %s' % self.tables[0]]
|
||||
result.append('SET')
|
||||
qn = self.connection.ops.quote_name
|
||||
values = ['%s = %s' % (qn(v[0]), v[1]) for v in self.values]
|
||||
result.append(', '.join(values))
|
||||
where, params = self.where.as_sql()
|
||||
result.append('WHERE %s' % where)
|
||||
return ' '.join(result), tuple(params)
|
||||
|
||||
def do_query(self, table, values, where):
|
||||
self.tables = [table]
|
||||
self.values = values
|
||||
self.where = where
|
||||
self.execute_sql(NONE)
|
||||
|
||||
def clear_related(self, related_field, pk_list):
|
||||
"""
|
||||
Set up and execute an update query that clears related entries for the
|
||||
keys in pk_list.
|
||||
|
||||
This is used by the QuerySet.delete_objects() method.
|
||||
"""
|
||||
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
|
||||
where = WhereNode()
|
||||
f = self.model._meta.pk
|
||||
where.add((None, f, f.db_type(), 'in',
|
||||
pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]),
|
||||
AND)
|
||||
values = [(related_field.column, 'NULL')]
|
||||
self.do_query(self.model._meta.db_table, values, where)
|
||||
|
||||
def find_field(name, field_list, related_query):
|
||||
"""
|
||||
Finds a field with a specific name in a list of field instances.
|
||||
Returns None if there are no matches, or several matches.
|
||||
"""
|
||||
if related_query:
|
||||
matches = [f for f in field_list
|
||||
if f.field.related_query_name() == name]
|
||||
else:
|
||||
matches = [f for f in field_list if f.name == name]
|
||||
if len(matches) != 1:
|
||||
return None
|
||||
return matches[0]
|
||||
|
||||
def field_choices(field_list, related_query):
|
||||
"""
|
||||
Returns the names of the field objects in field_list. Used to construct
|
||||
readable error messages.
|
||||
"""
|
||||
if related_query:
|
||||
return [f.field.related_query_name() for f in field_list]
|
||||
else:
|
||||
return [f.name for f in field_list]
|
||||
|
||||
def get_legal_fields(opts):
|
||||
"""
|
||||
Returns a list of fields that are valid at this point in the query. Used in
|
||||
error reporting.
|
||||
"""
|
||||
return (field_choices(opts.many_to_many, False)
|
||||
+ field_choices( opts.get_all_related_many_to_many_objects(), True)
|
||||
+ field_choices(opts.get_all_related_objects(), True)
|
||||
+ field_choices(opts.fields, False))
|
||||
|
||||
def orderfield_to_column(name, opts):
|
||||
"""
|
||||
For a field name specified in an "order by" clause, returns the database
|
||||
column name. If 'name' is not a field in the current model, it is returned
|
||||
unchanged.
|
||||
"""
|
||||
try:
|
||||
return opts.get_field(name, False).column
|
||||
except FieldDoesNotExist:
|
||||
return name
|
||||
|
27
django/db/models/sql/utils.py
Normal file
27
django/db/models/sql/utils.py
Normal file
@ -0,0 +1,27 @@
|
||||
"""
|
||||
Miscellaneous helper functions.
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
from django.utils.encoding import smart_unicode
|
||||
|
||||
# Django currently supports two forms of ordering.
|
||||
# Form 1 (deprecated) example:
|
||||
# order_by=(('pub_date', 'DESC'), ('headline', 'ASC'), (None, 'RANDOM'))
|
||||
# Form 2 (new-style) example:
|
||||
# order_by=('-pub_date', 'headline', '?')
|
||||
# Form 1 is deprecated and will no longer be supported for Django's first
|
||||
# official release. The following code converts from Form 1 to Form 2.
|
||||
|
||||
LEGACY_ORDERING_MAPPING = {'ASC': '_', 'DESC': '-_', 'RANDOM': '?'}
|
||||
|
||||
def handle_legacy_orderlist(order_list):
|
||||
if not order_list or isinstance(order_list[0], basestring):
|
||||
return order_list
|
||||
else:
|
||||
new_order_list = [LEGACY_ORDERING_MAPPING[j.upper()].replace('_', smart_unicode(i)) for i, j in order_list]
|
||||
warnings.warn("%r ordering syntax is deprecated. Use %r instead."
|
||||
% (order_list, new_order_list), DeprecationWarning)
|
||||
return new_order_list
|
||||
|
151
django/db/models/sql/where.py
Normal file
151
django/db/models/sql/where.py
Normal file
@ -0,0 +1,151 @@
|
||||
"""
|
||||
Code to manage the creation and SQL rendering of 'where' constraints.
|
||||
"""
|
||||
import datetime
|
||||
|
||||
from django.utils import tree
|
||||
from datastructures import EmptyResultSet
|
||||
|
||||
# Connection types
|
||||
AND = 'AND'
|
||||
OR = 'OR'
|
||||
|
||||
class WhereNode(tree.Node):
|
||||
"""
|
||||
Used to represent the SQL where-clause.
|
||||
|
||||
The class is tied to the Query class that created it (in order to create
|
||||
the corret SQL).
|
||||
|
||||
The children in this tree are usually either Q-like objects or lists of
|
||||
[table_alias, field_name, field_class, lookup_type, value]. However, a
|
||||
child could also be any class with as_sql() and relabel_aliases() methods.
|
||||
"""
|
||||
default = AND
|
||||
|
||||
def __init__(self, query=None, children=None, connection=None):
|
||||
super(WhereNode, self).__init__(children, connection)
|
||||
if query:
|
||||
# XXX: Would be nice to use a weakref here, but it seems tricky to
|
||||
# make it work.
|
||||
self.query = query
|
||||
|
||||
def __deepcopy__(self, memodict):
|
||||
"""
|
||||
Used by copy.deepcopy().
|
||||
"""
|
||||
obj = super(WhereNode, self).__deepcopy__(memodict)
|
||||
obj.query = self.query
|
||||
return obj
|
||||
|
||||
def as_sql(self, node=None):
|
||||
"""
|
||||
Returns the SQL version of the where clause and the value to be
|
||||
substituted in. Returns None, None if this node is empty.
|
||||
|
||||
If 'node' is provided, that is the root of the SQL generation
|
||||
(generally not needed except by the internal implementation for
|
||||
recursion).
|
||||
"""
|
||||
if node is None:
|
||||
node = self
|
||||
if not node.children:
|
||||
return None, []
|
||||
result = []
|
||||
result_params = []
|
||||
for child in node.children:
|
||||
if hasattr(child, 'as_sql'):
|
||||
sql, params = child.as_sql()
|
||||
format = '(%s)'
|
||||
elif isinstance(child, tree.Node):
|
||||
sql, params = self.as_sql(child)
|
||||
if child.negated:
|
||||
format = 'NOT (%s)'
|
||||
else:
|
||||
format = '(%s)'
|
||||
else:
|
||||
sql = self.make_atom(child)
|
||||
params = child[2].get_db_prep_lookup(child[3], child[4])
|
||||
format = '%s'
|
||||
result.append(format % sql)
|
||||
result_params.extend(params)
|
||||
conn = ' %s ' % node.connection
|
||||
return conn.join(result), result_params
|
||||
|
||||
def make_atom(self, child):
|
||||
"""
|
||||
Turn a tuple (table_alias, field_name, field_class, lookup_type, value)
|
||||
into valid SQL.
|
||||
|
||||
Returns the string for the SQL fragment. The caller is responsible for
|
||||
converting the child's value into an appropriate for for the parameters
|
||||
list.
|
||||
"""
|
||||
table_alias, name, field, lookup_type, value = child
|
||||
conn = self.query.connection
|
||||
if table_alias:
|
||||
lhs = '%s.%s' % (table_alias, conn.ops.quote_name(name))
|
||||
else:
|
||||
lhs = conn.ops.quote_name(name)
|
||||
field_sql = conn.ops.field_cast_sql(field.db_type()) % lhs
|
||||
|
||||
if isinstance(value, datetime.datetime):
|
||||
# FIXME datetime_cast_sql() should return '%s' by default.
|
||||
cast_sql = conn.ops.datetime_cast_sql() or '%s'
|
||||
else:
|
||||
cast_sql = '%s'
|
||||
|
||||
# FIXME: This is out of place. Move to a function like
|
||||
# datetime_cast_sql()
|
||||
if (lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith')
|
||||
and conn.features.needs_upper_for_iops):
|
||||
format = 'UPPER(%s) %s'
|
||||
else:
|
||||
format = '%s %s'
|
||||
|
||||
if lookup_type in conn.operators:
|
||||
return format % (field_sql, conn.operators[lookup_type] % cast_sql)
|
||||
|
||||
if lookup_type == 'in':
|
||||
if not value:
|
||||
raise EmptyResultSet
|
||||
return '%s IN (%s)' % (field_sql, ', '.join(['%s'] * len(value)))
|
||||
elif lookup_type in ('range', 'year'):
|
||||
return '%s BETWEEN %%s and %%s' % field_sql
|
||||
elif lookup_type in ('month', 'day'):
|
||||
return '%s = %%s' % conn.ops.date_extract_sql(lookup_type,
|
||||
field_sql)
|
||||
elif lookup_type == 'isnull':
|
||||
return '%s IS %sNULL' % (field_sql, (not value and 'NOT ' or ''))
|
||||
elif lookup_type in 'search':
|
||||
return conn.op.fulltest_search_sql(field_sql)
|
||||
elif lookup_type in ('regex', 'iregex'):
|
||||
# FIXME: Factor this out in to conn.ops
|
||||
if settings.DATABASE_ENGINE == 'oracle':
|
||||
if lookup_type == 'regex':
|
||||
match_option = 'c'
|
||||
else:
|
||||
match_option = 'i'
|
||||
return "REGEXP_LIKE(%s, %s, '%s')" % (field_sql, cast_sql,
|
||||
match_option)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
raise TypeError('Invalid lookup_type: %r' % lookup_type)
|
||||
|
||||
def relabel_aliases(self, change_map, node=None):
|
||||
"""
|
||||
Relabels the alias values of any children. 'change_map' is a dictionary
|
||||
mapping old (current) alias values to the new values.
|
||||
"""
|
||||
if not node:
|
||||
node = self
|
||||
for child in node.children:
|
||||
if hasattr(child, 'relabel_aliases'):
|
||||
child.relabel_aliases(change_map)
|
||||
elif isinstance(child, tree.Node):
|
||||
self.relabel_aliases(change_map, child)
|
||||
else:
|
||||
val = child[0]
|
||||
child[0] = change_map.get(val, val)
|
||||
|
Loading…
x
Reference in New Issue
Block a user