New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
don't allow implicit inactive transactions on connection #4712
Comments
Of course just as i type this all out, I realise I forgot begin_nested(). |
heh, you know what, while I've likely seen this kind of thing many times over the years, today I think it is a bug. The transaction has been rolled back in your test, but your outer transaction has not been rolled back, then the connection keeps emitting SQL on a new transaction. The outermost transaction you've created is now a do-nothing object. objects that go into a "do nothing" state implicitly create confusion like this. I had to stare for awhile at the code to see where it went wrong. The confusion of this is kind of validating to the behavior that the Session has in this regard, which is is that if you roll back an "inner" "sub" transaction, it won't let anything happen until you rollback all the way out to the outermost transaction block. This is the behavior I struggled to defend in https://docs.sqlalchemy.org/en/13/faq/sessions.html#but-why-isn-t-the-one-automatic-call-to-rollback-enough-why-must-i-rollback-again . In this case, it would have made this situation much easier to spot. I have a patch coming for 1.4 only, since this is quite a fundamental behavioral change, where your test above will emit: sqlalchemy.exc.InvalidRequestError: This connection is on an inactive transaction. Please rollback() fully before proceeding. (Background on this error at: http://sqlalche.me/e/8s2a) |
@zzzeek Thanks for your explanation, (and for even looking at closed bugs) I can't really grasp why the commit makes a difference. from sqlalchemy import Column
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column('id', Integer(), primary_key=True, nullable=False)
name = Column('name', String(128), nullable=False)
engine = create_engine('postgresql://u:p@localhost:5432/test_db', echo=True)
Base.metadata.bind = engine
Base.metadata.create_all(engine)
connection = engine.connect()
Session = sessionmaker(bind=connection)
# End of basic setup
session = Session()
session.query(User).all() # this is crucial
# with the below commit, this code runs fine. Without it crashes.
# session.commit()
transaction = connection.begin()
transaction.rollback()
session.commit() # Error: This transaction is inactive
|
the Session is starting a "transaction" as soon as you ask it to do anything, like query.all(), and not before. it does this by calling "conn = bind.connect(); conn.begin()". When you bind the Session to a Connection and not an Engine, the "connect()" method just returns a Connection object that is a proxy for the Connection that's already there. So it calls begin(), makes a Transaction at the root. Then you make a subtransaction on that with your own begin(), then you roll it back. This makes the outer transaction inactive. The Session is still holding onto the actual Transaction object, so when it tries to commit, it fails. The change I'm making is that the Connection itself doesn't throw away these Transaction objects either until there is an explicit call to rollback(). |
Mike Bayer has proposed a fix for this issue in the master branch: Don't discard inactive transaction until it is explicitly rolled back https://gerrit.sqlalchemy.org/1302 |
In preparation for #4712, add an errors.rst code to the Session's exception about waiting to be rolled back and rework the FAQ entry to be much more succinct. When this FAQ was first written, I found it hard to describe why flush worked this way but as the use case is clearer now, and #4712 actually showed it being confusing when it doesn't work this way, we can make a simpler and more definitive statement about this behavior. Additionally, language about "subtransactions" is minimized as I might be removing or de-emphasizing this concept in 2.0 (though maybe not as it does seem to work well). Change-Id: I557872aff255b07e14dd843aa024e027a017afb8 (cherry picked from commit 4bd3cf6)
In preparation for #4712, add an errors.rst code to the Session's exception about waiting to be rolled back and rework the FAQ entry to be much more succinct. When this FAQ was first written, I found it hard to describe why flush worked this way but as the use case is clearer now, and #4712 actually showed it being confusing when it doesn't work this way, we can make a simpler and more definitive statement about this behavior. Additionally, language about "subtransactions" is minimized as I might be removing or de-emphasizing this concept in 2.0 (though maybe not as it does seem to work well). Change-Id: I557872aff255b07e14dd843aa024e027a017afb8 (cherry picked from commit 4bd3cf6)
In preparation for #4712, add an errors.rst code to the Session's exception about waiting to be rolled back and rework the FAQ entry to be much more succinct. When this FAQ was first written, I found it hard to describe why flush worked this way but as the use case is clearer now, and #4712 actually showed it being confusing when it doesn't work this way, we can make a simpler and more definitive statement about this behavior. Additionally, language about "subtransactions" is minimized as I might be removing or de-emphasizing this concept in 2.0 (though maybe not as it does seem to work well). Change-Id: I557872aff255b07e14dd843aa024e027a017afb8
SQLAlchemy 1.4 will be adding stricter rules to Connection objects such that a new transaction cannot be started while an old one hasn't been cleared. Ensure this particular test fixture clears the "savepoint" transaction first before starting a new one. References: sqlalchemy/sqlalchemy#4712 Change-Id: Idcb616fd3add4d58f22b91865cec8a54fe492093
* Update oslo.db from branch 'master' - Rollback existing nested transacvtion before restarting SQLAlchemy 1.4 will be adding stricter rules to Connection objects such that a new transaction cannot be started while an old one hasn't been cleared. Ensure this particular test fixture clears the "savepoint" transaction first before starting a new one. References: sqlalchemy/sqlalchemy#4712 Change-Id: Idcb616fd3add4d58f22b91865cec8a54fe492093
Not sure if question or bug...
I'm using https://docs.sqlalchemy.org/en/13/orm/session_transaction.html#joining-a-session-into-an-external-transaction-such-as-for-test-suites as a guide.
This is the code
It's important that test 1 runs before test 2 on your system.
You can also just run test 1 alone, and see users being added to your database, even though it is meant to rollback.
I am expecting test 2 to not see the user that is added by test 1.
But that
session.flush()
for some reason makes the whole test transaction rollback fail and the user is actually persisted in the database.libraries
database: psql (10.5 (Debian 10.5-1.pgdg90+1))
How should I setup a test like this?
It's not that I want to
flush
, but I actually want to do asession.query(User).all()
, but that has the same effect: User gets persisted.I assume the reason is that it never enters the if-statement in the listener, but then the documentation is wrong?
The text was updated successfully, but these errors were encountered: