Refs #29548 -- Fixed failing window tests on MariaDB 10.3.
This commit is contained in:
parent
06a11ef6ec
commit
4198445afc
|
@ -258,6 +258,9 @@ class BaseDatabaseFeatures:
|
||||||
# unknown kwargs are passed to QuerySet.explain()?
|
# unknown kwargs are passed to QuerySet.explain()?
|
||||||
validates_explain_options = True
|
validates_explain_options = True
|
||||||
|
|
||||||
|
# Does the backend support the default parameter in lead() and lag()?
|
||||||
|
supports_default_in_lead_lag = True
|
||||||
|
|
||||||
def __init__(self, connection):
|
def __init__(self, connection):
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
|
|
||||||
|
|
|
@ -109,3 +109,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
cursor.execute('SELECT @@LOWER_CASE_TABLE_NAMES')
|
cursor.execute('SELECT @@LOWER_CASE_TABLE_NAMES')
|
||||||
result = cursor.fetchone()
|
result = cursor.fetchone()
|
||||||
return result and result[0] != 0
|
return result and result[0] != 0
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def supports_default_in_lead_lag(self):
|
||||||
|
# To be added in https://jira.mariadb.org/browse/MDEV-12981.
|
||||||
|
return not self.connection.mysql_is_mariadb
|
||||||
|
|
|
@ -1514,6 +1514,11 @@ Calculates the value offset by ``offset``, and if no row exists there, returns
|
||||||
``default`` must have the same type as the ``expression``, however, this is
|
``default`` must have the same type as the ``expression``, however, this is
|
||||||
only validated by the database and not in Python.
|
only validated by the database and not in Python.
|
||||||
|
|
||||||
|
.. admonition:: MariaDB and ``default``
|
||||||
|
|
||||||
|
MariaDB `doesn't support <https://jira.mariadb.org/browse/MDEV-12981>`_
|
||||||
|
the ``default`` parameter.
|
||||||
|
|
||||||
``LastValue``
|
``LastValue``
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
@ -1533,6 +1538,11 @@ Calculates the leading value in a given :ref:`frame <window-frames>`. Both
|
||||||
``default`` must have the same type as the ``expression``, however, this is
|
``default`` must have the same type as the ``expression``, however, this is
|
||||||
only validated by the database and not in Python.
|
only validated by the database and not in Python.
|
||||||
|
|
||||||
|
.. admonition:: MariaDB and ``default``
|
||||||
|
|
||||||
|
MariaDB `doesn't support <https://jira.mariadb.org/browse/MDEV-12981>`_
|
||||||
|
the ``default`` parameter.
|
||||||
|
|
||||||
``NthValue``
|
``NthValue``
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ class Employee(models.Model):
|
||||||
salary = models.PositiveIntegerField()
|
salary = models.PositiveIntegerField()
|
||||||
department = models.CharField(max_length=40, blank=False, null=False)
|
department = models.CharField(max_length=40, blank=False, null=False)
|
||||||
hire_date = models.DateField(blank=False, null=False)
|
hire_date = models.DateField(blank=False, null=False)
|
||||||
|
age = models.IntegerField(blank=False, null=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{}, {}, {}, {}'.format(self.name, self.department, self.salary, self.hire_date)
|
return '{}, {}, {}, {}'.format(self.name, self.department, self.salary, self.hire_date)
|
||||||
|
|
|
@ -16,25 +16,32 @@ from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
|
||||||
from .models import Employee
|
from .models import Employee
|
||||||
|
|
||||||
|
|
||||||
|
def fix_ordering_for_mariadb(qs, ordering):
|
||||||
|
if connection.vendor == 'mysql' and connection.mysql_is_mariadb:
|
||||||
|
# MariaDB requires repeating the ordering when using window functions
|
||||||
|
qs = qs.order_by(*ordering)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
@skipUnlessDBFeature('supports_over_clause')
|
@skipUnlessDBFeature('supports_over_clause')
|
||||||
class WindowFunctionTests(TestCase):
|
class WindowFunctionTests(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
Employee.objects.bulk_create([
|
Employee.objects.bulk_create([
|
||||||
Employee(name=e[0], salary=e[1], department=e[2], hire_date=e[3])
|
Employee(name=e[0], salary=e[1], department=e[2], hire_date=e[3], age=e[4])
|
||||||
for e in [
|
for e in [
|
||||||
('Jones', 45000, 'Accounting', datetime.datetime(2005, 11, 1)),
|
('Jones', 45000, 'Accounting', datetime.datetime(2005, 11, 1), 20),
|
||||||
('Williams', 37000, 'Accounting', datetime.datetime(2009, 6, 1)),
|
('Williams', 37000, 'Accounting', datetime.datetime(2009, 6, 1), 20),
|
||||||
('Jenson', 45000, 'Accounting', datetime.datetime(2008, 4, 1)),
|
('Jenson', 45000, 'Accounting', datetime.datetime(2008, 4, 1), 20),
|
||||||
('Adams', 50000, 'Accounting', datetime.datetime(2013, 7, 1)),
|
('Adams', 50000, 'Accounting', datetime.datetime(2013, 7, 1), 50),
|
||||||
('Smith', 55000, 'Sales', datetime.datetime(2007, 6, 1)),
|
('Smith', 55000, 'Sales', datetime.datetime(2007, 6, 1), 30),
|
||||||
('Brown', 53000, 'Sales', datetime.datetime(2009, 9, 1)),
|
('Brown', 53000, 'Sales', datetime.datetime(2009, 9, 1), 30),
|
||||||
('Johnson', 40000, 'Marketing', datetime.datetime(2012, 3, 1)),
|
('Johnson', 40000, 'Marketing', datetime.datetime(2012, 3, 1), 30),
|
||||||
('Smith', 38000, 'Marketing', datetime.datetime(2009, 10, 1)),
|
('Smith', 38000, 'Marketing', datetime.datetime(2009, 10, 1), 20),
|
||||||
('Wilkinson', 60000, 'IT', datetime.datetime(2011, 3, 1)),
|
('Wilkinson', 60000, 'IT', datetime.datetime(2011, 3, 1), 40),
|
||||||
('Moore', 34000, 'IT', datetime.datetime(2013, 8, 1)),
|
('Moore', 34000, 'IT', datetime.datetime(2013, 8, 1), 40),
|
||||||
('Miller', 100000, 'Management', datetime.datetime(2005, 6, 1)),
|
('Miller', 100000, 'Management', datetime.datetime(2005, 6, 1), 40),
|
||||||
('Johnson', 80000, 'Management', datetime.datetime(2005, 7, 1)),
|
('Johnson', 80000, 'Management', datetime.datetime(2005, 7, 1), 50),
|
||||||
]
|
]
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -187,6 +194,7 @@ class WindowFunctionTests(TestCase):
|
||||||
partition_by=F('department'),
|
partition_by=F('department'),
|
||||||
order_by=[F('salary').asc(), F('name').asc()],
|
order_by=[F('salary').asc(), F('name').asc()],
|
||||||
)).order_by('department')
|
)).order_by('department')
|
||||||
|
qs = fix_ordering_for_mariadb(qs, ('department', F('salary').asc(), F('name').asc()))
|
||||||
self.assertQuerysetEqual(qs, [
|
self.assertQuerysetEqual(qs, [
|
||||||
('Williams', 37000, 'Accounting', None),
|
('Williams', 37000, 'Accounting', None),
|
||||||
('Jenson', 45000, 'Accounting', 37000),
|
('Jenson', 45000, 'Accounting', 37000),
|
||||||
|
@ -250,6 +258,7 @@ class WindowFunctionTests(TestCase):
|
||||||
order_by=[F('hire_date').asc(), F('name').desc()],
|
order_by=[F('hire_date').asc(), F('name').desc()],
|
||||||
partition_by='department',
|
partition_by='department',
|
||||||
)).values_list('name', 'salary', 'department', 'hire_date', 'lead')
|
)).values_list('name', 'salary', 'department', 'hire_date', 'lead')
|
||||||
|
qs = fix_ordering_for_mariadb(qs, ('department', F('hire_date').asc(), F('name').desc()))
|
||||||
self.assertNotIn('GROUP BY', str(qs.query))
|
self.assertNotIn('GROUP BY', str(qs.query))
|
||||||
self.assertSequenceEqual(qs, [
|
self.assertSequenceEqual(qs, [
|
||||||
('Jones', 45000, 'Accounting', datetime.date(2005, 11, 1), 45000),
|
('Jones', 45000, 'Accounting', datetime.date(2005, 11, 1), 45000),
|
||||||
|
@ -373,6 +382,8 @@ class WindowFunctionTests(TestCase):
|
||||||
order_by=[F('hire_date').asc(), F('name').desc()],
|
order_by=[F('hire_date').asc(), F('name').desc()],
|
||||||
partition_by='department',
|
partition_by='department',
|
||||||
)).order_by('department')
|
)).order_by('department')
|
||||||
|
('department', F('hire_date').asc(), F('name').desc())
|
||||||
|
qs = fix_ordering_for_mariadb(qs, ('department', F('hire_date').asc(), F('name').desc()))
|
||||||
self.assertQuerysetEqual(qs, [
|
self.assertQuerysetEqual(qs, [
|
||||||
('Jones', 45000, 'Accounting', datetime.date(2005, 11, 1), 45000),
|
('Jones', 45000, 'Accounting', datetime.date(2005, 11, 1), 45000),
|
||||||
('Jenson', 45000, 'Accounting', datetime.date(2008, 4, 1), 37000),
|
('Jenson', 45000, 'Accounting', datetime.date(2008, 4, 1), 37000),
|
||||||
|
@ -415,6 +426,7 @@ class WindowFunctionTests(TestCase):
|
||||||
ordered=False
|
ordered=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_default_in_lead_lag')
|
||||||
def test_lead_default(self):
|
def test_lead_default(self):
|
||||||
qs = Employee.objects.annotate(lead_default=Window(
|
qs = Employee.objects.annotate(lead_default=Window(
|
||||||
expression=Lead(expression='salary', offset=5, default=60000),
|
expression=Lead(expression='salary', offset=5, default=60000),
|
||||||
|
@ -562,24 +574,24 @@ class WindowFunctionTests(TestCase):
|
||||||
"""A query with RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING."""
|
"""A query with RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING."""
|
||||||
qs = Employee.objects.annotate(sum=Window(
|
qs = Employee.objects.annotate(sum=Window(
|
||||||
expression=Sum('salary'),
|
expression=Sum('salary'),
|
||||||
partition_by='department',
|
partition_by='age',
|
||||||
order_by=[F('hire_date').asc(), F('name').asc()],
|
order_by=[F('age').asc()],
|
||||||
frame=ValueRange(start=None, end=None),
|
frame=ValueRange(start=None, end=None),
|
||||||
)).order_by('department', 'hire_date', 'name')
|
)).order_by('department', 'hire_date', 'name')
|
||||||
self.assertIn('RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING', str(qs.query))
|
self.assertIn('RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING', str(qs.query))
|
||||||
self.assertQuerysetEqual(qs, [
|
self.assertQuerysetEqual(qs, [
|
||||||
('Jones', 'Accounting', 45000, datetime.date(2005, 11, 1), 177000),
|
('Jones', 'Accounting', 45000, datetime.date(2005, 11, 1), 165000),
|
||||||
('Jenson', 'Accounting', 45000, datetime.date(2008, 4, 1), 177000),
|
('Jenson', 'Accounting', 45000, datetime.date(2008, 4, 1), 165000),
|
||||||
('Williams', 'Accounting', 37000, datetime.date(2009, 6, 1), 177000),
|
('Williams', 'Accounting', 37000, datetime.date(2009, 6, 1), 165000),
|
||||||
('Adams', 'Accounting', 50000, datetime.date(2013, 7, 1), 177000),
|
('Adams', 'Accounting', 50000, datetime.date(2013, 7, 1), 130000),
|
||||||
('Wilkinson', 'IT', 60000, datetime.date(2011, 3, 1), 94000),
|
('Wilkinson', 'IT', 60000, datetime.date(2011, 3, 1), 194000),
|
||||||
('Moore', 'IT', 34000, datetime.date(2013, 8, 1), 94000),
|
('Moore', 'IT', 34000, datetime.date(2013, 8, 1), 194000),
|
||||||
('Miller', 'Management', 100000, datetime.date(2005, 6, 1), 180000),
|
('Miller', 'Management', 100000, datetime.date(2005, 6, 1), 194000),
|
||||||
('Johnson', 'Management', 80000, datetime.date(2005, 7, 1), 180000),
|
('Johnson', 'Management', 80000, datetime.date(2005, 7, 1), 130000),
|
||||||
('Smith', 'Marketing', 38000, datetime.date(2009, 10, 1), 78000),
|
('Smith', 'Marketing', 38000, datetime.date(2009, 10, 1), 165000),
|
||||||
('Johnson', 'Marketing', 40000, datetime.date(2012, 3, 1), 78000),
|
('Johnson', 'Marketing', 40000, datetime.date(2012, 3, 1), 148000),
|
||||||
('Smith', 'Sales', 55000, datetime.date(2007, 6, 1), 108000),
|
('Smith', 'Sales', 55000, datetime.date(2007, 6, 1), 148000),
|
||||||
('Brown', 'Sales', 53000, datetime.date(2009, 9, 1), 108000),
|
('Brown', 'Sales', 53000, datetime.date(2009, 9, 1), 148000)
|
||||||
], transform=lambda row: (row.name, row.department, row.salary, row.hire_date, row.sum))
|
], transform=lambda row: (row.name, row.department, row.salary, row.hire_date, row.sum))
|
||||||
|
|
||||||
def test_row_range_rank(self):
|
def test_row_range_rank(self):
|
||||||
|
|
Loading…
Reference in New Issue