Kaydet (Commit) abf8e390 authored tarafından Nick Pope's avatar Nick Pope Kaydeden (comit) Tim Graham

Refs #28643 -- Added Reverse database function.

Thanks Mariusz Felisiak for Oracle advice and review.
üst b69f8eb0
...@@ -215,6 +215,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): ...@@ -215,6 +215,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
conn.create_function('POWER', 2, none_guard(operator.pow)) conn.create_function('POWER', 2, none_guard(operator.pow))
conn.create_function('RADIANS', 1, none_guard(math.radians)) conn.create_function('RADIANS', 1, none_guard(math.radians))
conn.create_function('REPEAT', 2, none_guard(operator.mul)) conn.create_function('REPEAT', 2, none_guard(operator.mul))
conn.create_function('REVERSE', 1, none_guard(lambda x: x[::-1]))
conn.create_function('RPAD', 3, _sqlite_rpad) conn.create_function('RPAD', 3, _sqlite_rpad)
conn.create_function('SIN', 1, none_guard(math.sin)) conn.create_function('SIN', 1, none_guard(math.sin))
conn.create_function('SQRT', 1, none_guard(math.sqrt)) conn.create_function('SQRT', 1, none_guard(math.sqrt))
......
...@@ -11,7 +11,7 @@ from .math import ( ...@@ -11,7 +11,7 @@ from .math import (
) )
from .text import ( from .text import (
Chr, Concat, ConcatPair, Left, Length, Lower, LPad, LTrim, Ord, Repeat, Chr, Concat, ConcatPair, Left, Length, Lower, LPad, LTrim, Ord, Repeat,
Replace, Right, RPad, RTrim, StrIndex, Substr, Trim, Upper, Replace, Reverse, Right, RPad, RTrim, StrIndex, Substr, Trim, Upper,
) )
from .window import ( from .window import (
CumeDist, DenseRank, FirstValue, Lag, LastValue, Lead, NthValue, Ntile, CumeDist, DenseRank, FirstValue, Lag, LastValue, Lead, NthValue, Ntile,
...@@ -34,8 +34,8 @@ __all__ = [ ...@@ -34,8 +34,8 @@ __all__ = [
'Sin', 'Sqrt', 'Tan', 'Sin', 'Sqrt', 'Tan',
# text # text
'Chr', 'Concat', 'ConcatPair', 'Left', 'Length', 'Lower', 'LPad', 'LTrim', 'Chr', 'Concat', 'ConcatPair', 'Left', 'Length', 'Lower', 'LPad', 'LTrim',
'Ord', 'Repeat', 'Replace', 'Right', 'RPad', 'RTrim', 'StrIndex', 'Substr', 'Ord', 'Repeat', 'Replace', 'Reverse', 'Right', 'RPad', 'RTrim',
'Trim', 'Upper', 'StrIndex', 'Substr', 'Trim', 'Upper',
# window # window
'CumeDist', 'DenseRank', 'FirstValue', 'Lag', 'LastValue', 'Lead', 'CumeDist', 'DenseRank', 'FirstValue', 'Lag', 'LastValue', 'Lead',
'NthValue', 'Ntile', 'PercentRank', 'Rank', 'RowNumber', 'NthValue', 'Ntile', 'PercentRank', 'Rank', 'RowNumber',
......
...@@ -185,6 +185,25 @@ class Replace(Func): ...@@ -185,6 +185,25 @@ class Replace(Func):
super().__init__(expression, text, replacement, **extra) super().__init__(expression, text, replacement, **extra)
class Reverse(Transform):
function = 'REVERSE'
lookup_name = 'reverse'
def as_oracle(self, compiler, connection, **extra_context):
# REVERSE in Oracle is undocumented and doesn't support multi-byte
# strings. Use a special subquery instead.
return super().as_sql(
compiler, connection,
template=(
'(SELECT LISTAGG(s) WITHIN GROUP (ORDER BY n DESC) FROM '
'(SELECT LEVEL n, SUBSTR(%(expressions)s, LEVEL, 1) s '
'FROM DUAL CONNECT BY LEVEL <= LENGTH(%(expressions)s)) '
'GROUP BY %(expressions)s)'
),
**extra_context
)
class Right(Left): class Right(Left):
function = 'RIGHT' function = 'RIGHT'
......
...@@ -1377,6 +1377,27 @@ Usage example:: ...@@ -1377,6 +1377,27 @@ Usage example::
>>> Author.objects.values('name') >>> Author.objects.values('name')
<QuerySet [{'name': 'Margareth Johnson'}, {'name': 'Margareth Smith'}]> <QuerySet [{'name': 'Margareth Johnson'}, {'name': 'Margareth Smith'}]>
``Reverse``
-----------
.. class:: Reverse(expression, **extra)
.. versionadded:: 2.2
Accepts a single text field or expression and returns the characters of that
expression in reverse order.
It can also be registered as a transform as described in :class:`Length`. The
default lookup name is ``reverse``.
Usage example::
>>> from django.db.models.functions import Reverse
>>> Author.objects.create(name='Margaret Smith')
>>> author = Author.objects.annotate(backward=Reverse('name')).get()
>>> print(author.backward)
htimS teragraM
``Right`` ``Right``
--------- ---------
......
...@@ -221,10 +221,9 @@ Models ...@@ -221,10 +221,9 @@ Models
* Added support for partial indexes (:attr:`.Index.condition`). * Added support for partial indexes (:attr:`.Index.condition`).
* Added many :ref:`math database functions <math-functions>`. * Added the :class:`~django.db.models.functions.NullIf` and
:class:`~django.db.models.functions.Reverse` database functions, as well as
* The new :class:`~django.db.models.functions.NullIf` database function many :ref:`math database functions <math-functions>`.
returns ``None`` if the two expressions are equal.
* Setting the new ``ignore_conflicts`` parameter of * Setting the new ``ignore_conflicts`` parameter of
:meth:`.QuerySet.bulk_create` to ``True`` tells the database to ignore :meth:`.QuerySet.bulk_create` to ``True`` tells the database to ignore
......
from django.db import connection
from django.db.models import CharField
from django.db.models.functions import Length, Reverse, Trim
from django.test import TestCase
from django.test.utils import register_lookup
from ..models import Author
class ReverseTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.john = Author.objects.create(name='John Smith', alias='smithj')
cls.elena = Author.objects.create(name='Élena Jordan', alias='elena')
cls.python = Author.objects.create(name='パイソン')
def test_null(self):
author = Author.objects.annotate(backward=Reverse('alias')).get(pk=self.python.pk)
self.assertEqual(author.backward, '' if connection.features.interprets_empty_strings_as_nulls else None)
def test_basic(self):
authors = Author.objects.annotate(backward=Reverse('name'))
self.assertQuerysetEqual(
authors,
[
('John Smith', 'htimS nhoJ'),
('Élena Jordan', 'nadroJ anelÉ'),
('パイソン', 'ンソイパ'),
],
lambda a: (a.name, a.backward),
ordered=False,
)
def test_transform(self):
with register_lookup(CharField, Reverse):
authors = Author.objects.all()
self.assertCountEqual(authors.filter(name__reverse=self.john.name[::-1]), [self.john])
self.assertCountEqual(authors.exclude(name__reverse=self.john.name[::-1]), [self.elena, self.python])
def test_expressions(self):
author = Author.objects.annotate(backward=Reverse(Trim('name'))).get(pk=self.john.pk)
self.assertEqual(author.backward, self.john.name[::-1])
with register_lookup(CharField, Reverse), register_lookup(CharField, Length):
authors = Author.objects.all()
self.assertCountEqual(authors.filter(name__reverse__length__gt=7), [self.john, self.elena])
self.assertCountEqual(authors.exclude(name__reverse__length__gt=7), [self.python])
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment