]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Handle SHOW VARIABLES returning no row
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 16 Jun 2017 17:30:25 +0000 (13:30 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 16 Jun 2017 19:52:46 +0000 (15:52 -0400)
MySQL 5.7 has introduced permission limiting for the "SHOW VARIABLES"
command; the MySQL dialect will now handle when SHOW returns no
row, in particular for the initial fetch of SQL_MODE, and will
emit a warning that user permissions should be modified to allow the
row to be present.

Change-Id: I98e7a69230da397b17eae07b7e9d024fa7aeeb26
Fixes: #4007
doc/build/changelog/changelog_11.rst
lib/sqlalchemy/dialects/mysql/base.py
test/dialect/mysql/test_dialect.py

index 071cf074e2669b9447823ef5eb2a41e76992b0cc..1ac54c5c41eb73f82d912aeedd0dd178d1b87e96 100644 (file)
         features a collation would fail to produce the correct syntax
         within CREATE TABLE.
 
+    .. change:: 4007
+        :tags: bug, mysql
+        :tickets: 4007
+        :versions: 1.2.0b1
+
+        MySQL 5.7 has introduced permission limiting for the "SHOW VARIABLES"
+        command; the MySQL dialect will now handle when SHOW returns no
+        row, in particular for the initial fetch of SQL_MODE, and will
+        emit a warning that user permissions should be modified to allow the
+        row to be present.
+
     .. change:: 3994
         :tags: bug, mssql
         :tickets: 3994
index 277ae58150e3c88004360d2e5a7e8524f6bf0b08..c192534782b4ffb8fed4438134d114c4ea8e1d3b 100644 (file)
@@ -1673,13 +1673,21 @@ class MySQLDialect(default.DefaultDialect):
         """Proxy a result row to smooth over MySQL-Python driver
         inconsistencies."""
 
-        return _DecodingRowProxy(rp.fetchone(), charset)
+        row = rp.fetchone()
+        if row:
+            return _DecodingRowProxy(row, charset)
+        else:
+            return None
 
     def _compat_first(self, rp, charset=None):
         """Proxy a result row to smooth over MySQL-Python driver
         inconsistencies."""
 
-        return _DecodingRowProxy(rp.first(), charset)
+        row = rp.first()
+        if row:
+            return _DecodingRowProxy(row, charset)
+        else:
+            return None
 
     def _extract_error_code(self, exception):
         raise NotImplementedError()
@@ -1720,6 +1728,7 @@ class MySQLDialect(default.DefaultDialect):
 
     def initialize(self, connection):
         self._connection_charset = self._detect_charset(connection)
+        self._detect_sql_mode(connection)
         self._detect_ansiquotes(connection)
         if self._server_ansiquotes:
             # if ansiquotes == True, build a new IdentifierPreparer
@@ -1993,21 +2002,28 @@ class MySQLDialect(default.DefaultDialect):
                 collations[row[0]] = row[1]
         return collations
 
-    def _detect_ansiquotes(self, connection):
-        """Detect and adjust for the ANSI_QUOTES sql mode."""
-
+    def _detect_sql_mode(self, connection):
         row = self._compat_first(
             connection.execute("SHOW VARIABLES LIKE 'sql_mode'"),
             charset=self._connection_charset)
 
         if not row:
-            mode = ''
+            util.warn(
+                "Could not retrieve SQL_MODE; please ensure the "
+                "MySQL user has permissions to SHOW VARIABLES")
+            self._sql_mode = ''
         else:
-            mode = row[1] or ''
-            # 4.0
-            if mode.isdigit():
-                mode_no = int(mode)
-                mode = (mode_no | 4 == mode_no) and 'ANSI_QUOTES' or ''
+            self._sql_mode = row[1] or ''
+
+    def _detect_ansiquotes(self, connection):
+        """Detect and adjust for the ANSI_QUOTES sql mode."""
+
+        mode = self._sql_mode
+        if not mode:
+            mode = ''
+        elif mode.isdigit():
+            mode_no = int(mode)
+            mode = (mode_no | 4 == mode_no) and 'ANSI_QUOTES' or ''
 
         self._server_ansiquotes = 'ANSI_QUOTES' in mode
 
index e6bff35530d35aa8635c70a9ee3c10218254ef13..cf8641ddcd9c49e52b59dfa0c5531a67664a572b 100644 (file)
@@ -3,7 +3,7 @@
 from sqlalchemy.testing import eq_
 from sqlalchemy import *
 from sqlalchemy.engine.url import make_url
-from sqlalchemy.testing import fixtures
+from sqlalchemy.testing import fixtures, expect_warnings
 from sqlalchemy import testing
 from sqlalchemy.testing import engines
 from ...engine import test_execute
@@ -102,6 +102,24 @@ class DialectTest(fixtures.TestBase):
             conn = eng.connect()
             eq_(conn.dialect._connection_charset, enc)
 
+    def test_no_show_variables(self):
+        from sqlalchemy.testing import mock
+        engine = engines.testing_engine()
+
+        def my_execute(self, statement, *args, **kw):
+            if statement.startswith("SHOW VARIABLES"):
+                statement = "SELECT 1 FROM DUAL WHERE 1=0"
+            return real_exec(self, statement, *args, **kw)
+
+        real_exec = engine._connection_cls._execute_text
+        with mock.patch.object(
+                engine._connection_cls, "_execute_text", my_execute):
+            with expect_warnings(
+                "Could not retrieve SQL_MODE; please ensure the "
+                "MySQL user has permissions to SHOW VARIABLES"
+            ):
+                engine.connect()
+
     def test_autocommit_isolation_level(self):
         c = testing.db.connect().execution_options(
             isolation_level='AUTOCOMMIT'