Reorganized text db function tests.

This commit is contained in:
Nick Pope 2018-08-16 00:45:11 +01:00 committed by Tim Graham
parent cd790ed1a6
commit 3288985822
16 changed files with 277 additions and 264 deletions

View File

@ -6,8 +6,7 @@ from django.db import connection
from django.db.models import CharField, TextField, Value as V from django.db.models import CharField, TextField, Value as V
from django.db.models.expressions import RawSQL from django.db.models.expressions import RawSQL
from django.db.models.functions import ( from django.db.models.functions import (
Coalesce, Concat, ConcatPair, Greatest, Least, Length, Lower, Now, Coalesce, Greatest, Least, Length, Lower, Now, Upper,
StrIndex, Substr, Upper,
) )
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
from django.utils import timezone from django.utils import timezone
@ -311,214 +310,6 @@ class FunctionTests(TestCase):
[obj], [obj],
) )
def test_concat(self):
Author.objects.create(name='Jayden')
Author.objects.create(name='John Smith', alias='smithj', goes_by='John')
Author.objects.create(name='Margaret', goes_by='Maggie')
Author.objects.create(name='Rhonda', alias='adnohR')
authors = Author.objects.annotate(joined=Concat('alias', 'goes_by'))
self.assertQuerysetEqual(
authors.order_by('name'), [
'',
'smithjJohn',
'Maggie',
'adnohR',
],
lambda a: a.joined
)
with self.assertRaisesMessage(ValueError, 'Concat must take at least two expressions'):
Author.objects.annotate(joined=Concat('alias'))
def test_concat_many(self):
Author.objects.create(name='Jayden')
Author.objects.create(name='John Smith', alias='smithj', goes_by='John')
Author.objects.create(name='Margaret', goes_by='Maggie')
Author.objects.create(name='Rhonda', alias='adnohR')
authors = Author.objects.annotate(
joined=Concat('name', V(' ('), 'goes_by', V(')'), output_field=CharField()),
)
self.assertQuerysetEqual(
authors.order_by('name'), [
'Jayden ()',
'John Smith (John)',
'Margaret (Maggie)',
'Rhonda ()',
],
lambda a: a.joined
)
def test_concat_mixed_char_text(self):
Article.objects.create(title='The Title', text=lorem_ipsum, written=timezone.now())
article = Article.objects.annotate(
title_text=Concat('title', V(' - '), 'text', output_field=TextField()),
).get(title='The Title')
self.assertEqual(article.title + ' - ' + article.text, article.title_text)
# wrap the concat in something else to ensure that we're still
# getting text rather than bytes
article = Article.objects.annotate(
title_text=Upper(Concat('title', V(' - '), 'text', output_field=TextField())),
).get(title='The Title')
expected = article.title + ' - ' + article.text
self.assertEqual(expected.upper(), article.title_text)
@skipUnless(connection.vendor == 'sqlite', "sqlite specific implementation detail.")
def test_concat_coalesce_idempotent(self):
pair = ConcatPair(V('a'), V('b'))
# Check nodes counts
self.assertEqual(len(list(pair.flatten())), 3)
self.assertEqual(len(list(pair.coalesce().flatten())), 7) # + 2 Coalesce + 2 Value()
self.assertEqual(len(list(pair.flatten())), 3)
def test_concat_sql_generation_idempotency(self):
qs = Article.objects.annotate(description=Concat('title', V(': '), 'summary'))
# Multiple compilations should not alter the generated query.
self.assertEqual(str(qs.query), str(qs.all().query))
def test_lower(self):
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='Rhonda')
authors = Author.objects.annotate(lower_name=Lower('name'))
self.assertQuerysetEqual(
authors.order_by('name'), [
'john smith',
'rhonda',
],
lambda a: a.lower_name
)
Author.objects.update(name=Lower('name'))
self.assertQuerysetEqual(
authors.order_by('name'), [
('john smith', 'john smith'),
('rhonda', 'rhonda'),
],
lambda a: (a.lower_name, a.name)
)
with self.assertRaisesMessage(TypeError, "'Lower' takes exactly 1 argument (2 given)"):
Author.objects.update(name=Lower('name', 'name'))
def test_upper(self):
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='Rhonda')
authors = Author.objects.annotate(upper_name=Upper('name'))
self.assertQuerysetEqual(
authors.order_by('name'), [
'JOHN SMITH',
'RHONDA',
],
lambda a: a.upper_name
)
Author.objects.update(name=Upper('name'))
self.assertQuerysetEqual(
authors.order_by('name'), [
('JOHN SMITH', 'JOHN SMITH'),
('RHONDA', 'RHONDA'),
],
lambda a: (a.upper_name, a.name)
)
def test_length(self):
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='Rhonda')
authors = Author.objects.annotate(
name_length=Length('name'),
alias_length=Length('alias'))
self.assertQuerysetEqual(
authors.order_by('name'), [
(10, 6),
(6, None),
],
lambda a: (a.name_length, a.alias_length)
)
self.assertEqual(authors.filter(alias_length__lte=Length('name')).count(), 1)
def test_length_ordering(self):
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='John Smith', alias='smithj1')
Author.objects.create(name='Rhonda', alias='ronny')
authors = Author.objects.order_by(Length('name'), Length('alias'))
self.assertQuerysetEqual(
authors, [
('Rhonda', 'ronny'),
('John Smith', 'smithj'),
('John Smith', 'smithj1'),
],
lambda a: (a.name, a.alias)
)
def test_substr(self):
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='Rhonda')
authors = Author.objects.annotate(name_part=Substr('name', 5, 3))
self.assertQuerysetEqual(
authors.order_by('name'), [
' Sm',
'da',
],
lambda a: a.name_part
)
authors = Author.objects.annotate(name_part=Substr('name', 2))
self.assertQuerysetEqual(
authors.order_by('name'), [
'ohn Smith',
'honda',
],
lambda a: a.name_part
)
# if alias is null, set to first 5 lower characters of the name
Author.objects.filter(alias__isnull=True).update(
alias=Lower(Substr('name', 1, 5)),
)
self.assertQuerysetEqual(
authors.order_by('name'), [
'smithj',
'rhond',
],
lambda a: a.alias
)
def test_substr_start(self):
Author.objects.create(name='John Smith', alias='smithj')
a = Author.objects.annotate(
name_part_1=Substr('name', 1),
name_part_2=Substr('name', 2),
).get(alias='smithj')
self.assertEqual(a.name_part_1[1:], a.name_part_2)
with self.assertRaisesMessage(ValueError, "'pos' must be greater than 0"):
Author.objects.annotate(raises=Substr('name', 0))
def test_substr_with_expressions(self):
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='Rhonda')
substr = Substr(Upper('name'), StrIndex('name', V('h')), 5, output_field=CharField())
authors = Author.objects.annotate(name_part=substr)
self.assertQuerysetEqual(
authors.order_by('name'), [
'HN SM',
'HONDA',
],
lambda a: a.name_part
)
def test_nested_function_ordering(self): def test_nested_function_ordering(self):
Author.objects.create(name='John Smith') Author.objects.create(name='John Smith')
Author.objects.create(name='Rhonda Simpson', alias='ronny') Author.objects.create(name='Rhonda Simpson', alias='ronny')
@ -578,51 +369,6 @@ class FunctionTests(TestCase):
lambda a: a.title lambda a: a.title
) )
def test_length_transform(self):
try:
CharField.register_lookup(Length)
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='Rhonda')
authors = Author.objects.filter(name__length__gt=7)
self.assertQuerysetEqual(
authors.order_by('name'), [
'John Smith',
],
lambda a: a.name
)
finally:
CharField._unregister_lookup(Length)
def test_lower_transform(self):
try:
CharField.register_lookup(Lower)
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='Rhonda')
authors = Author.objects.filter(name__lower__exact='john smith')
self.assertQuerysetEqual(
authors.order_by('name'), [
'John Smith',
],
lambda a: a.name
)
finally:
CharField._unregister_lookup(Lower)
def test_upper_transform(self):
try:
CharField.register_lookup(Upper)
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='Rhonda')
authors = Author.objects.filter(name__upper__exact='JOHN SMITH')
self.assertQuerysetEqual(
authors.order_by('name'), [
'John Smith',
],
lambda a: a.name
)
finally:
CharField._unregister_lookup(Upper)
def test_func_transform_bilateral(self): def test_func_transform_bilateral(self):
class UpperBilateral(Upper): class UpperBilateral(Upper):
bilateral = True bilateral = True

View File

View File

@ -2,7 +2,7 @@ from django.db.models import IntegerField
from django.db.models.functions import Chr, Left, Ord from django.db.models.functions import Chr, Left, Ord
from django.test import TestCase from django.test import TestCase
from .models import Author from ..models import Author
class ChrTests(TestCase): class ChrTests(TestCase):

View File

@ -0,0 +1,81 @@
from unittest import skipUnless
from django.db import connection
from django.db.models import CharField, TextField, Value as V
from django.db.models.functions import Concat, ConcatPair, Upper
from django.test import TestCase
from django.utils import timezone
from ..models import Article, Author
lorem_ipsum = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua."""
class ConcatTests(TestCase):
def test_basic(self):
Author.objects.create(name='Jayden')
Author.objects.create(name='John Smith', alias='smithj', goes_by='John')
Author.objects.create(name='Margaret', goes_by='Maggie')
Author.objects.create(name='Rhonda', alias='adnohR')
authors = Author.objects.annotate(joined=Concat('alias', 'goes_by'))
self.assertQuerysetEqual(
authors.order_by('name'), [
'',
'smithjJohn',
'Maggie',
'adnohR',
],
lambda a: a.joined
)
def test_gt_two_expressions(self):
with self.assertRaisesMessage(ValueError, 'Concat must take at least two expressions'):
Author.objects.annotate(joined=Concat('alias'))
def test_many(self):
Author.objects.create(name='Jayden')
Author.objects.create(name='John Smith', alias='smithj', goes_by='John')
Author.objects.create(name='Margaret', goes_by='Maggie')
Author.objects.create(name='Rhonda', alias='adnohR')
authors = Author.objects.annotate(
joined=Concat('name', V(' ('), 'goes_by', V(')'), output_field=CharField()),
)
self.assertQuerysetEqual(
authors.order_by('name'), [
'Jayden ()',
'John Smith (John)',
'Margaret (Maggie)',
'Rhonda ()',
],
lambda a: a.joined
)
def test_mixed_char_text(self):
Article.objects.create(title='The Title', text=lorem_ipsum, written=timezone.now())
article = Article.objects.annotate(
title_text=Concat('title', V(' - '), 'text', output_field=TextField()),
).get(title='The Title')
self.assertEqual(article.title + ' - ' + article.text, article.title_text)
# Wrap the concat in something else to ensure that text is returned
# rather than bytes.
article = Article.objects.annotate(
title_text=Upper(Concat('title', V(' - '), 'text', output_field=TextField())),
).get(title='The Title')
expected = article.title + ' - ' + article.text
self.assertEqual(expected.upper(), article.title_text)
@skipUnless(connection.vendor == 'sqlite', "sqlite specific implementation detail.")
def test_coalesce_idempotent(self):
pair = ConcatPair(V('a'), V('b'))
# Check nodes counts
self.assertEqual(len(list(pair.flatten())), 3)
self.assertEqual(len(list(pair.coalesce().flatten())), 7) # + 2 Coalesce + 2 Value()
self.assertEqual(len(list(pair.flatten())), 3)
def test_sql_generation_idempotency(self):
qs = Article.objects.annotate(description=Concat('title', V(': '), 'summary'))
# Multiple compilations should not alter the generated query.
self.assertEqual(str(qs.query), str(qs.all().query))

View File

@ -2,7 +2,7 @@ from django.db.models import CharField, Value
from django.db.models.functions import Left, Lower from django.db.models.functions import Left, Lower
from django.test import TestCase from django.test import TestCase
from .models import Author from ..models import Author
class LeftTests(TestCase): class LeftTests(TestCase):

View File

@ -0,0 +1,48 @@
from django.db.models import CharField
from django.db.models.functions import Length
from django.test import TestCase
from ..models import Author
class LengthTests(TestCase):
def test_basic(self):
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='Rhonda')
authors = Author.objects.annotate(
name_length=Length('name'),
alias_length=Length('alias'),
)
self.assertQuerysetEqual(
authors.order_by('name'), [(10, 6), (6, None)],
lambda a: (a.name_length, a.alias_length)
)
self.assertEqual(authors.filter(alias_length__lte=Length('name')).count(), 1)
def test_ordering(self):
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='John Smith', alias='smithj1')
Author.objects.create(name='Rhonda', alias='ronny')
authors = Author.objects.order_by(Length('name'), Length('alias'))
self.assertQuerysetEqual(
authors, [
('Rhonda', 'ronny'),
('John Smith', 'smithj'),
('John Smith', 'smithj1'),
],
lambda a: (a.name, a.alias)
)
def test_transform(self):
try:
CharField.register_lookup(Length)
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='Rhonda')
authors = Author.objects.filter(name__length__gt=7)
self.assertQuerysetEqual(
authors.order_by('name'), ['John Smith'],
lambda a: a.name
)
finally:
CharField._unregister_lookup(Length)

View File

@ -0,0 +1,42 @@
from django.db.models import CharField
from django.db.models.functions import Lower
from django.test import TestCase
from ..models import Author
class LowerTests(TestCase):
def test_basic(self):
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='Rhonda')
authors = Author.objects.annotate(lower_name=Lower('name'))
self.assertQuerysetEqual(
authors.order_by('name'), ['john smith', 'rhonda'],
lambda a: a.lower_name
)
Author.objects.update(name=Lower('name'))
self.assertQuerysetEqual(
authors.order_by('name'), [
('john smith', 'john smith'),
('rhonda', 'rhonda'),
],
lambda a: (a.lower_name, a.name)
)
def test_num_args(self):
with self.assertRaisesMessage(TypeError, "'Lower' takes exactly 1 argument (2 given)"):
Author.objects.update(name=Lower('name', 'name'))
def test_transform(self):
try:
CharField.register_lookup(Lower)
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='Rhonda')
authors = Author.objects.filter(name__lower__exact='john smith')
self.assertQuerysetEqual(
authors.order_by('name'), ['John Smith'],
lambda a: a.name
)
finally:
CharField._unregister_lookup(Lower)

View File

@ -2,7 +2,7 @@ from django.db.models import CharField, Value
from django.db.models.functions import Left, Ord from django.db.models.functions import Left, Ord
from django.test import TestCase from django.test import TestCase
from .models import Author from ..models import Author
class OrdTests(TestCase): class OrdTests(TestCase):

View File

@ -2,7 +2,7 @@ from django.db.models import CharField, Value
from django.db.models.functions import Length, LPad, RPad from django.db.models.functions import Length, LPad, RPad
from django.test import TestCase from django.test import TestCase
from .models import Author from ..models import Author
class PadTests(TestCase): class PadTests(TestCase):

View File

@ -2,7 +2,7 @@ from django.db.models import CharField, Value
from django.db.models.functions import Length, Repeat from django.db.models.functions import Length, Repeat
from django.test import TestCase from django.test import TestCase
from .models import Author from ..models import Author
class RepeatTests(TestCase): class RepeatTests(TestCase):

View File

@ -2,7 +2,7 @@ from django.db.models import F, Value
from django.db.models.functions import Concat, Replace from django.db.models.functions import Concat, Replace
from django.test import TestCase from django.test import TestCase
from .models import Author from ..models import Author
class ReplaceTests(TestCase): class ReplaceTests(TestCase):

View File

@ -2,7 +2,7 @@ from django.db.models import CharField, Value
from django.db.models.functions import Lower, Right from django.db.models.functions import Lower, Right
from django.test import TestCase from django.test import TestCase
from .models import Author from ..models import Author
class RightTests(TestCase): class RightTests(TestCase):

View File

@ -3,7 +3,7 @@ from django.db.models.functions import StrIndex
from django.test import TestCase from django.test import TestCase
from django.utils import timezone from django.utils import timezone
from .models import Article, Author from ..models import Article, Author
class StrIndexTests(TestCase): class StrIndexTests(TestCase):

View File

@ -0,0 +1,53 @@
from django.db.models import CharField, Value as V
from django.db.models.functions import Lower, StrIndex, Substr, Upper
from django.test import TestCase
from ..models import Author
class SubstrTests(TestCase):
def test_basic(self):
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='Rhonda')
authors = Author.objects.annotate(name_part=Substr('name', 5, 3))
self.assertQuerysetEqual(
authors.order_by('name'), [' Sm', 'da'],
lambda a: a.name_part
)
authors = Author.objects.annotate(name_part=Substr('name', 2))
self.assertQuerysetEqual(
authors.order_by('name'), ['ohn Smith', 'honda'],
lambda a: a.name_part
)
# If alias is null, set to first 5 lower characters of the name.
Author.objects.filter(alias__isnull=True).update(
alias=Lower(Substr('name', 1, 5)),
)
self.assertQuerysetEqual(
authors.order_by('name'), ['smithj', 'rhond'],
lambda a: a.alias
)
def test_start(self):
Author.objects.create(name='John Smith', alias='smithj')
a = Author.objects.annotate(
name_part_1=Substr('name', 1),
name_part_2=Substr('name', 2),
).get(alias='smithj')
self.assertEqual(a.name_part_1[1:], a.name_part_2)
def test_pos_gt_zero(self):
with self.assertRaisesMessage(ValueError, "'pos' must be greater than 0"):
Author.objects.annotate(raises=Substr('name', 0))
def test_expressions(self):
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='Rhonda')
substr = Substr(Upper('name'), StrIndex('name', V('h')), 5, output_field=CharField())
authors = Author.objects.annotate(name_part=substr)
self.assertQuerysetEqual(
authors.order_by('name'), ['HN SM', 'HONDA'],
lambda a: a.name_part
)

View File

@ -2,7 +2,7 @@ from django.db.models import CharField
from django.db.models.functions import LTrim, RTrim, Trim from django.db.models.functions import LTrim, RTrim, Trim
from django.test import TestCase from django.test import TestCase
from .models import Author from ..models import Author
class TrimTests(TestCase): class TrimTests(TestCase):

View File

@ -0,0 +1,43 @@
from django.db.models import CharField
from django.db.models.functions import Upper
from django.test import TestCase
from ..models import Author
class UpperTests(TestCase):
def test_basic(self):
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='Rhonda')
authors = Author.objects.annotate(upper_name=Upper('name'))
self.assertQuerysetEqual(
authors.order_by('name'), [
'JOHN SMITH',
'RHONDA',
],
lambda a: a.upper_name
)
Author.objects.update(name=Upper('name'))
self.assertQuerysetEqual(
authors.order_by('name'), [
('JOHN SMITH', 'JOHN SMITH'),
('RHONDA', 'RHONDA'),
],
lambda a: (a.upper_name, a.name)
)
def test_transform(self):
try:
CharField.register_lookup(Upper)
Author.objects.create(name='John Smith', alias='smithj')
Author.objects.create(name='Rhonda')
authors = Author.objects.filter(name__upper__exact='JOHN SMITH')
self.assertQuerysetEqual(
authors.order_by('name'), [
'John Smith',
],
lambda a: a.name
)
finally:
CharField._unregister_lookup(Upper)