From: Ants Aasma Date: Mon, 8 Oct 2007 15:25:51 +0000 (+0000) Subject: - fix multiple consequent two phase transactions not working with postgres. For some... X-Git-Tag: rel_0_4_0~55 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=47d3f45d583ce82e5c6f967e580716cbb8e1791b;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - fix multiple consequent two phase transactions not working with postgres. For some reason implicit transactions are not enough. [ticket:810] - add an option to scoped session mapper extension to not automatically save new objects to session. --- diff --git a/CHANGES b/CHANGES index 9fd4b82ca6..bad34bd22a 100644 --- a/CHANGES +++ b/CHANGES @@ -48,6 +48,11 @@ CHANGES - Firebird now uses dialect.preparer to format sequences names +- Fixed breakage with postgres and multiple two phase transactions. For some + reason the implicitly started transaction is not enough. [ticket:810] + +- Added an option to the _ScopedExt mapper extension to not automatically + save new objects to session on object initialization. 0.4.0beta6 ---------- diff --git a/lib/sqlalchemy/databases/postgres.py b/lib/sqlalchemy/databases/postgres.py index 74b9e6f437..345893524c 100644 --- a/lib/sqlalchemy/databases/postgres.py +++ b/lib/sqlalchemy/databases/postgres.py @@ -306,6 +306,9 @@ class PGDialect(default.DefaultDialect): return sqltypes.adapt_type(typeobj, colspecs) def do_begin_twophase(self, connection, xid): + # Two phase transactions seem to require that the transaction is explicitly started. + # The implicit transactions that usually work aren't enough. + connection.execute(sql.text("BEGIN")) self.do_begin(connection.connection) def do_prepare_twophase(self, connection, xid): diff --git a/lib/sqlalchemy/orm/scoping.py b/lib/sqlalchemy/orm/scoping.py index f4cec04333..e29f91da7d 100644 --- a/lib/sqlalchemy/orm/scoping.py +++ b/lib/sqlalchemy/orm/scoping.py @@ -1,4 +1,4 @@ -from sqlalchemy.util import ScopedRegistry, to_list +from sqlalchemy.util import ScopedRegistry, to_list, get_cls_kwargs from sqlalchemy.orm import MapperExtension, EXT_CONTINUE, object_session from sqlalchemy.orm.session import Session from sqlalchemy import exceptions @@ -52,10 +52,12 @@ class ScopedSession(object): """return a mapper() function which associates this ScopedSession with the Mapper.""" from sqlalchemy.orm import mapper - validate = kwargs.pop('validate', False) + + extension_args = dict((arg,kwargs.pop(arg)) for arg in get_cls_kwargs(_ScopedExt) if arg in kwargs) + kwargs['extension'] = extension = to_list(kwargs.get('extension', [])) - if validate: - extension.append(self.extension.validating()) + if extension_args: + extension.append(self.extension.configure(**extension_args)) else: extension.append(self.extension) return mapper(*args, **kwargs) @@ -89,13 +91,17 @@ for prop in ('close_all','object_session', 'identity_key'): setattr(ScopedSession, prop, clslevel(prop)) class _ScopedExt(MapperExtension): - def __init__(self, context, validate=False): + def __init__(self, context, validate=False, save_on_init=True): self.context = context self.validate = validate + self.save_on_init = save_on_init def validating(self): return _ScopedExt(self.context, validate=True) - + + def configure(self, **kwargs): + return _ScopedExt(self.context, **kwargs) + def get_session(self): return self.context.registry() @@ -117,7 +123,8 @@ class _ScopedExt(MapperExtension): if not mapper.get_property(key, resolve_synonyms=False, raiseerr=False): raise exceptions.ArgumentError("Invalid __init__ argument: '%s'" % key) setattr(instance, key, value) - session._save_impl(instance, entity_name=kwargs.pop('_sa_entity_name', None)) + if self.save_on_init: + session._save_impl(instance, entity_name=kwargs.pop('_sa_entity_name', None)) return EXT_CONTINUE def init_failed(self, mapper, class_, oldinit, instance, args, kwargs): diff --git a/test/engine/transaction.py b/test/engine/transaction.py index 6a26bf597d..6a5383b8c4 100644 --- a/test/engine/transaction.py +++ b/test/engine/transaction.py @@ -310,7 +310,36 @@ class TransactionTest(PersistTest): [(1,)] ) connection2.close() - + + @testing.supported('postgres', 'mysql') + @testing.exclude('mysql', '<', (5, 0, 3)) + def testmultipletwophase(self): + conn = testbase.db.connect() + + xa = conn.begin_twophase() + conn.execute(users.insert(), user_id=1, user_name='user1') + xa.prepare() + xa.commit() + + xa = conn.begin_twophase() + conn.execute(users.insert(), user_id=2, user_name='user2') + xa.prepare() + xa.rollback() + + xa = conn.begin_twophase() + conn.execute(users.insert(), user_id=3, user_name='user3') + xa.rollback() + + xa = conn.begin_twophase() + conn.execute(users.insert(), user_id=4, user_name='user4') + xa.prepare() + xa.commit() + + result = conn.execute(select([users.c.user_name]).order_by(users.c.user_id)) + self.assertEqual(result.fetchall(), [('user1',),('user4',)]) + + conn.close() + class AutoRollbackTest(PersistTest): def setUpAll(self): global metadata