Refs #28643 -- Added LPad and RPad database functions.

Thanks Tim Graham for the review.
This commit is contained in:
Mariusz Felisiak 2018-03-19 17:35:16 +01:00 committed by GitHub
parent 8d67c7cffd
commit cede5111bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 97 additions and 4 deletions

View File

@ -169,6 +169,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
conn.create_function("regexp", 2, _sqlite_regexp)
conn.create_function("django_format_dtdelta", 3, _sqlite_format_dtdelta)
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')
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
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):
return x ** y

View File

@ -6,8 +6,8 @@ from .datetime import (
TruncQuarter, TruncSecond, TruncTime, TruncWeek, TruncYear,
)
from .text import (
Chr, Concat, ConcatPair, Left, Length, Lower, LTrim, Ord, Replace, Right,
RTrim, StrIndex, Substr, Trim, Upper,
Chr, Concat, ConcatPair, Left, Length, Lower, LPad, LTrim, Ord, Replace,
Right, RPad, RTrim, StrIndex, Substr, Trim, Upper,
)
from .window import (
CumeDist, DenseRank, FirstValue, Lag, LastValue, Lead, NthValue, Ntile,
@ -24,8 +24,9 @@ __all__ = [
'TruncMinute', 'TruncMonth', 'TruncQuarter', 'TruncSecond', 'TruncTime',
'TruncWeek', 'TruncYear',
# text
'Chr', 'Concat', 'ConcatPair', 'Left', 'Length', 'Lower', 'LTrim', 'Ord',
'Replace', 'Right', 'RTrim', 'StrIndex', 'Substr', 'Trim', 'Upper',
'Chr', 'Concat', 'ConcatPair', 'Left', 'Length', 'Lower', 'LPad', 'LTrim',
'Ord', 'Replace', 'Right', 'RPad', 'RTrim', 'StrIndex', 'Substr', 'Trim',
'Upper',
# window
'CumeDist', 'DenseRank', 'FirstValue', 'Lag', 'LastValue', 'Lead',
'NthValue', 'Ntile', 'PercentRank', 'Rank', 'RowNumber',

View File

@ -110,6 +110,15 @@ class Lower(Transform):
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):
function = 'LTRIM'
lookup_name = 'ltrim'
@ -141,6 +150,10 @@ class Right(Left):
return Substr(self.source_expressions[0], self.source_expressions[1] * Value(-1))
class RPad(LPad):
function = 'RPAD'
class RTrim(Transform):
function = 'RTRIM'
lookup_name = 'rtrim'

View File

@ -800,6 +800,27 @@ Usage example::
>>> print(author.name_lower)
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``
---------
@ -872,6 +893,16 @@ Usage example::
>>> print(author.last_letter)
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``
---------

View File

@ -205,10 +205,12 @@ Models
* A number of new text database functions are added:
:class:`~django.db.models.functions.Chr`,
:class:`~django.db.models.functions.Left`,
:class:`~django.db.models.functions.LPad`,
:class:`~django.db.models.functions.LTrim`,
:class:`~django.db.models.functions.Ord`,
:class:`~django.db.models.functions.Replace`,
:class:`~django.db.models.functions.Right`,
:class:`~django.db.models.functions.RPad`,
:class:`~django.db.models.functions.RTrim`, and
:class:`~django.db.models.functions.Trim`.

View File

@ -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)