summaryrefslogtreecommitdiff
path: root/django/db/models/sql
diff options
context:
space:
mode:
authorJosh Smeaton <josh.smeaton@gmail.com>2015-01-10 02:16:16 +1100
committerJosh Smeaton <josh.smeaton@gmail.com>2015-01-13 09:39:55 +1100
commit21b858cb6735cdfdc695ff7b076e4cbc1981bc88 (patch)
treee7cd589b11e37e99788a85f3ac170b2ecfabed3b /django/db/models/sql
parentf48e2258a96a08dcec843921206bcf7656e3ae45 (diff)
Fixed #24060 -- Added OrderBy Expressions
Diffstat (limited to 'django/db/models/sql')
-rw-r--r--django/db/models/sql/compiler.py86
-rw-r--r--django/db/models/sql/query.py6
2 files changed, 58 insertions, 34 deletions
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index 7d9b3ee609..b091571835 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -1,9 +1,10 @@
from itertools import chain
+import re
import warnings
from django.core.exceptions import FieldError
from django.db.models.constants import LOOKUP_SEP
-from django.db.models.expressions import RawSQL, Ref, Random, ColIndexRef
+from django.db.models.expressions import OrderBy, Random, RawSQL, Ref
from django.db.models.query_utils import select_related_descend, QueryWrapper
from django.db.models.sql.constants import (CURSOR, SINGLE, MULTI, NO_RESULTS,
ORDER_DIR, GET_ITERATOR_CHUNK_SIZE)
@@ -28,6 +29,7 @@ class SQLCompiler(object):
self.select = None
self.annotation_col_map = None
self.klass_info = None
+ self.ordering_parts = re.compile(r'(.*)\s(ASC|DESC)(.*)')
def setup_query(self):
if all(self.query.alias_refcount[a] == 0 for a in self.query.tables):
@@ -105,14 +107,14 @@ class SQLCompiler(object):
cols = expr.get_group_by_cols()
for col in cols:
expressions.append(col)
- for expr, _ in order_by:
+ for expr, (sql, params, is_ref) in order_by:
if expr.contains_aggregate:
continue
# We can skip References to select clause, as all expressions in
# the select clause are already part of the group by.
- if isinstance(expr, Ref):
+ if is_ref:
continue
- expressions.append(expr)
+ expressions.extend(expr.get_source_expressions())
having = self.query.having.get_group_by_cols()
for expr in having:
expressions.append(expr)
@@ -234,54 +236,75 @@ class SQLCompiler(object):
order_by = []
for pos, field in enumerate(ordering):
- if field == '?':
- order_by.append((Random(), asc, False))
+ if hasattr(field, 'resolve_expression'):
+ if not isinstance(field, OrderBy):
+ field = field.asc()
+ if not self.query.standard_ordering:
+ field.reverse_ordering()
+ order_by.append((field, False))
continue
- if isinstance(field, int):
- if field < 0:
- field = -field
- int_ord = desc
- order_by.append((ColIndexRef(field), int_ord, True))
+ if field == '?': # random
+ order_by.append((OrderBy(Random()), False))
continue
+
col, order = get_order_dir(field, asc)
+ descending = True if order == 'DESC' else False
+
if col in self.query.annotation_select:
- order_by.append((Ref(col, self.query.annotation_select[col]), order, True))
+ order_by.append((
+ OrderBy(Ref(col, self.query.annotation_select[col]), descending=descending),
+ True))
continue
+
if '.' in field:
# This came in through an extra(order_by=...) addition. Pass it
# on verbatim.
table, col = col.split('.', 1)
- expr = RawSQL('%s.%s' % (self.quote_name_unless_alias(table), col), [])
- order_by.append((expr, order, False))
+ order_by.append((
+ OrderBy(RawSQL('%s.%s' % (self.quote_name_unless_alias(table), col), [])),
+ False))
continue
- if not self.query._extra or get_order_dir(field)[0] not in self.query._extra:
+
+ if not self.query._extra or col not in self.query._extra:
# 'col' is of the form 'field' or 'field1__field2' or
# '-field1__field2__field', etc.
- order_by.extend(self.find_ordering_name(field, self.query.get_meta(),
- default_order=asc))
+ order_by.extend(self.find_ordering_name(
+ field, self.query.get_meta(), default_order=asc))
else:
if col not in self.query.extra_select:
- order_by.append((RawSQL(*self.query.extra[col]), order, False))
+ order_by.append((
+ OrderBy(RawSQL(*self.query.extra[col]), descending=descending),
+ False))
else:
- order_by.append((Ref(col, RawSQL(*self.query.extra[col])),
- order, True))
+ order_by.append((
+ OrderBy(Ref(col, RawSQL(*self.query.extra[col])), descending=descending),
+ True))
result = []
seen = set()
- for expr, order, is_ref in order_by:
- sql, params = self.compile(expr)
- if (sql, tuple(params)) in seen:
+
+ for expr, is_ref in order_by:
+ resolved = expr.resolve_expression(
+ self.query, allow_joins=True, reuse=None)
+ sql, params = self.compile(resolved)
+ # Don't add the same column twice, but the order direction is
+ # not taken into account so we strip it. When this entire method
+ # is refactored into expressions, then we can check each part as we
+ # generate it.
+ without_ordering = self.ordering_parts.search(sql).group(1)
+ if (without_ordering, tuple(params)) in seen:
continue
- seen.add((sql, tuple(params)))
- result.append((expr, (sql, params, order, is_ref)))
+ seen.add((without_ordering, tuple(params)))
+ result.append((resolved, (sql, params, is_ref)))
return result
def get_extra_select(self, order_by, select):
extra_select = []
select_sql = [t[1] for t in select]
if self.query.distinct and not self.query.distinct_fields:
- for expr, (sql, params, _, is_ref) in order_by:
- if not is_ref and (sql, params) not in select_sql:
- extra_select.append((expr, (sql, params), None))
+ for expr, (sql, params, is_ref) in order_by:
+ without_ordering = self.ordering_parts.search(sql).group(1)
+ if not is_ref and (without_ordering, params) not in select_sql:
+ extra_select.append((expr, (without_ordering, params), None))
return extra_select
def __call__(self, name):
@@ -392,8 +415,8 @@ class SQLCompiler(object):
if order_by:
ordering = []
- for _, (o_sql, o_params, order, _) in order_by:
- ordering.append('%s %s' % (o_sql, order))
+ for _, (o_sql, o_params, _) in order_by:
+ ordering.append(o_sql)
params.extend(o_params)
result.append('ORDER BY %s' % ', '.join(ordering))
@@ -514,6 +537,7 @@ class SQLCompiler(object):
The 'name' is of the form 'field1__field2__...__fieldN'.
"""
name, order = get_order_dir(name, default_order)
+ descending = True if order == 'DESC' else False
pieces = name.split(LOOKUP_SEP)
field, targets, alias, joins, path, opts = self._setup_joins(pieces, opts, alias)
@@ -535,7 +559,7 @@ class SQLCompiler(object):
order, already_seen))
return results
targets, alias, _ = self.query.trim_joins(targets, joins, path)
- return [(t.get_col(alias), order, False) for t in targets]
+ return [(OrderBy(t.get_col(alias), descending=descending), False) for t in targets]
def _setup_joins(self, pieces, opts, alias):
"""
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index a613d8eba4..5d4538b2d9 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -1691,14 +1691,14 @@ class Query(object):
"""
Adds items from the 'ordering' sequence to the query's "order by"
clause. These items are either field names (not column names) --
- possibly with a direction prefix ('-' or '?') -- or ordinals,
- corresponding to column positions in the 'select' list.
+ possibly with a direction prefix ('-' or '?') -- or OrderBy
+ expressions.
If 'ordering' is empty, all ordering is cleared from the query.
"""
errors = []
for item in ordering:
- if not ORDER_PATTERN.match(item):
+ if not hasattr(item, 'resolve_expression') and not ORDER_PATTERN.match(item):
errors.append(item)
if errors:
raise FieldError('Invalid order_by arguments: %s' % errors)