From: Mike Bayer Date: Wed, 27 Jan 2021 20:29:29 +0000 (-0500) Subject: un-deprecate Oracle 2pc X-Git-Tag: rel_1_4_0b2~2^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8bf5ca1e9fd05a68ee12af3b00cf5733b6960be3;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git un-deprecate Oracle 2pc 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 --- diff --git a/doc/build/changelog/unreleased_14/5884.rst b/doc/build/changelog/unreleased_14/5884.rst new file mode 100644 index 0000000000..11365c8bc6 --- /dev/null +++ b/doc/build/changelog/unreleased_14/5884.rst @@ -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. diff --git a/lib/sqlalchemy/dialects/oracle/cx_oracle.py b/lib/sqlalchemy/dialects/oracle/cx_oracle.py index df00e071f4..37e67a0d54 100644 --- a/lib/sqlalchemy/dialects/oracle/cx_oracle.py +++ b/lib/sqlalchemy/dialects/oracle/cx_oracle.py @@ -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 diff --git a/lib/sqlalchemy/dialects/oracle/provision.py b/lib/sqlalchemy/dialects/oracle/provision.py index e0dadd58ea..239c23996f 100644 --- a/lib/sqlalchemy/dialects/oracle/provision.py +++ b/lib/sqlalchemy/dialects/oracle/provision.py @@ -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): diff --git a/test/engine/test_transaction.py b/test/engine/test_transaction.py index 8a8d1fa62d..7ba189d3d6 100644 --- a/test/engine/test_transaction.py +++ b/test/engine/test_transaction.py @@ -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) diff --git a/test/requirements.py b/test/requirements.py index 1a4c5f646d..14d4ccee6b 100644 --- a/test/requirements.py +++ b/test/requirements.py @@ -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