From 227d0c7365cfd0a64d021cb9bdcf77bed2d3f170 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 19 Jan 2020 18:30:54 -0500 Subject: [PATCH] Fixed #31183 -- Added a feature flag for " only supports UNBOUNDED together with PRECEDING and FOLLOWING". --- django/db/backends/base/features.py | 1 + django/db/backends/base/operations.py | 11 ++++++++++- django/db/backends/postgresql/features.py | 1 + django/db/backends/postgresql/operations.py | 10 ---------- tests/expressions_window/tests.py | 14 +++++++------- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py index 14092e793e..45c62f463b 100644 --- a/django/db/backends/base/features.py +++ b/django/db/backends/base/features.py @@ -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 diff --git a/django/db/backends/base/operations.py b/django/db/backends/base/operations.py index 43220a18bc..6e1187fd37 100644 --- a/django/db/backends/base/operations.py +++ b/django/db/backends/base/operations.py @@ -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: diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py index fad87081b0..67c04d89dc 100644 --- a/django/db/backends/postgresql/features.py +++ b/django/db/backends/postgresql/features.py @@ -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. diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py index ee7787c560..e8355a0e58 100644 --- a/django/db/backends/postgresql/operations.py +++ b/django/db/backends/postgresql/operations.py @@ -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 = {} diff --git a/tests/expressions_window/tests.py b/tests/expressions_window/tests.py index 4df58bf27d..37b0e95247 100644 --- a/tests/expressions_window/tests.py +++ b/tests/expressions_window/tests.py @@ -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'),