diff options
author | Nick Pope <nick.pope@flightdataservices.com> | 2018-08-16 01:06:19 +0100 |
---|---|---|
committer | Tim Graham <timograham@gmail.com> | 2018-08-16 16:04:39 -0400 |
commit | 5c3db0ef6a9aeaa9ba81262aee67fa918b9545f1 (patch) | |
tree | 7b69f1d2881f1d0dbf176512fb54564d47ccb999 /tests/db_functions/comparison | |
parent | 328898582267d963d79eb871300a7f4d5f5e5959 (diff) | |
download | django-5c3db0ef6a9aeaa9ba81262aee67fa918b9545f1.tar.gz |
Reorganized comparison db function tests.
Diffstat (limited to 'tests/db_functions/comparison')
-rw-r--r-- | tests/db_functions/comparison/__init__.py | 0 | ||||
-rw-r--r-- | tests/db_functions/comparison/test_cast.py | 81 | ||||
-rw-r--r-- | tests/db_functions/comparison/test_coalesce.py | 72 | ||||
-rw-r--r-- | tests/db_functions/comparison/test_greatest.py | 91 | ||||
-rw-r--r-- | tests/db_functions/comparison/test_least.py | 93 |
5 files changed, 337 insertions, 0 deletions
diff --git a/tests/db_functions/comparison/__init__.py b/tests/db_functions/comparison/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/db_functions/comparison/__init__.py diff --git a/tests/db_functions/comparison/test_cast.py b/tests/db_functions/comparison/test_cast.py new file mode 100644 index 0000000000..ffbb357835 --- /dev/null +++ b/tests/db_functions/comparison/test_cast.py @@ -0,0 +1,81 @@ +import datetime +import decimal +import unittest + +from django.db import connection, models +from django.db.models import Avg +from django.db.models.expressions import Value +from django.db.models.functions import Cast +from django.test import ( + TestCase, ignore_warnings, override_settings, skipUnlessDBFeature, +) + +from ..models import Author + + +class CastTests(TestCase): + @classmethod + def setUpTestData(self): + Author.objects.create(name='Bob', age=1, alias='1') + + def test_cast_from_value(self): + numbers = Author.objects.annotate(cast_integer=Cast(Value('0'), models.IntegerField())) + self.assertEqual(numbers.get().cast_integer, 0) + + def test_cast_from_field(self): + numbers = Author.objects.annotate(cast_string=Cast('age', models.CharField(max_length=255)),) + self.assertEqual(numbers.get().cast_string, '1') + + def test_cast_to_char_field_without_max_length(self): + numbers = Author.objects.annotate(cast_string=Cast('age', models.CharField())) + self.assertEqual(numbers.get().cast_string, '1') + + # Silence "Truncated incorrect CHAR(1) value: 'Bob'". + @ignore_warnings(module='django.db.backends.mysql.base') + @skipUnlessDBFeature('supports_cast_with_precision') + def test_cast_to_char_field_with_max_length(self): + names = Author.objects.annotate(cast_string=Cast('name', models.CharField(max_length=1))) + self.assertEqual(names.get().cast_string, 'B') + + def test_cast_to_integer(self): + for field_class in ( + models.AutoField, + models.BigAutoField, + models.IntegerField, + models.BigIntegerField, + models.SmallIntegerField, + models.PositiveIntegerField, + models.PositiveSmallIntegerField, + ): + with self.subTest(field_class=field_class): + numbers = Author.objects.annotate(cast_int=Cast('alias', field_class())) + self.assertEqual(numbers.get().cast_int, 1) + + def test_cast_from_python_to_date(self): + today = datetime.date.today() + dates = Author.objects.annotate(cast_date=Cast(today, models.DateField())) + self.assertEqual(dates.get().cast_date, today) + + def test_cast_from_python_to_datetime(self): + now = datetime.datetime.now() + dates = Author.objects.annotate(cast_datetime=Cast(now, models.DateTimeField())) + self.assertEqual(dates.get().cast_datetime, now) + + def test_cast_from_python(self): + numbers = Author.objects.annotate(cast_float=Cast(decimal.Decimal(0.125), models.FloatField())) + cast_float = numbers.get().cast_float + self.assertIsInstance(cast_float, float) + self.assertEqual(cast_float, 0.125) + + @unittest.skipUnless(connection.vendor == 'postgresql', 'PostgreSQL test') + @override_settings(DEBUG=True) + def test_expression_wrapped_with_parentheses_on_postgresql(self): + """ + The SQL for the Cast expression is wrapped with parentheses in case + it's a complex expression. + """ + list(Author.objects.annotate(cast_float=Cast(Avg('age'), models.FloatField()))) + self.assertIn('(AVG("db_functions_author"."age"))::double precision', connection.queries[-1]['sql']) + + def test_cast_to_text_field(self): + self.assertEqual(Author.objects.values_list(Cast('age', models.TextField()), flat=True).get(), '1') diff --git a/tests/db_functions/comparison/test_coalesce.py b/tests/db_functions/comparison/test_coalesce.py new file mode 100644 index 0000000000..8ba4b01fe6 --- /dev/null +++ b/tests/db_functions/comparison/test_coalesce.py @@ -0,0 +1,72 @@ +from django.db.models import TextField +from django.db.models.functions import Coalesce, Lower +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 CoalesceTests(TestCase): + + def test_basic(self): + Author.objects.create(name='John Smith', alias='smithj') + Author.objects.create(name='Rhonda') + authors = Author.objects.annotate(display_name=Coalesce('alias', 'name')) + self.assertQuerysetEqual( + authors.order_by('name'), ['smithj', 'Rhonda'], + lambda a: a.display_name + ) + + def test_gt_two_expressions(self): + with self.assertRaisesMessage(ValueError, 'Coalesce must take at least two expressions'): + Author.objects.annotate(display_name=Coalesce('alias')) + + def test_mixed_values(self): + a1 = Author.objects.create(name='John Smith', alias='smithj') + a2 = Author.objects.create(name='Rhonda') + ar1 = Article.objects.create( + title='How to Django', + text=lorem_ipsum, + written=timezone.now(), + ) + ar1.authors.add(a1) + ar1.authors.add(a2) + # mixed Text and Char + article = Article.objects.annotate( + headline=Coalesce('summary', 'text', output_field=TextField()), + ) + self.assertQuerysetEqual( + article.order_by('title'), [lorem_ipsum], + lambda a: a.headline + ) + # mixed Text and Char wrapped + article = Article.objects.annotate( + headline=Coalesce(Lower('summary'), Lower('text'), output_field=TextField()), + ) + self.assertQuerysetEqual( + article.order_by('title'), [lorem_ipsum.lower()], + lambda a: a.headline + ) + + def test_ordering(self): + Author.objects.create(name='John Smith', alias='smithj') + Author.objects.create(name='Rhonda') + authors = Author.objects.order_by(Coalesce('alias', 'name')) + self.assertQuerysetEqual( + authors, ['Rhonda', 'John Smith'], + lambda a: a.name + ) + authors = Author.objects.order_by(Coalesce('alias', 'name').asc()) + self.assertQuerysetEqual( + authors, ['Rhonda', 'John Smith'], + lambda a: a.name + ) + authors = Author.objects.order_by(Coalesce('alias', 'name').desc()) + self.assertQuerysetEqual( + authors, ['John Smith', 'Rhonda'], + lambda a: a.name + ) diff --git a/tests/db_functions/comparison/test_greatest.py b/tests/db_functions/comparison/test_greatest.py new file mode 100644 index 0000000000..ef93d808c2 --- /dev/null +++ b/tests/db_functions/comparison/test_greatest.py @@ -0,0 +1,91 @@ +from datetime import datetime, timedelta +from decimal import Decimal +from unittest import skipIf, skipUnless + +from django.db import connection +from django.db.models.expressions import RawSQL +from django.db.models.functions import Coalesce, Greatest +from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature +from django.utils import timezone + +from ..models import Article, Author, DecimalModel, Fan + + +class GreatestTests(TestCase): + + def test_basic(self): + now = timezone.now() + before = now - timedelta(hours=1) + Article.objects.create(title='Testing with Django', written=before, published=now) + articles = Article.objects.annotate(last_updated=Greatest('written', 'published')) + self.assertEqual(articles.first().last_updated, now) + + @skipUnlessDBFeature('greatest_least_ignores_nulls') + def test_ignores_null(self): + now = timezone.now() + Article.objects.create(title='Testing with Django', written=now) + articles = Article.objects.annotate(last_updated=Greatest('written', 'published')) + self.assertEqual(articles.first().last_updated, now) + + @skipIfDBFeature('greatest_least_ignores_nulls') + def test_propagates_null(self): + Article.objects.create(title='Testing with Django', written=timezone.now()) + articles = Article.objects.annotate(last_updated=Greatest('written', 'published')) + self.assertIsNone(articles.first().last_updated) + + @skipIf(connection.vendor == 'mysql', "This doesn't work on MySQL") + def test_coalesce_workaround(self): + past = datetime(1900, 1, 1) + now = timezone.now() + Article.objects.create(title='Testing with Django', written=now) + articles = Article.objects.annotate( + last_updated=Greatest( + Coalesce('written', past), + Coalesce('published', past), + ), + ) + self.assertEqual(articles.first().last_updated, now) + + @skipUnless(connection.vendor == 'mysql', "MySQL-specific workaround") + def test_coalesce_workaround_mysql(self): + past = datetime(1900, 1, 1) + now = timezone.now() + Article.objects.create(title='Testing with Django', written=now) + past_sql = RawSQL("cast(%s as datetime)", (past,)) + articles = Article.objects.annotate( + last_updated=Greatest( + Coalesce('written', past_sql), + Coalesce('published', past_sql), + ), + ) + self.assertEqual(articles.first().last_updated, now) + + def test_all_null(self): + Article.objects.create(title='Testing with Django', written=timezone.now()) + articles = Article.objects.annotate(last_updated=Greatest('published', 'updated')) + self.assertIsNone(articles.first().last_updated) + + def test_one_expressions(self): + with self.assertRaisesMessage(ValueError, 'Greatest must take at least two expressions'): + Greatest('written') + + def test_related_field(self): + author = Author.objects.create(name='John Smith', age=45) + Fan.objects.create(name='Margaret', age=50, author=author) + authors = Author.objects.annotate(highest_age=Greatest('age', 'fans__age')) + self.assertEqual(authors.first().highest_age, 50) + + def test_update(self): + author = Author.objects.create(name='James Smith', goes_by='Jim') + Author.objects.update(alias=Greatest('name', 'goes_by')) + author.refresh_from_db() + self.assertEqual(author.alias, 'Jim') + + def test_decimal_filter(self): + obj = DecimalModel.objects.create(n1=Decimal('1.1'), n2=Decimal('1.2')) + self.assertCountEqual( + DecimalModel.objects.annotate( + greatest=Greatest('n1', 'n2'), + ).filter(greatest=Decimal('1.2')), + [obj], + ) diff --git a/tests/db_functions/comparison/test_least.py b/tests/db_functions/comparison/test_least.py new file mode 100644 index 0000000000..de2c543f0b --- /dev/null +++ b/tests/db_functions/comparison/test_least.py @@ -0,0 +1,93 @@ +from datetime import datetime, timedelta +from decimal import Decimal +from unittest import skipIf, skipUnless + +from django.db import connection +from django.db.models.expressions import RawSQL +from django.db.models.functions import Coalesce, Least +from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature +from django.utils import timezone + +from ..models import Article, Author, DecimalModel, Fan + + +class LeastTests(TestCase): + + def test_basic(self): + now = timezone.now() + before = now - timedelta(hours=1) + Article.objects.create(title='Testing with Django', written=before, published=now) + articles = Article.objects.annotate(first_updated=Least('written', 'published')) + self.assertEqual(articles.first().first_updated, before) + + @skipUnlessDBFeature('greatest_least_ignores_nulls') + def test_ignores_null(self): + now = timezone.now() + Article.objects.create(title='Testing with Django', written=now) + articles = Article.objects.annotate( + first_updated=Least('written', 'published'), + ) + self.assertEqual(articles.first().first_updated, now) + + @skipIfDBFeature('greatest_least_ignores_nulls') + def test_propagates_null(self): + Article.objects.create(title='Testing with Django', written=timezone.now()) + articles = Article.objects.annotate(first_updated=Least('written', 'published')) + self.assertIsNone(articles.first().first_updated) + + @skipIf(connection.vendor == 'mysql', "This doesn't work on MySQL") + def test_coalesce_workaround(self): + future = datetime(2100, 1, 1) + now = timezone.now() + Article.objects.create(title='Testing with Django', written=now) + articles = Article.objects.annotate( + last_updated=Least( + Coalesce('written', future), + Coalesce('published', future), + ), + ) + self.assertEqual(articles.first().last_updated, now) + + @skipUnless(connection.vendor == 'mysql', "MySQL-specific workaround") + def test_coalesce_workaround_mysql(self): + future = datetime(2100, 1, 1) + now = timezone.now() + Article.objects.create(title='Testing with Django', written=now) + future_sql = RawSQL("cast(%s as datetime)", (future,)) + articles = Article.objects.annotate( + last_updated=Least( + Coalesce('written', future_sql), + Coalesce('published', future_sql), + ), + ) + self.assertEqual(articles.first().last_updated, now) + + def test_all_null(self): + Article.objects.create(title='Testing with Django', written=timezone.now()) + articles = Article.objects.annotate(first_updated=Least('published', 'updated')) + self.assertIsNone(articles.first().first_updated) + + def test_one_expressions(self): + with self.assertRaisesMessage(ValueError, 'Least must take at least two expressions'): + Least('written') + + def test_related_field(self): + author = Author.objects.create(name='John Smith', age=45) + Fan.objects.create(name='Margaret', age=50, author=author) + authors = Author.objects.annotate(lowest_age=Least('age', 'fans__age')) + self.assertEqual(authors.first().lowest_age, 45) + + def test_update(self): + author = Author.objects.create(name='James Smith', goes_by='Jim') + Author.objects.update(alias=Least('name', 'goes_by')) + author.refresh_from_db() + self.assertEqual(author.alias, 'James Smith') + + def test_decimal_filter(self): + obj = DecimalModel.objects.create(n1=Decimal('1.1'), n2=Decimal('1.2')) + self.assertCountEqual( + DecimalModel.objects.annotate( + least=Least('n1', 'n2'), + ).filter(least=Decimal('1.1')), + [obj], + ) |