|
6 | 6 | from django.db import DatabaseError, IntegrityError, NotSupportedError
|
7 | 7 | from django.db.models import Count, Expression
|
8 | 8 | from django.db.models.aggregates import Aggregate, Variance
|
9 |
| -from django.db.models.expressions import Col, Ref, Value |
| 9 | +from django.db.models.expressions import Case, Col, Ref, Value, When |
10 | 10 | from django.db.models.functions.comparison import Coalesce
|
11 | 11 | from django.db.models.functions.math import Power
|
| 12 | +from django.db.models.lookups import IsNull |
12 | 13 | from django.db.models.sql import compiler
|
13 | 14 | from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE, MULTI, SINGLE
|
14 | 15 | from django.utils.functional import cached_property
|
@@ -349,8 +350,14 @@ def build_query(self, columns=None):
|
349 | 350 | ordering_fields, sort_ordering, extra_fields = self._get_ordering()
|
350 | 351 | query.project_fields = self.get_project_fields(columns, ordering_fields)
|
351 | 352 | query.ordering = sort_ordering
|
352 |
| - if extra_fields and columns is None: |
353 |
| - query.extra_fields = self.get_project_fields(extra_fields) |
| 353 | + # If columns is None, then get_project_fields() won't add |
| 354 | + # ordering_fields to $project. Use $addFields (extra_fields) instead. |
| 355 | + if columns is None: |
| 356 | + extra_fields += ordering_fields |
| 357 | + if extra_fields: |
| 358 | + query.extra_fields = { |
| 359 | + field_name: expr.as_mql(self, self.connection) for field_name, expr in extra_fields |
| 360 | + } |
354 | 361 | where = self.get_where()
|
355 | 362 | try:
|
356 | 363 | expr = where.as_mql(self, self.connection) if where else {}
|
@@ -462,13 +469,21 @@ def _get_ordering(self):
|
462 | 469 | extra_fields = {}
|
463 | 470 | idx = itertools.count(start=1)
|
464 | 471 | for order in self.order_by_objs or []:
|
465 |
| - if isinstance(order.expression, Col | Ref): |
| 472 | + if isinstance(order.expression, Col): |
| 473 | + field_name = order.expression.as_mql(self, self.connection).removeprefix("$") |
| 474 | + fields[field_name] = order.expression |
| 475 | + elif isinstance(order.expression, Ref): |
466 | 476 | field_name = order.expression.as_mql(self, self.connection).removeprefix("$")
|
467 | 477 | else:
|
468 |
| - # The expression must be added to extra_fields with an alias. |
469 | 478 | field_name = f"__order{next(idx)}"
|
470 |
| - extra_fields[field_name] = order.expression |
471 |
| - fields[field_name] = order.expression |
| 479 | + fields[field_name] = order.expression |
| 480 | + # If the expression is ordered by NULLS FIRST or NULLS LAST, |
| 481 | + # add a field for sorting that's 1 if null or 0 if not. |
| 482 | + if order.nulls_first or order.nulls_last: |
| 483 | + null_fieldname = f"__order{next(idx)}" |
| 484 | + condition = When(IsNull(order.expression, True), then=Value(1)) |
| 485 | + extra_fields[null_fieldname] = Case(condition, default=Value(0)) |
| 486 | + sort_ordering[null_fieldname] = DESCENDING if order.nulls_first else ASCENDING |
472 | 487 | sort_ordering[field_name] = DESCENDING if order.descending else ASCENDING
|
473 | 488 | return tuple(fields.items()), sort_ordering, tuple(extra_fields.items())
|
474 | 489 |
|
|
0 commit comments