Open indiVar0508 opened 2 years ago
This seem to be happening due to a change in SQLA from this thread, not sure i tried to look into it, might be happening due expression_reflector but not sure how , i can be completely wrong
Edit: I think issue lies in criteria method of relationship builder for MANYTOMANY direction for non-versioned object(s),
I think I am probably experiencing the same thing. I get the warning but as far as I can tell it isn't a problem as the output is exactly what I intended.
The schema:
class MediaType(Base):
__tablename__ = "media_types"
media_type: Mapped[str] = mapped_column(init=True, primary_key=True)
file_format: Mapped[str] = mapped_column(
default=None, unique=True, nullable=True
)
class Collection(Base):
__tablename__ = "collections"
collection_id: Mapped[int] = mapped_column(
init=False, primary_key=True, autoincrement=True
)
collection_name: Mapped[str] = mapped_column(default=None)
collection_description: Mapped[str] = mapped_column(
default=None, unique=False, nullable=True
)
media_type_id: Mapped[str] = mapped_column(
"media_type",
ForeignKey("media_types.media_type"), default=None
)
media_type: Mapped[MediaType] = relationship(init=False)
collection_ezname: Mapped[str] = mapped_column(Computed(func.LOWER(func.REGEXP_REPLACE((collection_name), '[ /%<>#";?:@&=+$,.]', '_', 'g'))), unique=True, init=False)
tags: Mapped[list] = mapped_column(ARRAY(TEXT, dimensions=1), default=None, nullable=True)
date_added: Mapped[datetime.datetime] = mapped_column(default=None, nullable=True)
__mapper_args__ = {
"polymorphic_identity": "collections",
"eager_defaults": True,
}
class Post(Base):
__tablename__ = "posts"
id: Mapped[int] = mapped_column(init=False, primary_key=True)
title: Mapped[str] = mapped_column(default=None, nullable=True)
description: Mapped[str] = mapped_column(default=None, nullable=True)
collection_id: Mapped[int] = mapped_column(
ForeignKey("collections.collection_id"), init=False
)
collection: Mapped[Collection] = relationship(kw_only=True)
media_type: Mapped[str] = mapped_column(
ForeignKey("media_types.media_type"), init=False
)
media_type_reference: Mapped[MediaType] = relationship(init=False)
tags: Mapped[list] = mapped_column(ARRAY(TEXT, dimensions=1), default=None, nullable=True)
views: Mapped[int] = mapped_column(default=0, nullable=True)
social_media: Mapped[dict] =mapped_column(JSONB, default=None, nullable=True)
date_added: Mapped[datetime.datetime] = mapped_column(default=None, nullable=True)
date_modified: Mapped[datetime.datetime] = mapped_column(default=None, nullable=True)
rss_pub_date: Mapped[str] = mapped_column(default=None, nullable=True)
added_to_rss: Mapped[bool] = mapped_column(default=False, nullable=True)
__mapper_args__ = {
"polymorphic_identity": "posts",
"polymorphic_on": "media_type",
"eager_defaults": True,
}
class Link(Post):
__tablename__ = "links"
id: Mapped[int] = mapped_column(
ForeignKey("posts.id"), init=False, primary_key=True
)
url: Mapped[str] = mapped_column(default=None, unique=True)
other_info: Mapped[str] = mapped_column(default=None, nullable=True)
clicks: Mapped[int] = mapped_column(default=0, nullable=True)
__mapper_args__ = {
"polymorphic_identity": "links",
"eager_defaults": True,
}
The line that makes the error:
link_collections = db.session.query(Collection).where(MediaType.media_type == "links").all()
print(link_collections)
The output (which, I stress, was the output I desired):
[Collection(collection_id=1, collection_name='Essentials', collection_description='', media_type_id='links', media_type=MediaType(media_type='links', file_format=None), collection_ezname='essentials', tags=None, date_added=None), Collection(collection_id=2, collection_name='NYC', collection_description='', media_type_id='links', media_type=MediaType(media_type='links', file_format=None), collection_ezname='nyc', tags=None, date_added=None), Collection(collection_id=3, collection_name='Amazing Art', collection_description='', media_type_id='links', media_type=MediaType(media_type='links', file_format=None), collection_ezname='amazing_art', tags=None, date_added=None),
....
]
I'm also getting this same warning, however its causing me troubles as it's returning ALL the models on one side of the many-to-many relationship.
For context I'm integrating into an existing project, I made one class versioned which made version tables for all of the many to many association tables.
Whenever I do video.versions[1].tags
I get ALL of the tags that exist
sqlalchemy_continuum/relationship_builder.py:81: SAWarning: SELECT statement has a cartesian product between FROM element(s) "tags" and FROM element "video_tags_version". Apply join condition(s) between each element to resolve.
return query.all()
I'm on sqlalchemy 1.4.42 and continuum 1.4.2
Update, I think I found the problem, indeed as @indiVar0508 mentioned, for context my Video
class is versioned and my Tag
class is not.
I think issue lies in criteria method of relationship builder for MANYTOMANY direction for non-versioned object(s),
The issue is that the join is only using the primaryjoin
for all of the types of relationships while many-to-many setups can have a seocndaryjoin
component as well. This is causing the cartesian product.
Example from my code
class Video(Base):
# truncated class for brevity
tags: Mapped[list["Tag"]] = relationship(
"Tag",
secondary=video_tags_association,
back_populates="videos",
primaryjoin="video_tags.c.videoid == Video.id",
secondaryjoin="and_(video_tags.c.tag_id == Tag.id, Tag.deleted_at.is_(None))",
)
and the reflector is only using primaryjoin
I got it to work by combining the conditions into the primaryjoin
primaryjoin="and_(video_tags.c.videoid == Video.id, video_tags.c.tag_id == Tag.id, Tag.deleted_at.is_(None))"
but it would be great if the logic would consider secondaryjoins
as SQLAlchemy recommends using primaryjoin + secondaryjoin
when joining across multiple tables.
Getting this warning when running pytest
tests/relationships/test_non_versioned_classes.py::TestManyToManyRelationshipToNonVersionedClass::test_single_insert
I created a normal py-script from testfile to replicate the issue
Logs:
This seem to be to happening only for SQLA > 1.4