mirror of https://github.com/django/django.git
Refs #28643 -- Added LPad and RPad database functions.
Thanks Tim Graham for the review.
This commit is contained in:
parent
8d67c7cffd
commit
cede5111bb
|
@ -169,6 +169,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
conn.create_function("regexp", 2, _sqlite_regexp)
|
conn.create_function("regexp", 2, _sqlite_regexp)
|
||||||
conn.create_function("django_format_dtdelta", 3, _sqlite_format_dtdelta)
|
conn.create_function("django_format_dtdelta", 3, _sqlite_format_dtdelta)
|
||||||
conn.create_function("django_power", 2, _sqlite_power)
|
conn.create_function("django_power", 2, _sqlite_power)
|
||||||
|
conn.create_function('LPAD', 3, _sqlite_lpad)
|
||||||
|
conn.create_function('RPAD', 3, _sqlite_rpad)
|
||||||
conn.execute('PRAGMA foreign_keys = ON')
|
conn.execute('PRAGMA foreign_keys = ON')
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
|
@ -467,5 +469,15 @@ def _sqlite_regexp(re_pattern, re_string):
|
||||||
return bool(re.search(re_pattern, str(re_string))) if re_string is not None else False
|
return bool(re.search(re_pattern, str(re_string))) if re_string is not None else False
|
||||||
|
|
||||||
|
|
||||||
|
def _sqlite_lpad(text, length, fill_text):
|
||||||
|
if len(text) >= length:
|
||||||
|
return text[:length]
|
||||||
|
return (fill_text * length)[:length - len(text)] + text
|
||||||
|
|
||||||
|
|
||||||
|
def _sqlite_rpad(text, length, fill_text):
|
||||||
|
return (text + fill_text * length)[:length]
|
||||||
|
|
||||||
|
|
||||||
def _sqlite_power(x, y):
|
def _sqlite_power(x, y):
|
||||||
return x ** y
|
return x ** y
|
||||||
|
|
|
@ -6,8 +6,8 @@ from .datetime import (
|
||||||
TruncQuarter, TruncSecond, TruncTime, TruncWeek, TruncYear,
|
TruncQuarter, TruncSecond, TruncTime, TruncWeek, TruncYear,
|
||||||
)
|
)
|
||||||
from .text import (
|
from .text import (
|
||||||
Chr, Concat, ConcatPair, Left, Length, Lower, LTrim, Ord, Replace, Right,
|
Chr, Concat, ConcatPair, Left, Length, Lower, LPad, LTrim, Ord, Replace,
|
||||||
RTrim, StrIndex, Substr, Trim, Upper,
|
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,
|
||||||
|
@ -24,8 +24,9 @@ __all__ = [
|
||||||
'TruncMinute', 'TruncMonth', 'TruncQuarter', 'TruncSecond', 'TruncTime',
|
'TruncMinute', 'TruncMonth', 'TruncQuarter', 'TruncSecond', 'TruncTime',
|
||||||
'TruncWeek', 'TruncYear',
|
'TruncWeek', 'TruncYear',
|
||||||
# text
|
# text
|
||||||
'Chr', 'Concat', 'ConcatPair', 'Left', 'Length', 'Lower', 'LTrim', 'Ord',
|
'Chr', 'Concat', 'ConcatPair', 'Left', 'Length', 'Lower', 'LPad', 'LTrim',
|
||||||
'Replace', 'Right', 'RTrim', 'StrIndex', 'Substr', 'Trim', 'Upper',
|
'Ord', 'Replace', 'Right', 'RPad', 'RTrim', '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',
|
||||||
|
|
|
@ -110,6 +110,15 @@ class Lower(Transform):
|
||||||
lookup_name = 'lower'
|
lookup_name = 'lower'
|
||||||
|
|
||||||
|
|
||||||
|
class LPad(Func):
|
||||||
|
function = 'LPAD'
|
||||||
|
|
||||||
|
def __init__(self, expression, length, fill_text=Value(' '), **extra):
|
||||||
|
if not hasattr(length, 'resolve_expression') and length < 0:
|
||||||
|
raise ValueError("'length' must be greater or equal to 0.")
|
||||||
|
super().__init__(expression, length, fill_text, **extra)
|
||||||
|
|
||||||
|
|
||||||
class LTrim(Transform):
|
class LTrim(Transform):
|
||||||
function = 'LTRIM'
|
function = 'LTRIM'
|
||||||
lookup_name = 'ltrim'
|
lookup_name = 'ltrim'
|
||||||
|
@ -141,6 +150,10 @@ class Right(Left):
|
||||||
return Substr(self.source_expressions[0], self.source_expressions[1] * Value(-1))
|
return Substr(self.source_expressions[0], self.source_expressions[1] * Value(-1))
|
||||||
|
|
||||||
|
|
||||||
|
class RPad(LPad):
|
||||||
|
function = 'RPAD'
|
||||||
|
|
||||||
|
|
||||||
class RTrim(Transform):
|
class RTrim(Transform):
|
||||||
function = 'RTRIM'
|
function = 'RTRIM'
|
||||||
lookup_name = 'rtrim'
|
lookup_name = 'rtrim'
|
||||||
|
|
|
@ -800,6 +800,27 @@ Usage example::
|
||||||
>>> print(author.name_lower)
|
>>> print(author.name_lower)
|
||||||
margaret smith
|
margaret smith
|
||||||
|
|
||||||
|
``LPad``
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. class:: LPad(expression, length, fill_text=Value(' '), **extra)
|
||||||
|
|
||||||
|
.. versionadded:: 2.1
|
||||||
|
|
||||||
|
Returns the value of the given text field or expression padded on the left side
|
||||||
|
with ``fill_text`` so that the resulting value is ``length`` characters long.
|
||||||
|
The default ``fill_text`` is a space.
|
||||||
|
|
||||||
|
Usage example::
|
||||||
|
|
||||||
|
>>> from django.db.models import Value
|
||||||
|
>>> from django.db.models.functions import LPad
|
||||||
|
>>> Author.objects.create(name='John', alias='j')
|
||||||
|
>>> Author.objects.update(name=LPad('name', 8, Value('abc')))
|
||||||
|
1
|
||||||
|
>>> print(Author.objects.get(alias='j').name)
|
||||||
|
abcaJohn
|
||||||
|
|
||||||
``LTrim``
|
``LTrim``
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
@ -872,6 +893,16 @@ Usage example::
|
||||||
>>> print(author.last_letter)
|
>>> print(author.last_letter)
|
||||||
h
|
h
|
||||||
|
|
||||||
|
``RPad``
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. class:: RPad(expression, length, fill_text=Value(' '), **extra)
|
||||||
|
|
||||||
|
.. versionadded:: 2.1
|
||||||
|
|
||||||
|
Similar to :class:`~django.db.models.functions.LPad`, but pads on the right
|
||||||
|
side.
|
||||||
|
|
||||||
``RTrim``
|
``RTrim``
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
|
|
@ -205,10 +205,12 @@ Models
|
||||||
* A number of new text database functions are added:
|
* A number of new text database functions are added:
|
||||||
:class:`~django.db.models.functions.Chr`,
|
:class:`~django.db.models.functions.Chr`,
|
||||||
:class:`~django.db.models.functions.Left`,
|
:class:`~django.db.models.functions.Left`,
|
||||||
|
:class:`~django.db.models.functions.LPad`,
|
||||||
:class:`~django.db.models.functions.LTrim`,
|
:class:`~django.db.models.functions.LTrim`,
|
||||||
:class:`~django.db.models.functions.Ord`,
|
:class:`~django.db.models.functions.Ord`,
|
||||||
:class:`~django.db.models.functions.Replace`,
|
:class:`~django.db.models.functions.Replace`,
|
||||||
:class:`~django.db.models.functions.Right`,
|
:class:`~django.db.models.functions.Right`,
|
||||||
|
:class:`~django.db.models.functions.RPad`,
|
||||||
:class:`~django.db.models.functions.RTrim`, and
|
:class:`~django.db.models.functions.RTrim`, and
|
||||||
:class:`~django.db.models.functions.Trim`.
|
:class:`~django.db.models.functions.Trim`.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
from django.db.models import Value
|
||||||
|
from django.db.models.functions import LPad, RPad
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from .models import Author
|
||||||
|
|
||||||
|
|
||||||
|
class PadTests(TestCase):
|
||||||
|
def test_pad(self):
|
||||||
|
Author.objects.create(name='John', alias='j')
|
||||||
|
tests = (
|
||||||
|
(LPad('name', 7, Value('xy')), 'xyxJohn'),
|
||||||
|
(RPad('name', 7, Value('xy')), 'Johnxyx'),
|
||||||
|
(LPad('name', 6, Value('x')), 'xxJohn'),
|
||||||
|
(RPad('name', 6, Value('x')), 'Johnxx'),
|
||||||
|
# The default pad string is a space.
|
||||||
|
(LPad('name', 6), ' John'),
|
||||||
|
(RPad('name', 6), 'John '),
|
||||||
|
# If string is longer than length it is truncated.
|
||||||
|
(LPad('name', 2), 'Jo'),
|
||||||
|
(RPad('name', 2), 'Jo'),
|
||||||
|
(LPad('name', 0), ''),
|
||||||
|
(RPad('name', 0), ''),
|
||||||
|
)
|
||||||
|
for function, padded_name in tests:
|
||||||
|
with self.subTest(function=function):
|
||||||
|
authors = Author.objects.annotate(padded_name=function)
|
||||||
|
self.assertQuerysetEqual(authors, [padded_name], lambda a: a.padded_name, ordered=False)
|
||||||
|
|
||||||
|
def test_pad_negative_length(self):
|
||||||
|
for function in (LPad, RPad):
|
||||||
|
with self.subTest(function=function):
|
||||||
|
with self.assertRaisesMessage(ValueError, "'length' must be greater or equal to 0."):
|
||||||
|
function('name', -1)
|
Loading…
Reference in New Issue