-
Notifications
You must be signed in to change notification settings - Fork 192
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
QueryBuilder
: Use a nested session in iterall
and iterdict
#5736
QueryBuilder
: Use a nested session in iterall
and iterdict
#5736
Conversation
@sphuber this would be the case, if the number of rows yielded was more than the
so I'm afraid this is not a solution. |
Ah yeah of course. I guess I should update the test to the following: @pytest.mark.usefixtures('aiida_profile_clean')
def test_iterall_with_mutation(self):
"""Test that nodes can be mutated while being iterated using ``QueryBuilder.iterall``."""
count = 10
pks = []
for _ in range(count):
node = orm.Data().store()
pks.append(node.pk)
# Ensure that batch size is smaller than the total rows yielded
for [node] in orm.QueryBuilder().append(orm.Data).iterall(batch_size=2):
node.base.extras.set('key', 'value')
for pk in pks:
assert orm.load_node(pk).get_extra('key') == 'value' This now correctly fails, as only the first two nodes of the iteration are updated and the other batches are not processed. |
This passes that specific test, but not sure if it has other implications: def iterall(self, data: QueryDictType, batch_size: Optional[int]) -> Iterable[List[Any]]:
"""Return an iterator over all the results of a list of lists."""
with self.query_session(data) as build:
stmt = build.query.statement.execution_options(yield_per=batch_size)
session = self.get_session()
with session.begin_nested():
for resultrow in session.execute(stmt):
yield [self.to_backend(rowitem) for rowitem in resultrow] |
517790f
to
ef02bbc
Compare
Codecov ReportBase: 79.86% // Head: 80.25% // Increases project coverage by
Additional details and impacted files@@ Coverage Diff @@
## main #5736 +/- ##
==========================================
+ Coverage 79.86% 80.25% +0.40%
==========================================
Files 496 497 +1
Lines 37021 38058 +1037
==========================================
+ Hits 29563 30540 +977
- Misses 7458 7518 +60
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report at Codecov. |
ef02bbc
to
61aa335
Compare
Currently, when iterating over the rows of a query using `iterall` or `iterdict`, will raise `sqlalchemy.ProgrammingError` if any of the nodes returned by the iterator are mutated, for example by setting an extra. The reason is that adding an extra is mutating the node, and the model is wrapped in the `ModelWrapper` class which will automatically call save if a field of a stored node is changed. This is calling `commit` on the session which is invalidating the cursor. The documentation of sqlalchemy explicitly states that one should not commit on a cursor that is being used to iterate over a query with `yield_per`.
61aa335
to
1bfddf9
Compare
This will prevent the `ModelWrapper` from calling commit on the session when any of the yielded results is mutated as this will cause the cursor of the connection to be reset and it to become invalid. In the next iteration of the yield an exception will be raised. Note that a session transaction should only be opened if we are not already inside one, in which case a `nullcontext` is used.
1bfddf9
to
0c5890c
Compare
@chrisjsewell found the problem. Tests pass now, ready for final review |
QueryBuilder
: Catch exception in iterall
and iterdict
QueryBuilder
: Use a nested session in iterall
and iterdict
Fixes #5672
This is a minimal alternative to #5731 . It adds a test that reproduces the bug reported in #5672 and then adds the fix.
This will prevent the
ModelWrapper
from calling commit on the sessionwhen any of the yielded results is mutated as this will cause the cursor
of the connection to be reset and it to become invalid. In the next
iteration of the yield an exception will be raised.
Note that a session transaction should only be opened if we are not
already inside one, in which case a
nullcontext
is used.