Skip to content

Commit ed5bbbf

Browse files
committed
INTPYTHON-974 Add support for MD5/SHA256 database functions
1 parent 2326fee commit ed5bbbf

5 files changed

Lines changed: 64 additions & 4 deletions

File tree

django_mongodb_backend/features.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,15 +260,13 @@ def django_test_expected_failures(self):
260260
"MongoDB does not support this database function.": {
261261
"db_functions.math.test_sign.SignTests",
262262
"db_functions.text.test_chr.ChrTests",
263-
"db_functions.text.test_md5.MD5Tests",
264263
"db_functions.text.test_ord.OrdTests",
265264
"db_functions.text.test_pad.PadTests",
266265
"db_functions.text.test_repeat.RepeatTests",
267266
"db_functions.text.test_reverse.ReverseTests",
268267
"db_functions.text.test_right.RightTests",
269268
"db_functions.text.test_sha1.SHA1Tests",
270269
"db_functions.text.test_sha224.SHA224Tests",
271-
"db_functions.text.test_sha256.SHA256Tests",
272270
"db_functions.text.test_sha384.SHA384Tests",
273271
"db_functions.text.test_sha512.SHA512Tests",
274272
},
@@ -460,6 +458,15 @@ def django_test_expected_failures(self):
460458
def django_test_skips(self):
461459
skips = super().django_test_skips
462460
skips.update(self._django_test_skips)
461+
if not self.is_mongodb_8_3:
462+
skips.update(
463+
{
464+
"MD5 and SHA256 functions required MongoDB 8.3.+.": {
465+
"db_functions.text.test_md5.MD5Tests",
466+
"db_functions.text.test_sha256.SHA256Tests",
467+
}
468+
}
469+
)
463470
return skips
464471

465472
# Tests that are expected to raise certain exceptions:
@@ -767,6 +774,10 @@ def mongodb_version(self):
767774
def is_mongodb_8_0(self):
768775
return self.mongodb_version >= (8, 0)
769776

777+
@cached_property
778+
def is_mongodb_8_3(self):
779+
return self.mongodb_version >= (8, 3)
780+
770781
@cached_property
771782
def supports_search(self):
772783
"""Does the server support MongoDB search queries and indexes?"""

django_mongodb_backend/functions.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
)
2727
from django.db.models.functions.math import Ceil, Cot, Degrees, Log, Power, Radians, Random, Round
2828
from django.db.models.functions.text import (
29+
MD5,
30+
SHA256,
2931
Concat,
3032
ConcatPair,
3133
Left,
@@ -114,6 +116,30 @@ def func(self, compiler, connection):
114116
return {f"${operator}": lhs_mql}
115117

116118

119+
def hash_func(algorithm):
120+
def wrapped(self, compiler, connection):
121+
if not connection.features.is_mongodb_8_3:
122+
raise NotSupportedError(f"{self.__class__.__name__} requires MongoDB 8.3+.")
123+
lhs_mql = process_lhs(self, compiler, connection, as_expr=True)
124+
return {
125+
"$cond": {
126+
"if": {"$eq": [lhs_mql, None]},
127+
"then": None,
128+
"else": {
129+
"$toLower": {
130+
"$convert": {
131+
"input": {"$hash": {"input": lhs_mql, "algorithm": algorithm}},
132+
"to": "string",
133+
"format": "hex",
134+
}
135+
}
136+
},
137+
}
138+
}
139+
140+
return wrapped
141+
142+
117143
def left(self, compiler, connection):
118144
return self.get_substr().as_mql(compiler, connection, as_expr=True)
119145

@@ -290,6 +316,7 @@ def register_functions():
290316
JSONArray.as_mql_expr = partialmethod(process_lhs, as_expr=True)
291317
Left.as_mql_expr = left
292318
Length.as_mql_expr = length
319+
MD5.as_mql_expr = hash_func("md5")
293320
Log.as_mql_expr = log
294321
Lower.as_mql_expr = preserve_null("toLower")
295322
LTrim.as_mql_expr = trim("ltrim")
@@ -298,6 +325,7 @@ def register_functions():
298325
Replace.as_mql_expr = replace
299326
Round.as_mql_expr = round_
300327
RTrim.as_mql_expr = trim("rtrim")
328+
SHA256.as_mql_expr = hash_func("sha256")
301329
StrIndex.as_mql_expr = str_index
302330
Substr.as_mql_expr = substr
303331
Trim.as_mql_expr = trim("trim")

docs/releases/6.0.x.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ Django MongoDB Backend 6.0.x
1010
New features
1111
------------
1212

13+
- Added support for the following database functions:
14+
15+
- :class:`~django.db.models.functions.MD5` (requires MongoDB 8.3+)
16+
- :class:`~django.db.models.functions.SHA256` (requires MongoDB 8.3+)
17+
1318
- Added support for Django's warning when performing import-time queries.
1419

1520
Bug fixes

docs/topics/known-issues.rst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,12 @@ Database functions
7979
- :class:`~django.db.models.functions.ExtractQuarter`
8080
- :class:`~django.db.models.functions.LPad`,
8181
:class:`~django.db.models.functions.RPad`
82-
- :class:`~django.db.models.functions.MD5`
8382
- :class:`~django.db.models.functions.Ord`
8483
- :class:`~django.db.models.functions.Repeat`
8584
- :class:`~django.db.models.functions.Reverse`
8685
- :class:`~django.db.models.functions.Right`
8786
- :class:`~django.db.models.functions.SHA1`,
8887
:class:`~django.db.models.functions.SHA224`,
89-
:class:`~django.db.models.functions.SHA256`,
9088
:class:`~django.db.models.functions.SHA384`,
9189
:class:`~django.db.models.functions.SHA512`
9290
- :class:`~django.db.models.functions.Sign`

tests/db_functions_/test_text.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from django.db import NotSupportedError
2+
from django.db.models.functions import MD5, SHA256
3+
from django.test import TestCase, skipIfDBFeature
4+
5+
from .models import DTModel
6+
7+
8+
@skipIfDBFeature("is_mongodb_8_3")
9+
class HashFuncTests(TestCase):
10+
def test_md5_requires_mongodb_8_3(self):
11+
msg = "MD5 requires MongoDB 8.3+."
12+
with self.assertRaisesMessage(NotSupportedError, msg):
13+
DTModel.objects.annotate(md5=MD5("start_datetime")).get()
14+
15+
def test_sha256_requires_mongodb_8_3(self):
16+
msg = "SHA256 requires MongoDB 8.3+."
17+
with self.assertRaisesMessage(NotSupportedError, msg):
18+
DTModel.objects.annotate(sha256=SHA256("start_datetime")).get()

0 commit comments

Comments
 (0)