Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions django_mongodb_backend/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,15 +260,13 @@ def django_test_expected_failures(self):
"MongoDB does not support this database function.": {
"db_functions.math.test_sign.SignTests",
"db_functions.text.test_chr.ChrTests",
"db_functions.text.test_md5.MD5Tests",
"db_functions.text.test_ord.OrdTests",
"db_functions.text.test_pad.PadTests",
"db_functions.text.test_repeat.RepeatTests",
"db_functions.text.test_reverse.ReverseTests",
"db_functions.text.test_right.RightTests",
"db_functions.text.test_sha1.SHA1Tests",
"db_functions.text.test_sha224.SHA224Tests",
"db_functions.text.test_sha256.SHA256Tests",
"db_functions.text.test_sha384.SHA384Tests",
"db_functions.text.test_sha512.SHA512Tests",
},
Expand Down Expand Up @@ -460,6 +458,15 @@ def django_test_expected_failures(self):
def django_test_skips(self):
skips = super().django_test_skips
skips.update(self._django_test_skips)
if not self.is_mongodb_8_3:
skips.update(
{
"This database function requires MongoDB 8.3+.": {
"db_functions.text.test_md5.MD5Tests",
"db_functions.text.test_sha256.SHA256Tests",
}
}
)
return skips

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

@cached_property
def is_mongodb_8_3(self):
return self.mongodb_version >= (8, 3)

@cached_property
def supports_search(self):
"""Does the server support MongoDB search queries and indexes?"""
Expand Down
28 changes: 28 additions & 0 deletions django_mongodb_backend/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
)
from django.db.models.functions.math import Ceil, Cot, Degrees, Log, Power, Radians, Random, Round
from django.db.models.functions.text import (
MD5,
SHA256,
Concat,
ConcatPair,
Left,
Expand Down Expand Up @@ -114,6 +116,30 @@ def func(self, compiler, connection):
return {f"${operator}": lhs_mql}


def hash_func(algorithm):
def wrapped(self, compiler, connection):
if not connection.features.is_mongodb_8_3:
raise NotSupportedError(f"{self.__class__.__name__} requires MongoDB 8.3+.")
lhs_mql = process_lhs(self, compiler, connection, as_expr=True)
return {
"$cond": {
"if": {"$eq": [lhs_mql, None]},
"then": None, # Return null for null input.
"else": {
"$toLower": {
"$convert": {
"input": {"$hash": {"input": lhs_mql, "algorithm": algorithm}},
"to": "string",
"format": "hex",
}
}
},
}
}

return wrapped


def left(self, compiler, connection):
return self.get_substr().as_mql(compiler, connection, as_expr=True)

Expand Down Expand Up @@ -293,11 +319,13 @@ def register_functions():
Log.as_mql_expr = log
Lower.as_mql_expr = preserve_null("toLower")
LTrim.as_mql_expr = trim("ltrim")
MD5.as_mql_expr = hash_func("md5")
Now.as_mql_expr = now
NullIf.as_mql_expr = null_if
Replace.as_mql_expr = replace
Round.as_mql_expr = round_
RTrim.as_mql_expr = trim("rtrim")
SHA256.as_mql_expr = hash_func("sha256")
StrIndex.as_mql_expr = str_index
Substr.as_mql_expr = substr
Trim.as_mql_expr = trim("trim")
Expand Down
5 changes: 5 additions & 0 deletions docs/releases/6.0.x.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ Django MongoDB Backend 6.0.x
New features
------------

- Added support for the following database functions:

- :class:`~django.db.models.functions.MD5` (requires MongoDB 8.3+)
- :class:`~django.db.models.functions.SHA256` (requires MongoDB 8.3+)

- Added support for Django's warning when performing import-time queries.

Bug fixes
Expand Down
2 changes: 0 additions & 2 deletions docs/topics/known-issues.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,12 @@ Database functions
- :class:`~django.db.models.functions.ExtractQuarter`
- :class:`~django.db.models.functions.LPad`,
:class:`~django.db.models.functions.RPad`
- :class:`~django.db.models.functions.MD5`
- :class:`~django.db.models.functions.Ord`
- :class:`~django.db.models.functions.Repeat`
- :class:`~django.db.models.functions.Reverse`
- :class:`~django.db.models.functions.Right`
- :class:`~django.db.models.functions.SHA1`,
:class:`~django.db.models.functions.SHA224`,
:class:`~django.db.models.functions.SHA256`,
:class:`~django.db.models.functions.SHA384`,
:class:`~django.db.models.functions.SHA512`
- :class:`~django.db.models.functions.Sign`
Expand Down
18 changes: 18 additions & 0 deletions tests/db_functions_/test_text.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.db import NotSupportedError
from django.db.models.functions import MD5, SHA256
from django.test import TestCase, skipIfDBFeature

from .models import DTModel


@skipIfDBFeature("is_mongodb_8_3")
class HashFuncTests(TestCase):
def test_md5_requires_mongodb_8_3(self):
msg = "MD5 requires MongoDB 8.3+."
with self.assertRaisesMessage(NotSupportedError, msg):
DTModel.objects.annotate(md5=MD5("start_datetime")).get()

def test_sha256_requires_mongodb_8_3(self):
msg = "SHA256 requires MongoDB 8.3+."
with self.assertRaisesMessage(NotSupportedError, msg):
DTModel.objects.annotate(sha256=SHA256("start_datetime")).get()