From 5da9d135b98efa572199e920b808d90abbe5dece Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 19 Jun 2024 11:03:25 -0400 Subject: [PATCH] use literal execute for SQL Server frame parameters Fixed issue where SQL Server drivers don't support bound parameters when rendering the "frame specification" for a window function, e.g. "ROWS BETWEEN", etc. Fixes: #11514 Change-Id: I0664f4076a2a8266434a4670949b8b44cd261f44 (cherry picked from commit c088b6426f1d73efe7de3e42b3e86f8027076bc3) (cherry picked from commit 9524e4bffc9c8545fdb8698ef029c420374ac00f) --- doc/build/changelog/unreleased_14/11514.rst | 8 ++++ lib/sqlalchemy/dialects/mssql/base.py | 4 ++ lib/sqlalchemy/testing/suite/test_select.py | 51 +++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 doc/build/changelog/unreleased_14/11514.rst diff --git a/doc/build/changelog/unreleased_14/11514.rst b/doc/build/changelog/unreleased_14/11514.rst new file mode 100644 index 0000000000..81f0ddeddc --- /dev/null +++ b/doc/build/changelog/unreleased_14/11514.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: bug, mssql + :tickets: 11514 + + Fixed issue where SQL Server drivers don't support bound parameters when + rendering the "frame specification" for a window function, e.g. "ROWS + BETWEEN", etc. + diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py index 326d9f54fc..efaec75c54 100644 --- a/lib/sqlalchemy/dialects/mssql/base.py +++ b/lib/sqlalchemy/dialects/mssql/base.py @@ -1847,6 +1847,10 @@ class MSSQLCompiler(compiler.SQLCompiler): self.tablealiases = {} super(MSSQLCompiler, self).__init__(*args, **kwargs) + def _format_frame_clause(self, range_, **kw): + kw["literal_execute"] = True + return super()._format_frame_clause(range_, **kw) + def _with_legacy_schema_aliasing(fn): def decorate(self, *arg, **kw): if self.dialect.legacy_schema_aliasing: diff --git a/lib/sqlalchemy/testing/suite/test_select.py b/lib/sqlalchemy/testing/suite/test_select.py index 42369a4e0f..eca2203d58 100644 --- a/lib/sqlalchemy/testing/suite/test_select.py +++ b/lib/sqlalchemy/testing/suite/test_select.py @@ -1787,3 +1787,54 @@ class IsOrIsNotDistinctFromTest(fixtures.TablesTest): len(result), expected_row_count_for_is_not, ) + + +class WindowFunctionTest(fixtures.TablesTest): + __requires__ = ("window_functions",) + + __backend__ = True + + @classmethod + def define_tables(cls, metadata): + Table( + "some_table", + metadata, + Column("id", Integer, primary_key=True), + Column("col1", Integer), + Column("col2", Integer), + ) + + @classmethod + def insert_data(cls, connection): + connection.execute( + cls.tables.some_table.insert(), + [{"id": i, "col1": i, "col2": i * 5} for i in range(1, 50)], + ) + + def test_window(self, connection): + some_table = self.tables.some_table + rows = connection.execute( + select( + func.max(some_table.c.col2).over( + order_by=[some_table.c.col1.desc()] + ) + ).where(some_table.c.col1 < 20) + ).all() + + eq_(rows, [(95,) for i in range(19)]) + + def test_window_rows_between(self, connection): + some_table = self.tables.some_table + + # note the rows are part of the cache key right now, not handled + # as binds. this is issue #11515 + rows = connection.execute( + select( + func.max(some_table.c.col2).over( + order_by=[some_table.c.col1], + rows=(-5, 0), + ) + ) + ).all() + + eq_(rows, [(i,) for i in range(5, 250, 5)]) -- 2.47.2