Skip to content
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

Versioning issues on session.flush #79

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

dtheodor
Copy link
Contributor

Hey, opening a pull request with some fixes related to mid-transaction flushes. Check the added tests to see what I think the expected behavior should be

  • Keep the version's type as INSERT when a new object is flushed and then modified within the same single transaction. Currently there is no insert entry ever created.
  • Ensure the same versioning behavior when primary keys of objects are modified after a flush, with the behavior that you get when they are modified without a flush.

Update:

  • When a new object is created, flushed, then deleted (all in the same transaction), no version entry to be saved.
  • Postgres triggers have also been updated.

@@ -414,3 +414,20 @@ def changeset(obj):
if new_value:
data[prop.key] = [new_value, old_value]
return data


def commited_identity(obj):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Judging by the function description maybe we could just use: sa.inspect(obj).identity

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For sure! I knew there was probably a built-in API for this...

@kvesteri
Copy link
Owner

Good stuff and great unit tests!

@dtheodor
Copy link
Contributor Author

Progress has stalled a bit on this... on both items.

Added tests on behavior when primary keys are modified within the
same transaction but after a flush()
Both operations and unit of work internal dicts use PKs to point to
stored objects. If the PKs change mid-transaction, keys are now
updated to the new correct values.
utils.commited_identity is InstanceState.identity
trasaction

Added tests for the create/delete and update/delete within the same
transaction cases.
Added new operation type STALE_DATA that marks an object's version
entry as stale that should be removed.
Added method to remove version object in the uow.
Modify the UPDATE trigger to:
1. Leave operation type as INSERT for existing versions whose operation
type is INSERT. This fixes the missing INSERT version.
2. Use the OLD primary keys to check if a version already exists. This
fixes behavior on primary key updates
3. Added documentation
1. Delete versions that already exist with an operation type of
INSERT
2. Added documentation
@dtheodor
Copy link
Contributor Author

Fixing the INSERT + DELETE within the same transaction took some effort. I decided to add a new Operation.STALE_VERSION that signifies that the version is no longer valid, and a new uow.delete_version_object that takes care of it. if..else is used indiscriminately.

I also tried to update the transaction changes plugin but met different problems, so it is left as it was.

@dtheodor
Copy link
Contributor Author

I updated the Postgres triggers to have the same behavior on INSERT->UPDATE and INSERT->DELETE within the same transaction as the sql alchemy implementation. I also documented some decisions. Tests pass.

You may want to have a look at the implementation.

@@ -107,6 +107,7 @@ def after_version_class_built(self, parent_cls, version_cls):
primaryjoin=(
self.model_class.transaction_id == transaction_column
),
foreign_keys=[self.model_class.transaction_id]
foreign_keys=[self.model_class.transaction_id],
passive_deletes='all'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed otherwise after deleting a version sql alchemy will by default set the transaction_id of the TransactionChange to NULL, which is never desired.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than this I think a better one would be:

cascade='all, delete-orphan',
passive_deletes=True

This uses foreign keys for deletion, but at the same time marks the in memory objects as deleted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was happening was that deleting an object of ArticleVersion was setting the transaction_id of the TransactionChanges object to NULL, which was causing a AssertionError: Dependency rule tried to blank-out primary key column 'transaction_changes.transaction_id' on instance '<TransactionChanges at 0x7f7d17a544d0>' error.

Deleting a version object should never affect a TransactionChanges object imo (at least not through foreign key behavior). If for instance you have 2 new ArticleVersions, deleting one of them should not affect the existing TransactionChanges that points to the ArticleVersion (Deleting both of them should, but that's a different story).

With the above in mind, I don't think the cascade='all, delete-orphan' has a place here as it creates a wrong impression about what the motivation here is. The passive_deletes=True does make sense. I set it to passive_deletes='all' because according to documentation, if the changes object is in memory SQL Alchemy will still set the foreign key to NULL, which is not desired. This whole thing can also be avoided if we remove the .changes attribute on the version class; it can still be accessed through the .transaction.changes attribute chain.

What does make sense is to use cascade='all, delete-orphan' on the Transaction->TransactionChanges relation. When a whole transcation is deleted, the changes have to go away with it as well.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm thanks for pointing that out! I removed the generated changes attr from version classes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants