Skip to content

Commit d50988d

Browse files
authored
Fix Exists in order by (#30)
1 parent 6af4bce commit d50988d

File tree

3 files changed

+34
-13
lines changed

3 files changed

+34
-13
lines changed

sql_server/pyodbc/compiler.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import django
55
from django.db.models.aggregates import Avg, Count, StdDev, Variance
6-
from django.db.models.expressions import OrderBy, Ref, Subquery, Value
6+
from django.db.models.expressions import Ref, Subquery, Value
77
from django.db.models.functions import (
88
Chr, ConcatPair, Greatest, Least, Length, LPad, Repeat, RPad, StrIndex, Substr, Trim
99
)
@@ -70,15 +70,6 @@ def _as_sql_lpad(self, compiler, connection):
7070
return template % {'expression': expression, 'length': length, 'fill_text': fill_text}, params
7171

7272

73-
def _as_sql_order_by(self, compiler, connection):
74-
template = None
75-
if self.nulls_last:
76-
template = 'CASE WHEN %(expression)s IS NULL THEN 1 ELSE 0 END, %(expression)s %(ordering)s'
77-
if self.nulls_first:
78-
template = 'CASE WHEN %(expression)s IS NULL THEN 0 ELSE 1 END, %(expression)s %(ordering)s'
79-
return self.as_sql(compiler, connection, template=template)
80-
81-
8273
def _as_sql_repeat(self, compiler, connection):
8374
return self.as_sql(compiler, connection, function='REPLICATE')
8475

@@ -390,8 +381,6 @@ def _as_microsoft(self, node):
390381
as_microsoft = _as_sql_rpad
391382
elif isinstance(node, LPad):
392383
as_microsoft = _as_sql_lpad
393-
elif isinstance(node, OrderBy):
394-
as_microsoft = _as_sql_order_by
395384
elif isinstance(node, Repeat):
396385
as_microsoft = _as_sql_repeat
397386
elif isinstance(node, StdDev):

sql_server/pyodbc/functions.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from django.db.models import BooleanField
33
from django.db.models.functions import Cast
44
from django.db.models.functions.math import ATan2, Log, Ln, Round
5-
from django.db.models.expressions import Case, Exists, When
5+
from django.db.models.expressions import Case, Exists, OrderBy, When
66
from django.db.models.lookups import Lookup
77

88
DJANGO3 = VERSION[0] >= 3
@@ -53,6 +53,27 @@ def sqlserver_lookup(self, compiler, connection):
5353
return lookup.as_sql(compiler, connection)
5454

5555

56+
def sqlserver_orderby(self, compiler, connection):
57+
# MSSQL doesn't allow ORDER BY EXISTS() unless it's wrapped in
58+
# a CASE WHEN.
59+
60+
template = None
61+
if self.nulls_last:
62+
template = 'CASE WHEN %(expression)s IS NULL THEN 1 ELSE 0 END, %(expression)s %(ordering)s'
63+
if self.nulls_first:
64+
template = 'CASE WHEN %(expression)s IS NULL THEN 0 ELSE 1 END, %(expression)s %(ordering)s'
65+
66+
if isinstance(self.expression, Exists):
67+
copy = self.copy()
68+
copy.expression = Case(
69+
When(self.expression, then=True),
70+
default=False,
71+
output_field=BooleanField(),
72+
)
73+
return copy.as_sql(compiler, connection, template=template)
74+
return self.as_sql(compiler, connection, template=template)
75+
76+
5677
ATan2.as_microsoft = sqlserver_atan2
5778
Log.as_microsoft = sqlserver_log
5879
Ln.as_microsoft = sqlserver_ln
@@ -62,3 +83,5 @@ def sqlserver_lookup(self, compiler, connection):
6283
Lookup.as_microsoft = sqlserver_lookup
6384
else:
6485
Exists.as_microsoft = sqlserver_exists
86+
87+
OrderBy.as_microsoft = sqlserver_orderby

testapp/tests/test_expressions.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,15 @@ def test_with_case_when(self):
4444
).get()
4545
self.assertEqual(author.has_post, 1)
4646

47+
@skipUnless(DJANGO3, "Django 3 specific tests")
48+
def test_order_by_exists(self):
49+
author_without_posts = Author.objects.create(name="other author")
50+
authors_by_posts = Author.objects.order_by(Exists(Post.objects.filter(author=OuterRef('pk'))).desc())
51+
self.assertSequenceEqual(authors_by_posts, [self.author, author_without_posts])
52+
53+
authors_by_posts = Author.objects.order_by(Exists(Post.objects.filter(author=OuterRef('pk'))).asc())
54+
self.assertSequenceEqual(authors_by_posts, [author_without_posts, self.author])
55+
4756

4857
@skipUnlessDBFeature('supports_partially_nullable_unique_constraints')
4958
class TestPartiallyNullableUniqueTogether(TestCase):

0 commit comments

Comments
 (0)