]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
un-deprecate Oracle 2pc
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 27 Jan 2021 20:29:29 +0000 (15:29 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 3 Feb 2021 21:24:37 +0000 (16:24 -0500)
Oracle two-phase transactions at a rudimentary level are now no longer
deprecated. After receiving support from cx_Oracle devs we can provide for
basic xid + begin/prepare support with some limitations, which will work
more fully in an upcoming release of cx_Oracle. Two phase "recovery" is not
currently supported.

Fixes: #5884
Change-Id: I961c0ad14a530acc6b069bd9bfce99fc34124abc

doc/build/changelog/unreleased_14/5884.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/oracle/cx_oracle.py
lib/sqlalchemy/dialects/oracle/provision.py
test/engine/test_transaction.py
test/requirements.py

diff --git a/doc/build/changelog/unreleased_14/5884.rst b/doc/build/changelog/unreleased_14/5884.rst
new file mode 100644 (file)
index 0000000..11365c8
--- /dev/null
@@ -0,0 +1,9 @@
+.. change::
+    :tags: bug, oracle
+    :tickets: 5884
+
+    Oracle two-phase transactions at a rudimentary level are now no longer
+    deprecated. After receiving support from cx_Oracle devs we can provide for
+    basic xid + begin/prepare support with some limitations, which will work
+    more fully in an upcoming release of cx_Oracle. Two phase "recovery" is not
+    currently supported.
index df00e071f4c626f9e5b6810b99ffdc5370373ae8..37e67a0d54e98b6c08f42b5f0ee339491a375145 100644 (file)
@@ -1175,14 +1175,6 @@ class OracleDialect_cx_oracle(OracleDialect):
         else:
             return False
 
-    @util.deprecated(
-        "1.2",
-        "The create_xid() method of the cx_Oracle dialect is deprecated and "
-        "will be removed in a future release.  "
-        "Two-phase transaction support is no longer functional "
-        "in SQLAlchemy's cx_Oracle dialect as of cx_Oracle 6.0b1, which no "
-        "longer supports the API that SQLAlchemy relied upon.",
-    )
     def create_xid(self):
         """create a two-phase transaction ID.
 
@@ -1201,6 +1193,7 @@ class OracleDialect_cx_oracle(OracleDialect):
 
     def do_begin_twophase(self, connection, xid):
         connection.connection.begin(*xid)
+        connection.connection.info["cx_oracle_xid"] = xid
 
     def do_prepare_twophase(self, connection, xid):
         result = connection.connection.prepare()
@@ -1210,16 +1203,23 @@ class OracleDialect_cx_oracle(OracleDialect):
         self, connection, xid, is_prepared=True, recover=False
     ):
         self.do_rollback(connection.connection)
+        # TODO: need to end XA state here
 
     def do_commit_twophase(
         self, connection, xid, is_prepared=True, recover=False
     ):
+
         if not is_prepared:
             self.do_commit(connection.connection)
         else:
+            if recover:
+                raise NotImplementedError(
+                    "2pc recovery not implemented for cx_Oracle"
+                )
             oci_prepared = connection.info["cx_oracle_prepared"]
             if oci_prepared:
                 self.do_commit(connection.connection)
+        # TODO: need to end XA state here
 
     def do_set_input_sizes(self, cursor, list_of_tuples, context):
         if self.positional:
@@ -1245,7 +1245,9 @@ class OracleDialect_cx_oracle(OracleDialect):
             cursor.setinputsizes(**{key: dbtype for key, dbtype in collection})
 
     def do_recover_twophase(self, connection):
-        connection.info.pop("cx_oracle_prepared", None)
+        raise NotImplementedError(
+            "recover two phase query for cx_Oracle not implemented"
+        )
 
 
 dialect = OracleDialect_cx_oracle
index e0dadd58ea0e490d41ac05a3c9f8751f98d4918b..239c23996fd8aa66547396817852fcf520207cb4 100644 (file)
@@ -91,6 +91,14 @@ def _oracle_post_configure_engine(url, engine, follower_ident):
     def checkout(dbapi_con, con_record, con_proxy):
         _all_conns.add(dbapi_con)
 
+    @event.listens_for(engine, "checkin")
+    def checkin(dbapi_connection, connection_record):
+        # work around cx_Oracle issue:
+        # https://github.com/oracle/python-cx_Oracle/issues/530
+        # invalidate oracle connections that had 2pc set up
+        if "cx_oracle_xid" in connection_record.info:
+            connection_record.invalidate()
+
 
 @run_reap_dbs.for_db("oracle")
 def _reap_oracle_dbs(url, idents):
index 8a8d1fa62d66f6f22f41b535cb9525d09e93f40e..7ba189d3d6dc80b9469199d4e9743573fad75257 100644 (file)
@@ -407,12 +407,12 @@ class TransactionTest(fixtures.TablesTest):
                 ).fetchall(),
                 [],
             )
-
         # recover_twophase needs to be run in a new transaction
         with testing.db.connect() as connection2:
             recoverables = connection2.recover_twophase()
             assert transaction.xid in recoverables
             connection2.commit_prepared(transaction.xid, recover=True)
+
             eq_(
                 connection2.execute(
                     select(users.c.user_id).order_by(users.c.user_id)
index 1a4c5f646d066bd8c17775bf314b5705e577e3ea..14d4ccee6b77cbd0bcde71a320e1efecc43b0993 100644 (file)
@@ -768,9 +768,6 @@ class DefaultRequirements(SuiteRequirements):
             [
                 no_support("firebird", "no SA implementation"),
                 no_support("mssql", "two-phase xact not supported by drivers"),
-                no_support(
-                    "oracle", "two-phase xact not implemented in SQLA/oracle"
-                ),
                 no_support(
                     "sqlite", "two-phase xact not supported by database"
                 ),
@@ -801,6 +798,7 @@ class DefaultRequirements(SuiteRequirements):
                 ["mysql", "mariadb"],
                 "still can't get recover to work w/ MariaDB / MySQL",
             )
+            + skip_if("oracle", "recovery not functional")
         )
 
     @property