question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Duplicate key value on many-to-many relationship versioning

See original GitHub issue

I have a many-to-many relationship, here’s the models:

class Membership(BaseModel):
    tenant_id = Column(Integer, ForeignKey('tenant.tenant_id', ondelete='CASCADE'), primary_key=True)
    user_id = Column(Integer, ForeignKey('user.user_id', ondelete='CASCADE'), primary_key=True)

class User(BaseModel):
    user_id = Column(Integer, primary_key=True)

class Tenant(BaseModel):
    tenant_id = Column(Integer, primary_key=True)

When I tried to create the association model, I got psycopg2.IntegrityError error on the versioned membership table.

>> m = Membership(tenant_id=tenant_id, user_id=user_id)
>> db.session.add(m)
>> db.commit()
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1182, in _execute_context
    context)
  File "/usr/local/lib/python3.6/site-packages/sqlalchemy/engine/default.py", line 470, in do_execute
    cursor.execute(statement, parameters)
psycopg2.IntegrityError: duplicate key value violates unique constraint "version_membership_pkey"
DETAIL:  Key (tenant_id, user_id, transaction_id)=(1, 1, 2) already exists.

However, it seems to work fine if I use the association table instead of the association object to create this relationship:

membership = db.Table('membership',
                      Column('tenant_id', Integer, ForeignKey('tenant.tenant_id', ondelete='CASCADE'), primary_key=True),
                      Column('user_id', Integer, ForeignKey('user.user_id', ondelete='CASCADE'), primary_key=True))

But I do need association object in my case to support extra columns. Does Continuum actually support versioning use of association object for many-to-many relationship? Or is there any configuration I’m missing here to get this work?

Issue Analytics

  • State:open
  • Created 6 years ago
  • Reactions:3
  • Comments:17 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
Simescommented, Jul 16, 2020

I didn’t put the kludge I come up with into this thread originally, because it does seem to be a horrible kludge. But, as this issue is still ongoing, maybe it’ll be helpful to someone. It removes the duplicate statements by the hacky method of putting them into a dictionary and using the stringified version of the statement as the key.

Add the replacement UnitOfWork class to your codebase somewhere:

class UnitOfWork(sqlalchemy_continuum.UnitOfWork):
    """ We replace the function we need to patch, otherwise the superclass still does everything
    """

    def create_association_versions(self, session):
        """
        Creates association table version records for given session.
        :param session: SQLAlchemy session object

        """
        # statements = copy(self.pending_statements)

        # Dedupe statements
        temp = { str(st) : st for st in self.pending_statements }

        statements = temp.values()

        for stmt in statements:
            stmt = stmt.values(
                **{
                    self.manager.options['transaction_column_name']:
                    self.current_transaction.id
                }
            )
            session.execute(stmt)
        self.pending_statements = []

Also, right after your call to make_versioned, insert the code to replace the supplied UnitOfWork class with the patched one:

sqlalchemy_continuum.versioning_manager.uow_class = horrible_kludges.UnitOfWork

I am 100% certain that a better solution than this exists and I am by no means advocating for adding this to Continuum. But it does get rid of the issue, at the cost of additional execution time.

1reaction
maarten-dpcommented, Jul 17, 2020

I’ve made a small alembic migration script that fixes this issue, for anyone in need

from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils

from sqlalchemy_continuum.version import VersionClassBase
from my_package import db
import inspect


def alter_version_model(klass, conn, metadata):
    table_name = klass.__table__.name
    pk_cols = [c.name for c in klass.__table__.primary_key.columns]

    existing_table = sa.Table(table_name, metadata, autoload_with=conn)
    existing_pk_cols = [c.name for c in existing_table.primary_key.columns]
    pkey = existing_table.primary_key.name

    if sorted(pk_cols) == sorted(existing_pk_cols):
        print('Nothing to do for {}'.format(table_name))
        return

    msg = 'Updating the PK of {} from {} to {}'
    print(msg.format(table_name, existing_pk_cols, pk_cols))

    op.drop_constraint(pkey, table_name)
    op.create_primary_key(pkey, table_name, pk_cols)


def upgrade():
    conn = op.get_bind()
    metadata = sa.schema.MetaData()

    for klass in db.Model._decl_class_registry.values():
        if inspect.isclass(klass) and issubclass(klass, VersionClassBase):
            alter_version_model(klass, conn, metadata)


def downgrade():
    pass

I also confirm that running this migration script fixes my issues.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to avoid duplicate entries in a many-to-many relationship ...
If the name property of the Tag entity is only a UNIQUE KEY, that's probably why it's always returning false. You will need...
Read more >
Entity Framework - saving/updating many to many ... - MSDN
I'm getting a duplicate key insert exception on "SaveChanges()" call, which is quite logical here. EF tries to insert new data instead of ......
Read more >
Duplicate Entry For Key Primary In Many To Many ... - ADocLib
We have an unidirectional ManyToMany relationship between two objects Student and Module. JDBCExceptionReporter - Duplicate entry '46' for key 'students_id' In ...
Read more >
Best practices for managing many-to-many relationships
The advantages of this pattern include minimal data duplication and simplified ... The partition key in this example is either an InvoiceID or...
Read more >
Common Hibernate Exceptions - Baeldung
Object-Relational mapping is a major benefit of Hibernate. ... duplicate mapping for a class, table, or property name ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found