Skip to content

Commit

Permalink
Refs #28643 -- Added MD5 database function.
Browse files Browse the repository at this point in the history
Thanks Nick Pope and Simon Charette for reviews.
  • Loading branch information
felixxm committed Feb 20, 2019
1 parent edec11c commit 3d0499b
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 5 deletions.
2 changes: 2 additions & 0 deletions django/db/backends/sqlite3/base.py
Expand Up @@ -4,6 +4,7 @@
import datetime
import decimal
import functools
import hashlib
import math
import operator
import re
Expand Down Expand Up @@ -217,6 +218,7 @@ def get_new_connection(self, conn_params):
conn.create_function('LN', 1, none_guard(math.log))
conn.create_function('LOG', 2, none_guard(lambda x, y: math.log(y, x)))
conn.create_function('LPAD', 3, _sqlite_lpad)
conn.create_function('MD5', 1, none_guard(lambda x: hashlib.md5(x.encode()).hexdigest()))
conn.create_function('MOD', 2, none_guard(math.fmod))
conn.create_function('PI', 0, lambda: math.pi)
conn.create_function('POWER', 2, none_guard(operator.pow))
Expand Down
9 changes: 5 additions & 4 deletions django/db/models/functions/__init__.py
Expand Up @@ -10,8 +10,9 @@
Mod, Pi, Power, Radians, Round, Sin, Sqrt, Tan,
)
from .text import (
Chr, Concat, ConcatPair, Left, Length, Lower, LPad, LTrim, Ord, Repeat,
Replace, Reverse, Right, RPad, RTrim, StrIndex, Substr, Trim, Upper,
MD5, Chr, Concat, ConcatPair, Left, Length, Lower, LPad, LTrim, Ord,
Repeat, Replace, Reverse, Right, RPad, RTrim, StrIndex, Substr, Trim,
Upper,
)
from .window import (
CumeDist, DenseRank, FirstValue, Lag, LastValue, Lead, NthValue, Ntile,
Expand All @@ -33,8 +34,8 @@
'Exp', 'Floor', 'Ln', 'Log', 'Mod', 'Pi', 'Power', 'Radians', 'Round',
'Sin', 'Sqrt', 'Tan',
# text
'Chr', 'Concat', 'ConcatPair', 'Left', 'Length', 'Lower', 'LPad', 'LTrim',
'Ord', 'Repeat', 'Replace', 'Reverse', 'Right', 'RPad', 'RTrim',
'MD5', 'Chr', 'Concat', 'ConcatPair', 'Left', 'Length', 'Lower', 'LPad',
'LTrim', 'Ord', 'Repeat', 'Replace', 'Reverse', 'Right', 'RPad', 'RTrim',
'StrIndex', 'Substr', 'Trim', 'Upper',
# window
'CumeDist', 'DenseRank', 'FirstValue', 'Lag', 'LastValue', 'Lead',
Expand Down
16 changes: 16 additions & 0 deletions django/db/models/functions/text.py
Expand Up @@ -150,6 +150,22 @@ class LTrim(Transform):
lookup_name = 'ltrim'


class MD5(Transform):
function = 'MD5'
lookup_name = 'md5'

def as_oracle(self, compiler, connection, **extra_context):
return super().as_sql(
compiler,
connection,
template=(
"LOWER(RAWTOHEX(STANDARD_HASH(UTL_I18N.STRING_TO_RAW("
"%(expressions)s, 'AL32UTF8'), '%(function)s')))"
),
**extra_context,
)


class Ord(Transform):
function = 'ASCII'
lookup_name = 'ord'
Expand Down
18 changes: 18 additions & 0 deletions docs/ref/models/database-functions.txt
Expand Up @@ -1303,6 +1303,24 @@ Usage example::
Similar to :class:`~django.db.models.functions.Trim`, but removes only leading
spaces.

``MD5``
-------

.. class:: MD5(expression, **extra)

Accepts a single text field or expression and returns the MD5 hash for the
string.

It can also be registered as a transform as described in :class:`Length`.

Usage example::

>>> from django.db.models.functions import MD5
>>> Author.objects.create(name='Margaret Smith')
>>> author = Author.objects.annotate(name_md5=MD5('name')).get()
>>> print(author.name_md5)
749fb689816b2db85f5b169c2055b247

``Ord``
-------

Expand Down
2 changes: 1 addition & 1 deletion docs/releases/3.0.txt
Expand Up @@ -162,7 +162,7 @@ Migrations
Models
~~~~~~

* ...
* Added the :class:`~django.db.models.functions.MD5` database function.

Requests and Responses
~~~~~~~~~~~~~~~~~~~~~~
Expand Down
41 changes: 41 additions & 0 deletions tests/db_functions/text/test_md5.py
@@ -0,0 +1,41 @@
from django.db import connection
from django.db.models import CharField
from django.db.models.functions import MD5
from django.test import TestCase
from django.test.utils import register_lookup

from ..models import Author


class MD5Tests(TestCase):
@classmethod
def setUpTestData(cls):
Author.objects.bulk_create([
Author(alias='John Smith'),
Author(alias='Jordan Élena'),
Author(alias='皇帝'),
Author(alias=''),
Author(alias=None),
])

def test_basic(self):
authors = Author.objects.annotate(
md5_alias=MD5('alias'),
).values_list('md5_alias', flat=True).order_by('pk')
self.assertSequenceEqual(
authors,
[
'6117323d2cabbc17d44c2b44587f682c',
'ca6d48f6772000141e66591aee49d56c',
'bf2c13bc1154e3d2e7df848cbc8be73d',
'd41d8cd98f00b204e9800998ecf8427e',
'd41d8cd98f00b204e9800998ecf8427e' if connection.features.interprets_empty_strings_as_nulls else None,
],
)

def test_transform(self):
with register_lookup(CharField, MD5):
authors = Author.objects.filter(
alias__md5='6117323d2cabbc17d44c2b44587f682c',
).values_list('alias', flat=True)
self.assertSequenceEqual(authors, ['John Smith'])

0 comments on commit 3d0499b

Please sign in to comment.