From: Mike Bayer Date: Fri, 16 Jun 2017 17:30:25 +0000 (-0400) Subject: Handle SHOW VARIABLES returning no row X-Git-Tag: rel_1_2_0b1~19^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5650a0c306391216a9c9ce1961c5b548e534b5eb;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Handle SHOW VARIABLES returning no row 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 --- diff --git a/doc/build/changelog/changelog_11.rst b/doc/build/changelog/changelog_11.rst index 071cf074e2..1ac54c5c41 100644 --- a/doc/build/changelog/changelog_11.rst +++ b/doc/build/changelog/changelog_11.rst @@ -51,6 +51,17 @@ 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 diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py index 277ae58150..c192534782 100644 --- a/lib/sqlalchemy/dialects/mysql/base.py +++ b/lib/sqlalchemy/dialects/mysql/base.py @@ -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 diff --git a/test/dialect/mysql/test_dialect.py b/test/dialect/mysql/test_dialect.py index e6bff35530..cf8641ddcd 100644 --- a/test/dialect/mysql/test_dialect.py +++ b/test/dialect/mysql/test_dialect.py @@ -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'