Fixed #31183 -- Added a feature flag for "<db> only supports UNBOUNDED together with PRECEDING and FOLLOWING".

This commit is contained in:
Tim Graham 2020-01-19 18:30:54 -05:00 committed by Mariusz Felisiak
parent 2a2ea4ee18
commit 227d0c7365
5 changed files with 19 additions and 18 deletions

View File

@ -246,6 +246,7 @@ class BaseDatabaseFeatures:
# Does the backend support window expressions (expression OVER (...))?
supports_over_clause = False
supports_frame_range_fixed_distance = False
only_supports_unbounded_with_preceding_and_following = False
# Does the backend support CAST with precision?
supports_cast_with_precision = True

View File

@ -658,7 +658,16 @@ class BaseDatabaseOperations:
return self.window_frame_start(start), self.window_frame_end(end)
def window_frame_range_start_end(self, start=None, end=None):
return self.window_frame_rows_start_end(start, end)
start_, end_ = self.window_frame_rows_start_end(start, end)
if (
self.connection.features.only_supports_unbounded_with_preceding_and_following and
((start and start < 0) or (end and end > 0))
):
raise NotSupportedError(
'%s only supports UNBOUNDED together with PRECEDING and '
'FOLLOWING.' % self.connection.display_name
)
return start_, end_
def explain_query_prefix(self, format=None, **options):
if not self.connection.features.supports_explaining_query_execution:

View File

@ -52,6 +52,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
$$ LANGUAGE plpgsql;"""
requires_casted_case_in_updates = True
supports_over_clause = True
only_supports_unbounded_with_preceding_and_following = True
supports_aggregate_filter_clause = True
supported_explain_formats = {'JSON', 'TEXT', 'XML', 'YAML'}
validates_explain_options = False # A query will error on invalid options.

View File

@ -1,7 +1,6 @@
from psycopg2.extras import Inet
from django.conf import settings
from django.db import NotSupportedError
from django.db.backends.base.operations import BaseDatabaseOperations
@ -275,15 +274,6 @@ class DatabaseOperations(BaseDatabaseOperations):
return "(interval '1 day' * (%s - %s))" % (lhs_sql, rhs_sql), params
return super().subtract_temporals(internal_type, lhs, rhs)
def window_frame_range_start_end(self, start=None, end=None):
start_, end_ = super().window_frame_range_start_end(start, end)
if (start and start < 0) or (end and end > 0):
raise NotSupportedError(
'PostgreSQL only supports UNBOUNDED together with PRECEDING '
'and FOLLOWING.'
)
return start_, end_
def explain_query_prefix(self, format=None, **options):
prefix = super().explain_query_prefix(format)
extra = {}

View File

@ -1,5 +1,5 @@
import datetime
from unittest import mock, skipIf, skipUnless
from unittest import mock, skipIf
from django.core.exceptions import FieldError
from django.db import NotSupportedError, connection
@ -750,9 +750,9 @@ class WindowFunctionTests(TestCase):
frame=RowRange(end='a'),
)))
@skipUnless(connection.vendor == 'postgresql', 'Frame construction not allowed on PostgreSQL')
def test_postgresql_illegal_range_frame_start(self):
msg = 'PostgreSQL only supports UNBOUNDED together with PRECEDING and FOLLOWING.'
@skipUnlessDBFeature('only_supports_unbounded_with_preceding_and_following')
def test_unsupported_range_frame_start(self):
msg = '%s only supports UNBOUNDED together with PRECEDING and FOLLOWING.' % connection.display_name
with self.assertRaisesMessage(NotSupportedError, msg):
list(Employee.objects.annotate(test=Window(
expression=Sum('salary'),
@ -760,9 +760,9 @@ class WindowFunctionTests(TestCase):
frame=ValueRange(start=-1),
)))
@skipUnless(connection.vendor == 'postgresql', 'Frame construction not allowed on PostgreSQL')
def test_postgresql_illegal_range_frame_end(self):
msg = 'PostgreSQL only supports UNBOUNDED together with PRECEDING and FOLLOWING.'
@skipUnlessDBFeature('only_supports_unbounded_with_preceding_and_following')
def test_unsupported_range_frame_end(self):
msg = '%s only supports UNBOUNDED together with PRECEDING and FOLLOWING.' % connection.display_name
with self.assertRaisesMessage(NotSupportedError, msg):
list(Employee.objects.annotate(test=Window(
expression=Sum('salary'),