]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Repair Oracle compat version check; dont warn if failed
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 9 Oct 2019 20:05:34 +0000 (16:05 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 9 Oct 2019 21:17:54 +0000 (17:17 -0400)
Fixed regression in Oracle dialect that was inadvertently using max
identifier length of 128 characters on Oracle server 12.2 and greater even
though the stated contract for the remainder of the 1.3 series is  that
this value stays at 30 until version SQLAlchemy 1.4.  Also repaired issues
with the retrieval of the "compatibility" version, and removed the warning
emitted when the "v$parameter" view was not accessible as this was  causing
user confusion.

Fixes: #4898
Change-Id: Ieb7b3e093610896c5aa12d0789b63262e0ecf9d8

doc/build/changelog/unreleased_13/4898.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/oracle/base.py
test/dialect/oracle/test_dialect.py

diff --git a/doc/build/changelog/unreleased_13/4898.rst b/doc/build/changelog/unreleased_13/4898.rst
new file mode 100644 (file)
index 0000000..eb6cdcd
--- /dev/null
@@ -0,0 +1,11 @@
+.. change::
+    :tags: bug, oracle
+    :tickets: 4898, 4857
+
+    Fixed regression in Oracle dialect that was inadvertently using max
+    identifier length of 128 characters on Oracle server 12.2 and greater even
+    though the stated contract for the remainder of the 1.3 series is  that
+    this value stays at 30 until version SQLAlchemy 1.4.  Also repaired issues
+    with the retrieval of the "compatibility" version, and removed the warning
+    emitted when the "v$parameter" view was not accessible as this was  causing
+    user confusion.
index 5c781e3a6f8cdfef63af94ad2f7f58f5ac2bbb94..2353f15e6860fd8b45bda7e654169d38a82678eb 100644 (file)
@@ -84,10 +84,12 @@ To assist with this change and others, Oracle includes the concept of a
 actual server version in order to assist with migration of Oracle databases,
 and may be configured within the Oracle server itself. This compatibility
 version is retrieved using the query  ``SELECT value FROM v$parameter WHERE
-name = 'compatible';``.   The SQLAlchemy Oracle dialect as of version 1.3.9
-will use this query upon first connect in order to determine the effective
-compatibility version of the server, which determines what the maximum allowed
-identifier length is for the server.
+name = 'compatible';``.   The SQLAlchemy Oracle dialect, when tasked with
+determining the default max identifier length, will attempt to use this query
+upon first connect in order to determine the effective compatibility version of
+the server, which determines what the maximum allowed identifier length is for
+the server.  If the table is not available, the  server version information is
+used instead.
 
 For the duration of the SQLAlchemy 1.3 series, the default max identifier
 length will remain at 30, even if compatibility version 12.2 or greater is in
@@ -1191,11 +1193,6 @@ class OracleDialect(default.DefaultDialect):
     supports_unicode_binds = False
     max_identifier_length = 30
 
-    # this should be set to
-    # "SELECT value FROM v$parameter WHERE name = 'compatible'"
-    # upon connect.
-    _compat_server_version_info = None
-
     supports_simple_order_by_label = False
     cte_follows_insert = True
 
@@ -1249,12 +1246,6 @@ class OracleDialect(default.DefaultDialect):
     def initialize(self, connection):
         super(OracleDialect, self).initialize(connection)
 
-        _compat_server_version_info = self._get_compat_server_version_info(
-            connection
-        )
-        if _compat_server_version_info is not None:
-            self._compat_server_version_info = _compat_server_version_info
-
         self.implicit_returning = self.__dict__.get(
             "implicit_returning", self.server_version_info > (10,)
         )
@@ -1264,18 +1255,24 @@ class OracleDialect(default.DefaultDialect):
             self.colspecs.pop(sqltypes.Interval)
             self.use_ansi = False
 
-    def _get_compat_server_version_info(self, connection):
+    def _get_effective_compat_server_version_info(self, connection):
+        # dialect does not need compat levels below 12.2, so don't query
+        # in those cases
+
+        if self.server_version_info < (12, 2):
+            return self.server_version_info
         try:
-            return connection.execute(
+            compat = connection.execute(
                 "SELECT value FROM v$parameter WHERE name = 'compatible'"
             ).scalar()
-        except exc.DBAPIError as err:
-            util.warn("Could not determine compatibility version: %s" % err)
-
-    @property
-    def _effective_compat_server_version_info(self):
-        if self._compat_server_version_info is not None:
-            return self._compat_server_version_info
+        except exc.DBAPIError:
+            compat = None
+
+        if compat:
+            try:
+                return tuple(int(x) for x in compat.split("."))
+            except:
+                return self.server_version_info
         else:
             return self.server_version_info
 
@@ -1300,9 +1297,12 @@ class OracleDialect(default.DefaultDialect):
         pass
 
     def _check_max_identifier_length(self, connection):
-        if self._effective_compat_server_version_info >= (12, 2):
+        if self._get_effective_compat_server_version_info(connection) >= (
+            12,
+            2,
+        ):
             util.warn(
-                "Oracle compatibility version %r is known to have a maximum "
+                "Oracle version %r is known to have a maximum "
                 "identifier length of 128, rather than the historical default "
                 "of 30. SQLAlchemy 1.4 will use 128 for this "
                 "database; please set max_identifier_length=128 "
@@ -1315,10 +1315,9 @@ class OracleDialect(default.DefaultDialect):
                 "SQLAlchemy Oracle dialect documentation for background."
                 % ((self.server_version_info,))
             )
-            return 128
-        else:
-            # use the default
-            return None
+
+        # use the default
+        return None
 
     def _check_unicode_returns(self, connection):
         additional_tests = [
index f11bab1aefcb63ba73ad7732f1ec7a8ebcee972f..b549935b93258883f353dc93a022cadcf44b6d02 100644 (file)
@@ -1,5 +1,6 @@
 # coding: utf-8
 
+import re
 
 from sqlalchemy import bindparam
 from sqlalchemy import create_engine
@@ -221,6 +222,121 @@ class CompatFlagsTest(fixtures.TestBase, AssertsCompiledSQL):
         self.assert_compile(Unicode(50), "NVARCHAR2(50)", dialect=dialect)
         self.assert_compile(UnicodeText(), "NCLOB", dialect=dialect)
 
+    def _expect_max_ident_warning(self):
+        return testing.expect_warnings(
+            "Oracle version .* is known to have a maximum "
+            "identifier length of 128"
+        )
+
+    def test_ident_length_in_13_is_30(self):
+        from sqlalchemy import __version__
+
+        m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", __version__)
+        version = tuple(int(x) for x in m.group(1, 2, 3) if x is not None)
+        if version >= (1, 4):
+            length = 128
+        else:
+            length = 30
+
+        eq_(oracle.OracleDialect.max_identifier_length, length)
+
+        dialect = self._dialect((12, 2, 0))
+        conn = mock.Mock(
+            execute=mock.Mock(return_value=mock.Mock(scalar=lambda: "12.2.0"))
+        )
+        with self._expect_max_ident_warning():
+            dialect.initialize(conn)
+        eq_(dialect.server_version_info, (12, 2, 0))
+        eq_(
+            dialect._get_effective_compat_server_version_info(conn), (12, 2, 0)
+        )
+        eq_(dialect.max_identifier_length, length)
+
+    def test_max_ident_122(self):
+        dialect = self._dialect((12, 2, 0))
+
+        conn = mock.Mock(
+            execute=mock.Mock(return_value=mock.Mock(scalar=lambda: "12.2.0"))
+        )
+        with self._expect_max_ident_warning():
+            dialect.initialize(conn)
+        eq_(dialect.server_version_info, (12, 2, 0))
+        eq_(
+            dialect._get_effective_compat_server_version_info(conn), (12, 2, 0)
+        )
+        eq_(
+            dialect.max_identifier_length,
+            oracle.OracleDialect.max_identifier_length,
+        )
+
+    def test_max_ident_112(self):
+        dialect = self._dialect((11, 2, 0))
+
+        conn = mock.Mock(
+            execute=mock.Mock(return_value=mock.Mock(scalar="11.0.0"))
+        )
+        dialect.initialize(conn)
+        eq_(dialect.server_version_info, (11, 2, 0))
+        eq_(
+            dialect._get_effective_compat_server_version_info(conn), (11, 2, 0)
+        )
+        eq_(dialect.max_identifier_length, 30)
+
+    def test_max_ident_122_11compat(self):
+        dialect = self._dialect((12, 2, 0))
+
+        conn = mock.Mock(
+            execute=mock.Mock(return_value=mock.Mock(scalar=lambda: "11.0.0"))
+        )
+        dialect.initialize(conn)
+        eq_(dialect.server_version_info, (12, 2, 0))
+        eq_(
+            dialect._get_effective_compat_server_version_info(conn), (11, 0, 0)
+        )
+        eq_(dialect.max_identifier_length, 30)
+
+    def test_max_ident_122_11compat_vparam_raises(self):
+        dialect = self._dialect((12, 2, 0))
+
+        def c122():
+            raise exc.DBAPIError(
+                "statement", None, "no such table", None, None
+            )
+
+        conn = mock.Mock(
+            execute=mock.Mock(return_value=mock.Mock(scalar=c122))
+        )
+        with self._expect_max_ident_warning():
+            dialect.initialize(conn)
+        eq_(dialect.server_version_info, (12, 2, 0))
+        eq_(
+            dialect._get_effective_compat_server_version_info(conn), (12, 2, 0)
+        )
+        eq_(
+            dialect.max_identifier_length,
+            oracle.OracleDialect.max_identifier_length,
+        )
+
+    def test_max_ident_122_11compat_vparam_cant_parse(self):
+        dialect = self._dialect((12, 2, 0))
+
+        def c122():
+            return "12.thisiscrap.0"
+
+        conn = mock.Mock(
+            execute=mock.Mock(return_value=mock.Mock(scalar=c122))
+        )
+        with self._expect_max_ident_warning():
+            dialect.initialize(conn)
+        eq_(dialect.server_version_info, (12, 2, 0))
+        eq_(
+            dialect._get_effective_compat_server_version_info(conn), (12, 2, 0)
+        )
+        eq_(
+            dialect.max_identifier_length,
+            oracle.OracleDialect.max_identifier_length,
+        )
+
 
 class ExecuteTest(fixtures.TestBase):