diff options
| author | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2023-10-25 17:30:32 +0200 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2023-10-30 12:04:55 +0100 |
| commit | e4d012ca05b23445f422b0400d6dd7aa85743885 (patch) | |
| tree | aae4a65b182cfb1c2b482ce0df918479791708f2 /tests | |
| parent | 34b411762b50883d768d7b67e0a158ec39da8b09 (diff) | |
Refs #29850 -- Added exclusion support to window frames.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/expressions_window/tests.py | 235 |
1 files changed, 234 insertions, 1 deletions
diff --git a/tests/expressions_window/tests.py b/tests/expressions_window/tests.py index f73bd511e5..ee51ace93b 100644 --- a/tests/expressions_window/tests.py +++ b/tests/expressions_window/tests.py @@ -22,6 +22,7 @@ from django.db.models import ( When, Window, WindowFrame, + WindowFrameExclusion, ) from django.db.models.fields.json import KeyTextTransform, KeyTransform from django.db.models.functions import ( @@ -41,7 +42,7 @@ from django.db.models.functions import ( Upper, ) from django.db.models.lookups import Exact -from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature +from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBFeature from django.test.utils import CaptureQueriesContext from .models import Classification, Detail, Employee, PastEmployeeDepartment @@ -1211,6 +1212,47 @@ class WindowFunctionTests(TestCase): ordered=False, ) + @skipUnlessDBFeature( + "supports_frame_exclusion", "supports_frame_range_fixed_distance" + ) + def test_range_exclude_current(self): + qs = Employee.objects.annotate( + sum=Window( + expression=Sum("salary"), + order_by=F("salary").asc(), + partition_by="department", + frame=ValueRange(end=2, exclusion=WindowFrameExclusion.CURRENT_ROW), + ) + ).order_by("department", "salary") + self.assertIn( + "RANGE BETWEEN UNBOUNDED PRECEDING AND 2 FOLLOWING EXCLUDE CURRENT ROW", + str(qs.query), + ) + self.assertQuerySetEqual( + qs, + [ + ("Williams", 37000, "Accounting", datetime.date(2009, 6, 1), None), + ("Jones", 45000, "Accounting", datetime.date(2005, 11, 1), 82000), + ("Jenson", 45000, "Accounting", datetime.date(2008, 4, 1), 82000), + ("Adams", 50000, "Accounting", datetime.date(2013, 7, 1), 127000), + ("Moore", 34000, "IT", datetime.date(2013, 8, 1), None), + ("Wilkinson", 60000, "IT", datetime.date(2011, 3, 1), 34000), + ("Johnson", 80000, "Management", datetime.date(2005, 7, 1), None), + ("Miller", 100000, "Management", datetime.date(2005, 6, 1), 80000), + ("Smith", 38000, "Marketing", datetime.date(2009, 10, 1), None), + ("Johnson", 40000, "Marketing", datetime.date(2012, 3, 1), 38000), + ("Brown", 53000, "Sales", datetime.date(2009, 9, 1), None), + ("Smith", 55000, "Sales", datetime.date(2007, 6, 1), 53000), + ], + transform=lambda row: ( + row.name, + row.salary, + row.department, + row.hire_date, + row.sum, + ), + ) + def test_range_unbound(self): """A query with RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING.""" qs = Employee.objects.annotate( @@ -1289,6 +1331,190 @@ class WindowFunctionTests(TestCase): ), ) + @skipUnlessDBFeature("supports_frame_exclusion") + def test_row_range_rank_exclude_current_row(self): + qs = Employee.objects.annotate( + avg_salary_cohort=Window( + expression=Avg("salary"), + order_by=[F("hire_date").asc(), F("name").desc()], + frame=RowRange( + start=-1, end=1, exclusion=WindowFrameExclusion.CURRENT_ROW + ), + ) + ).order_by("hire_date") + self.assertIn( + "ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW", + str(qs.query), + ) + self.assertQuerySetEqual( + qs, + [ + ("Miller", 100000, "Management", datetime.date(2005, 6, 1), 80000), + ("Johnson", 80000, "Management", datetime.date(2005, 7, 1), 72500), + ("Jones", 45000, "Accounting", datetime.date(2005, 11, 1), 67500), + ("Smith", 55000, "Sales", datetime.date(2007, 6, 1), 45000), + ("Jenson", 45000, "Accounting", datetime.date(2008, 4, 1), 46000), + ("Williams", 37000, "Accounting", datetime.date(2009, 6, 1), 49000), + ("Brown", 53000, "Sales", datetime.date(2009, 9, 1), 37500), + ("Smith", 38000, "Marketing", datetime.date(2009, 10, 1), 56500), + ("Wilkinson", 60000, "IT", datetime.date(2011, 3, 1), 39000), + ("Johnson", 40000, "Marketing", datetime.date(2012, 3, 1), 55000), + ("Adams", 50000, "Accounting", datetime.date(2013, 7, 1), 37000), + ("Moore", 34000, "IT", datetime.date(2013, 8, 1), 50000), + ], + transform=lambda row: ( + row.name, + row.salary, + row.department, + row.hire_date, + row.avg_salary_cohort, + ), + ) + + @skipUnlessDBFeature("supports_frame_exclusion") + def test_row_range_rank_exclude_group(self): + qs = Employee.objects.annotate( + avg_salary_cohort=Window( + expression=Avg("salary"), + order_by=[F("hire_date").asc(), F("name").desc()], + frame=RowRange(start=-1, end=1, exclusion=WindowFrameExclusion.GROUP), + ) + ).order_by("hire_date") + self.assertIn( + "ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP", + str(qs.query), + ) + self.assertQuerySetEqual( + qs, + [ + ("Miller", 100000, "Management", datetime.date(2005, 6, 1), 80000), + ("Johnson", 80000, "Management", datetime.date(2005, 7, 1), 72500), + ("Jones", 45000, "Accounting", datetime.date(2005, 11, 1), 67500), + ("Smith", 55000, "Sales", datetime.date(2007, 6, 1), 45000), + ("Jenson", 45000, "Accounting", datetime.date(2008, 4, 1), 46000), + ("Williams", 37000, "Accounting", datetime.date(2009, 6, 1), 49000), + ("Brown", 53000, "Sales", datetime.date(2009, 9, 1), 37500), + ("Smith", 38000, "Marketing", datetime.date(2009, 10, 1), 56500), + ("Wilkinson", 60000, "IT", datetime.date(2011, 3, 1), 39000), + ("Johnson", 40000, "Marketing", datetime.date(2012, 3, 1), 55000), + ("Adams", 50000, "Accounting", datetime.date(2013, 7, 1), 37000), + ("Moore", 34000, "IT", datetime.date(2013, 8, 1), 50000), + ], + transform=lambda row: ( + row.name, + row.salary, + row.department, + row.hire_date, + row.avg_salary_cohort, + ), + ) + + @skipUnlessDBFeature("supports_frame_exclusion") + def test_row_range_rank_exclude_ties(self): + qs = Employee.objects.annotate( + sum_salary_cohort=Window( + expression=Sum("salary"), + order_by=[F("hire_date").asc(), F("name").desc()], + frame=RowRange(start=-1, end=1, exclusion=WindowFrameExclusion.TIES), + ) + ).order_by("hire_date") + self.assertIn( + "ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES", + str(qs.query), + ) + self.assertQuerySetEqual( + qs, + [ + ("Miller", 100000, "Management", datetime.date(2005, 6, 1), 180000), + ("Johnson", 80000, "Management", datetime.date(2005, 7, 1), 225000), + ("Jones", 45000, "Accounting", datetime.date(2005, 11, 1), 180000), + ("Smith", 55000, "Sales", datetime.date(2007, 6, 1), 145000), + ("Jenson", 45000, "Accounting", datetime.date(2008, 4, 1), 137000), + ("Williams", 37000, "Accounting", datetime.date(2009, 6, 1), 135000), + ("Brown", 53000, "Sales", datetime.date(2009, 9, 1), 128000), + ("Smith", 38000, "Marketing", datetime.date(2009, 10, 1), 151000), + ("Wilkinson", 60000, "IT", datetime.date(2011, 3, 1), 138000), + ("Johnson", 40000, "Marketing", datetime.date(2012, 3, 1), 150000), + ("Adams", 50000, "Accounting", datetime.date(2013, 7, 1), 124000), + ("Moore", 34000, "IT", datetime.date(2013, 8, 1), 84000), + ], + transform=lambda row: ( + row.name, + row.salary, + row.department, + row.hire_date, + row.sum_salary_cohort, + ), + ) + + @skipUnlessDBFeature("supports_frame_exclusion") + def test_row_range_rank_exclude_no_others(self): + qs = Employee.objects.annotate( + sum_salary_cohort=Window( + expression=Sum("salary"), + order_by=[F("hire_date").asc(), F("name").desc()], + frame=RowRange( + start=-1, end=1, exclusion=WindowFrameExclusion.NO_OTHERS + ), + ) + ).order_by("hire_date") + self.assertIn( + "ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE NO OTHERS", + str(qs.query), + ) + self.assertQuerySetEqual( + qs, + [ + ("Miller", 100000, "Management", datetime.date(2005, 6, 1), 180000), + ("Johnson", 80000, "Management", datetime.date(2005, 7, 1), 225000), + ("Jones", 45000, "Accounting", datetime.date(2005, 11, 1), 180000), + ("Smith", 55000, "Sales", datetime.date(2007, 6, 1), 145000), + ("Jenson", 45000, "Accounting", datetime.date(2008, 4, 1), 137000), + ("Williams", 37000, "Accounting", datetime.date(2009, 6, 1), 135000), + ("Brown", 53000, "Sales", datetime.date(2009, 9, 1), 128000), + ("Smith", 38000, "Marketing", datetime.date(2009, 10, 1), 151000), + ("Wilkinson", 60000, "IT", datetime.date(2011, 3, 1), 138000), + ("Johnson", 40000, "Marketing", datetime.date(2012, 3, 1), 150000), + ("Adams", 50000, "Accounting", datetime.date(2013, 7, 1), 124000), + ("Moore", 34000, "IT", datetime.date(2013, 8, 1), 84000), + ], + transform=lambda row: ( + row.name, + row.salary, + row.department, + row.hire_date, + row.sum_salary_cohort, + ), + ) + + @skipIfDBFeature("supports_frame_exclusion") + def test_unsupported_frame_exclusion_raises_error(self): + msg = "This backend does not support window frame exclusions." + with self.assertRaisesMessage(NotSupportedError, msg): + list( + Employee.objects.annotate( + avg_salary_cohort=Window( + expression=Avg("salary"), + order_by=[F("hire_date").asc(), F("name").desc()], + frame=RowRange( + start=-1, end=1, exclusion=WindowFrameExclusion.CURRENT_ROW + ), + ) + ) + ) + + @skipUnlessDBFeature("supports_frame_exclusion") + def test_invalid_frame_exclusion_value_raises_error(self): + msg = "RowRange.exclusion must be a WindowFrameExclusion instance." + with self.assertRaisesMessage(TypeError, msg): + Employee.objects.annotate( + avg_salary_cohort=Window( + expression=Avg("salary"), + order_by=[F("hire_date").asc(), F("name").desc()], + frame=RowRange(start=-1, end=1, exclusion="RUBBISH"), + ) + ) + def test_row_range_rank(self): """ A query with ROWS BETWEEN UNBOUNDED PRECEDING AND 3 FOLLOWING. @@ -1735,6 +1961,13 @@ class NonQueryWindowTests(SimpleTestCase): repr(RowRange(start=1, end=2)), "<RowRange: ROWS BETWEEN 1 FOLLOWING AND 2 FOLLOWING>", ) + self.assertEqual( + repr(RowRange(start=1, end=2, exclusion=WindowFrameExclusion.CURRENT_ROW)), + "<RowRange: ROWS BETWEEN 1 FOLLOWING AND 2 FOLLOWING EXCLUDE CURRENT ROW>", + ) + + def test_window_frame_exclusion_repr(self): + self.assertEqual(repr(WindowFrameExclusion.TIES), "WindowFrameExclusion.TIES") def test_empty_group_by_cols(self): window = Window(expression=Sum("pk")) |
