]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Deprecate ``DISTINCT ON`` when not targeting PostgreSQL
authorFederico Caselli <cfederico87@gmail.com>
Thu, 16 Apr 2020 21:16:32 +0000 (23:16 +0200)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 20 Apr 2020 15:28:51 +0000 (11:28 -0400)
Deprecate usage of ``DISTINCT ON`` in dialect other than PostgreSQL.
Previously this was silently ignored.
Deprecate old usage of string distinct in MySQL dialect

Fixes: #4002
Change-Id: I38fc64aef75e77748083c11d388ec831f161c9c9

14 files changed:
doc/build/changelog/unreleased_14/4002.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/firebird/base.py
lib/sqlalchemy/dialects/mssql/base.py
lib/sqlalchemy/dialects/mysql/base.py
lib/sqlalchemy/dialects/postgresql/base.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/selectable.py
lib/sqlalchemy/testing/assertions.py
lib/sqlalchemy/testing/requirements.py
lib/sqlalchemy/testing/suite/test_select.py
test/dialect/mysql/test_deprecations.py
test/requirements.py
test/sql/test_compiler.py

diff --git a/doc/build/changelog/unreleased_14/4002.rst b/doc/build/changelog/unreleased_14/4002.rst
new file mode 100644 (file)
index 0000000..53dc0cf
--- /dev/null
@@ -0,0 +1,6 @@
+.. change::
+    :tags: bug, sql
+    :tickets: 4002
+
+    Deprecate usage of ``DISTINCT ON`` in dialect other than PostgreSQL.
+    Deprecate old usage of string distinct in MySQL dialect
index a43413780a744a01c955aeacf683ffbc2fe045ac..5779ac8858162b975d6eeaff08eeafe8d42f18f1 100644 (file)
@@ -525,8 +525,7 @@ class FBCompiler(sql.compiler.SQLCompiler):
             result += "FIRST %s " % self.process(select._limit_clause, **kw)
         if select._offset_clause is not None:
             result += "SKIP %s " % self.process(select._offset_clause, **kw)
-        if select._distinct:
-            result += "DISTINCT "
+        result += super(FBCompiler, self).get_select_precolumns(select, **kw)
         return result
 
     def limit_clause(self, select, **kw):
index df6196baefb9a4d08442823302541e37fd07e029..0dd2ac11b5287b71d6a8b80a5b6c2e708695bfee 100644 (file)
@@ -1636,9 +1636,7 @@ class MSSQLCompiler(compiler.SQLCompiler):
     def get_select_precolumns(self, select, **kw):
         """ MS-SQL puts TOP, it's version of LIMIT here """
 
-        s = ""
-        if select._distinct:
-            s += "DISTINCT "
+        s = super(MSSQLCompiler, self).get_select_precolumns(select, **kw)
 
         if select._simple_int_limit and (
             select._offset_clause is None
@@ -1649,12 +1647,8 @@ class MSSQLCompiler(compiler.SQLCompiler):
             # so have to use literal here.
             kw["literal_execute"] = True
             s += "TOP %s " % self.process(select._limit_clause, **kw)
-        if s:
-            return s
-        else:
-            return compiler.SQLCompiler.get_select_precolumns(
-                self, select, **kw
-            )
+
+        return s
 
     def get_from_hint_text(self, table, text):
         return text
index 53c91630499a0db5049dfca051faf9e1e474468d..38f3fa6111aa138079d0ed7e0d03129d7ce72a09 100644 (file)
@@ -1450,19 +1450,22 @@ class MySQLCompiler(compiler.SQLCompiler):
     def get_select_precolumns(self, select, **kw):
         """Add special MySQL keywords in place of DISTINCT.
 
-        .. note::
-
-          this usage is deprecated.  :meth:`_expression.Select.prefix_with`
-          should be used for special keywords at the start
-          of a SELECT.
+        .. deprecated 1.4:: this usage is deprecated.
+           :meth:`_expression.Select.prefix_with` should be used for special
+           keywords at the start of a SELECT.
 
         """
         if isinstance(select._distinct, util.string_types):
+            util.warn_deprecated(
+                "Sending string values for 'distinct' is deprecated in the "
+                "MySQL dialect and will be removed in a future release.  "
+                "Please use :meth:`.Select.prefix_with` for special keywords "
+                "at the start of a SELECT statement",
+                version="1.4",
+            )
             return select._distinct.upper() + " "
-        elif select._distinct:
-            return "DISTINCT "
-        else:
-            return ""
+
+        return super(MySQLCompiler, self).get_select_precolumns(select, **kw)
 
     def visit_join(self, join, asfrom=False, from_linter=None, **kwargs):
         if from_linter:
index 20540ac0200c78c71b308f103d735443d4351cc1..ca2f6a8a43357b42c4d20e8fb252421b4194591a 100644 (file)
@@ -1693,6 +1693,8 @@ class PGCompiler(compiler.SQLCompiler):
         return "ONLY " + sqltext
 
     def get_select_precolumns(self, select, **kw):
+        # Do not call super().get_select_precolumns because
+        # it will warn/raise when distinct on is present
         if select._distinct or select._distinct_on:
             if select._distinct_on:
                 return (
index 4d7eee9ba2067fd0c45595139593f7f13510795e..ab49a4dccc0ac2aafe893831684dfa26d91d8454 100644 (file)
@@ -3156,6 +3156,9 @@ class Query(Generative):
          the PostgreSQL dialect will render a ``DISTINCT ON (<expressions>)``
          construct.
 
+         .. deprecated:: 1.4 Using \*expr in other dialects is deprecated
+            and will raise :class:`_exc.CompileError` in a future version.
+
         """
         if not expr:
             self._distinct = True
index bc16b142964816482998e85dcc82885fb8489c60..23eff773c46f2ff0f8439ce4aac8d0008d9f8496 100644 (file)
@@ -2868,7 +2868,16 @@ class SQLCompiler(Compiled):
         before column list.
 
         """
-        return select._distinct and "DISTINCT " or ""
+        if select._distinct_on:
+            util.warn_deprecated(
+                "DISTINCT ON is currently supported only by the PostgreSQL "
+                "dialect.  Use of DISTINCT ON for other backends is currently "
+                "silently ignored, however this usage is deprecated, and will "
+                "raise CompileError in a future release for all backends "
+                "that do not support this syntax.",
+                version="1.4",
+            )
+        return "DISTINCT " if select._distinct else ""
 
     def group_by_clause(self, select, **kw):
         """allow dialects to customize how GROUP BY is rendered."""
index 89ee7e28ff52728265a49e12997fdb153eaa5004..c8df637baf6105b92f048dc98767365d1f8d23ea 100644 (file)
@@ -4138,6 +4138,9 @@ class Select(
          the PostgreSQL dialect will render a ``DISTINCT ON (<expressions>>)``
          construct.
 
+         .. deprecated:: 1.4 Using \*expr in other dialects is deprecated
+            and will raise :class:`_exc.CompileError` in a future version.
+
         """
         if expr:
             self._distinct = True
index 61d272160aa87ddc408ac184b5238f5be2b3849b..05dcf230b243acac0b51ce5b90f0061bc2a6f24b 100644 (file)
@@ -412,7 +412,6 @@ class AssertsCompiledSQL(object):
         c = clause.compile(dialect=dialect, **kw)
 
         param_str = repr(getattr(c, "params", {}))
-
         if util.py3k:
             param_str = param_str.encode("utf-8").decode("ascii", "ignore")
             print(
index 644483b79dedbd7f3c1f05769ae536a48a6a1950..31011a9708c5d785532bd82f61f9043d8860065f 100644 (file)
@@ -1169,6 +1169,11 @@ class SuiteRequirements(Requirements):
         computed columns"""
         return exclusions.closed()
 
+    @property
+    def supports_distinct_on(self):
+        """If a backend supports the DISTINCT ON in a select"""
+        return exclusions.closed()
+
     @property
     def supports_is_distinct_from(self):
         """Supports some form of "x IS [NOT] DISTINCT FROM y" construct.
index c1f77a7c18cfc05842a8dc688269b2eddd6494e8..ad17ebb4ac77c577375ddc36b7908c1d14c8476d 100644 (file)
@@ -12,6 +12,7 @@ from ..schema import Column
 from ..schema import Table
 from ... import bindparam
 from ... import case
+from ... import column
 from ... import Computed
 from ... import false
 from ... import func
@@ -20,6 +21,7 @@ from ... import literal_column
 from ... import null
 from ... import select
 from ... import String
+from ... import table
 from ... import testing
 from ... import text
 from ... import true
@@ -1016,6 +1018,19 @@ class ComputedColumnTest(fixtures.TablesTest):
             eq_(res, [(100, 40), (1764, 168)])
 
 
+class DistinctOnTest(AssertsCompiledSQL, fixtures.TablesTest):
+    __backend__ = True
+    __requires__ = ("standard_cursor_sql",)
+
+    @testing.fails_if(testing.requires.supports_distinct_on)
+    def test_distinct_on(self):
+        stm = select(["*"]).distinct(column("q")).select_from(table("foo"))
+        with testing.expect_deprecated(
+            "DISTINCT ON is currently supported only by the PostgreSQL "
+        ):
+            self.assert_compile(stm, "SELECT DISTINCT * FROM foo")
+
+
 class IsOrIsNotDistinctFromTest(fixtures.TablesTest):
     __backend__ = True
     __requires__ = ("supports_is_distinct_from",)
index 544284a219198da3023a033cb126fb856f1d322d..b2bd99d828bb1453242220c7638ee30990fb3755 100644 (file)
@@ -1,9 +1,29 @@
+from sqlalchemy import select
+from sqlalchemy import table
+from sqlalchemy.dialects.mysql import base as mysql
 from sqlalchemy.dialects.mysql import ENUM
 from sqlalchemy.dialects.mysql import SET
+from sqlalchemy.testing import AssertsCompiledSQL
+from sqlalchemy.testing import expect_deprecated
 from sqlalchemy.testing import expect_deprecated_20
 from sqlalchemy.testing import fixtures
 
 
+class CompileTest(AssertsCompiledSQL, fixtures.TestBase):
+
+    __dialect__ = mysql.dialect()
+
+    def test_distinct_string(self):
+        s = select(["*"]).select_from(table("foo"))
+        s._distinct = "foo"
+
+        with expect_deprecated(
+            "Sending string values for 'distinct' is deprecated in the MySQL "
+            "dialect and will be removed in a future release"
+        ):
+            self.assert_compile(s, "SELECT FOO * FROM foo")
+
+
 class DeprecateQuoting(fixtures.TestBase):
     def test_enum_warning(self):
         ENUM("a", "b")
index aac376dba19343982196e020e45590ecb7882326..cf9168f5a23d184f827426f72d112e1cc2603e39 100644 (file)
@@ -1611,3 +1611,8 @@ class DefaultRequirements(SuiteRequirements):
     @property
     def computed_columns_reflect_persisted(self):
         return self.computed_columns + skip_if("oracle")
+
+    @property
+    def supports_distinct_on(self):
+        """If a backend supports the DISTINCT ON in a select"""
+        return only_if(["postgresql"])
index e44deed90d5fa47570420cd189b71131e33d149f..440eaa05a70ac381d9c8ab05a82ceda3a41a07b6 100644 (file)
@@ -1462,6 +1462,13 @@ class SelectTest(fixtures.TestBase, AssertsCompiledSQL):
             "SELECT count(DISTINCT mytable.myid) AS count_1 FROM mytable",
         )
 
+    def test_distinct_on(self):
+        with testing.expect_deprecated(
+            "DISTINCT ON is currently supported only by the PostgreSQL "
+            "dialect"
+        ):
+            select(["*"]).distinct(table1.c.myid).compile()
+
     def test_where_empty(self):
         self.assert_compile(
             select([table1.c.myid]).where(