]> 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:17 +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..5ff489e
--- /dev/null
@@ -0,0 +1,13 @@
+.. 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 d0facb956d86e4732e54fba2dc58910667268159..48b90f7e9bf1691dbe847e4dd1e8f97f2cfb7af1 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
-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.
 
 As of SQLAlchemy 1.4, the default max identifier length for the Oracle dialect
 is 128 characters.  Upon first connect, the compatibility version is detected
@@ -1226,11 +1228,6 @@ class OracleDialect(default.DefaultDialect):
     supports_unicode_binds = False
     max_identifier_length = 128
 
-    # 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
 
@@ -1293,12 +1290,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,)
         )
@@ -1308,18 +1299,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
 
@@ -1344,7 +1341,10 @@ 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,
+        ):
             return 30
         else:
             # use the default
index 866cbf5c98df1271e2661b4ec1522168cd389af6..c29a0b76d799d48b001f25f09ebf9b8ab56836b0 100644 (file)
@@ -1,5 +1,6 @@
 # coding: utf-8
 
+import re
 
 from sqlalchemy import bindparam
 from sqlalchemy import create_engine
@@ -234,6 +235,111 @@ class CompatFlagsTest(fixtures.TestBase, AssertsCompiledSQL):
         self.assert_compile(Unicode(50), "NVARCHAR2(50)", dialect=dialect)
         self.assert_compile(UnicodeText(), "NCLOB", dialect=dialect)
 
+    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"))
+        )
+        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"))
+        )
+        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))
+        )
+        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))
+        )
+        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):